J'ai un projet qui utilise Entity Framework. En appelant SaveChanges
sur ma DbContext
, j'obtiens l'exception suivante:
System.Data.Entity.Validation.DbEntityValidationException: Validation échoué pour une ou plusieurs entités. Voir la propriété 'EntityValidationErrors' pour plus de détails.
Tout va bien, mais je ne veux pas attacher un débogueur à chaque fois que cette exception se produit. De plus, dans les environnements de production, je ne peux pas facilement attacher un débogueur, je dois donc faire de gros efforts pour reproduire ces erreurs.
Comment puis-je voir les détails cachés dans la DbEntityValidationException
?
La solution la plus simple consiste à remplacer SaveChanges
dans votre classe d'entités. Vous pouvez intercepter la DbEntityValidationException
, décompresser les erreurs réelles et créer une nouvelle DbEntityValidationException
avec le message amélioré.
Votre message d'exception ressemblera maintenant à ceci:
System.Data.Entity.Validation.DbEntityValidationException: Validation échoué pour une ou plusieurs entités. Voir la propriété 'EntityValidationErrors' pour plus de détails. Les erreurs de validation sont les suivantes: Le champ PhoneNumber doit être une chaîne ou un type de tableau avec une longueur maximale de '12'; Le Le champ Nom est requis.
Vous pouvez supprimer les SaveChanges remplacés dans n’importe quelle classe qui hérite de DbContext
:
public partial class SomethingSomethingEntities
{
public override int SaveChanges()
{
try
{
return base.SaveChanges();
}
catch (DbEntityValidationException ex)
{
// Retrieve the error messages as a list of strings.
var errorMessages = ex.EntityValidationErrors
.SelectMany(x => x.ValidationErrors)
.Select(x => x.ErrorMessage);
// Join the list to a single string.
var fullErrorMessage = string.Join("; ", errorMessages);
// Combine the original exception message with the new one.
var exceptionMessage = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);
// Throw a new DbEntityValidationException with the improved exception message.
throw new DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
}
}
}
DbEntityValidationException
contient également les entités qui ont provoqué les erreurs de validation. Ainsi, si vous avez besoin d'informations supplémentaires, vous pouvez modifier le code ci-dessus pour générer des informations sur ces entités.
Voir aussi: http://devillers.nl/improving-dbentityvalidationexception/
Comme Martin l'a indiqué, la DbEntityValidationResult
contient plus d'informations. J'ai trouvé utile d'obtenir à la fois mon nom de classe POCO et mon nom de propriété, et je voulais éviter d'avoir à écrire des attributs ErrorMessage
personnalisés sur toutes mes balises [Required]
rien que pour cela.
Le code suivant de Tweak au code de Martin s'est occupé de ces détails pour moi:
// Retrieve the error messages as a list of strings.
List<string> errorMessages = new List<string>();
foreach (DbEntityValidationResult validationResult in ex.EntityValidationErrors)
{
string entityName = validationResult.Entry.Entity.GetType().Name;
foreach (DbValidationError error in validationResult.ValidationErrors)
{
errorMessages.Add(entityName + "." + error.PropertyName + ": " + error.ErrorMessage);
}
}
Pour afficher la collection EntityValidationErrors
, ajoutez l'expression de surveillance suivante à la fenêtre de surveillance.
((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors
J'utilise Visual Studio 2013
Pendant que vous êtes en mode débogage dans le bloc catch {...}
, ouvrez la fenêtre "QuickWatch" (ctrl+alt+q) et coller dedans:
((System.Data.Entity.Validation.DbEntityValidationException)ex).EntityValidationErrors
Cela vous permettra d’avancer dans l’arborescence ValidationErrors
name__. C'est le moyen le plus simple que j'ai trouvé d'obtenir un aperçu instantané de ces erreurs.
Pour les utilisateurs de Visual 2012+ qui se soucient uniquement de la première erreur et qui peuvent ne pas avoir de bloc catch
name__, vous pouvez même effectuer les opérations suivantes:
((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors.First().ValidationErrors.First().ErrorMessage
Pour trouver rapidement un message d'erreur significatif en inspectant l'erreur lors du débogage:
Ajouter une surveillance rapide pour:
((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors
Accédez à EntityValidationErrors de la manière suivante:
(élément de collecte, par exemple [0])> ValidationErrors> (élément de collection, par exemple [0])> ErrorMessage
En fait, il ne s'agit que du problème de validation, EF validera d'abord les propriétés de l'entité avant d'apporter des modifications à la base de données . EF vérifiera donc si la valeur de la propriété est hors limites, comme lors de la conception de la table. Table_Column_UserName est varchar (20). Mais, dans EF, vous avez entré une valeur supérieure à 20.. Ou, dans d'autres cas, si la colonne ne permet pas d'être Null . Dans le processus de validation, vous devez donc définir une valeur. à la colonne non nulle, que vous fassiez le changement ou non ..__ Personnellement, j'aime la réponse de Leniel Macaferi. Il peut vous montrer le détail des problèmes de validation
Je pense que "Les erreurs de validation réelles" peuvent contenir des informations sensibles, ce qui pourrait expliquer pourquoi Microsoft a choisi de les placer dans un autre endroit (propriétés). La solution indiquée ici est pratique, mais il convient de la prendre avec prudence.
Je préférerais créer une méthode d'extension. Plus de raisons à cela:
Pour Azure Functions, nous utilisons cette simple extension vers Microsoft.Extensions.Logging.ILogger
public static class LoggerExtensions
{
public static void Error(this ILogger logger, string message, Exception exception)
{
if (exception is DbEntityValidationException dbException)
{
message += "\nValidation Errors: ";
foreach (var error in dbException.EntityValidationErrors.SelectMany(entity => entity.ValidationErrors))
{
message += $"\n * Field name: {error.PropertyName}, Error message: {error.ErrorMessage}";
}
}
logger.LogError(default(EventId), exception, message);
}
}
et exemple d'utilisation:
try
{
do something with request and EF
}
catch (Exception e)
{
log.Error($"Failed to create customer due to an exception: {e.Message}", e);
return await StringResponseUtil.CreateResponse(HttpStatusCode.InternalServerError, e.Message);
}
Utilisez try block dans votre code comme
try
{
// Your code...
// Could also be before try if you know the exception occurs in SaveChanges
context.SaveChanges();
}
catch (DbEntityValidationException e)
{
foreach (var eve in e.EntityValidationErrors)
{
Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
eve.Entry.Entity.GetType().Name, eve.Entry.State);
foreach (var ve in eve.ValidationErrors)
{
Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
ve.PropertyName, ve.ErrorMessage);
}
}
throw;
}
Vous pouvez vérifier les détails ici aussi