DbEntityValidationException - Comment savoir facilement la cause de l'erreur?

217

J'ai un projet qui utilise Entity Framework. En faisant appel SaveChangesà mon DbContext, j'obtiens l'exception suivante:

System.Data.Entity.Validation.DbEntityValidationException: la validation a échoué pour une ou plusieurs entités. Voir la propriété 'EntityValidationErrors' pour plus de détails.

C'est très bien et dandy, 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 grands efforts pour reproduire ces erreurs.

Comment puis-je voir les détails cachés dans le DbEntityValidationException?

Martin Devillers
la source

Réponses:

429

La solution la plus simple consiste à remplacer SaveChangesvotre classe d'entités. Vous pouvez attraper le DbEntityValidationException, déballer les erreurs réelles et en créer un nouveau DbEntityValidationExceptionavec le message amélioré.

  1. Créez une classe partielle à côté de votre fichier SomethingSomething.Context.cs.
  2. Utilisez le code au bas de cet article.
  3. C'est tout. Votre implémentation utilisera automatiquement les SaveChanges remplacés sans aucun travail de refactorisation.

Votre message d'exception ressemblera maintenant à ceci:

System.Data.Entity.Validation.DbEntityValidationException: la validation a é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 d'une longueur maximale de '12'; Le champ LastName est obligatoire.

Vous pouvez supprimer les SaveChanges substitué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);
        }
    }
}

Le DbEntityValidationExceptioncontient également les entités qui ont provoqué les erreurs de validation. Donc, si vous avez besoin de plus d'informations, vous pouvez modifier le code ci-dessus pour afficher des informations sur ces entités.

Voir aussi: http://devillers.nl/improving-dbentityvalidationexception/

Martin Devillers
la source
6
La classe Entities générée hérite déjà de DbContext, vous n'avez donc pas à l'ajouter à nouveau sur la classe partielle. Vous ne casserez ou ne changerez rien en l'ajoutant à la classe partielle. En fait, si vous ajoutez l'héritage de DbContext, Resharper vous proposera de le supprimer: "Le type de base 'DbContext' est déjà spécifié dans d'autres parties."
Martin Devillers
15
Pourquoi n'est-ce pas le comportement par défaut de SaveChanges?
John Shedletsky
4
"Pourquoi n'est-ce pas le comportement par défaut de SaveChanges?" - C'est une très bonne question. C'était une solution incroyable, cela m'a fait gagner des heures! Je devais jeterusing System.Linq;
John August
1
Mes erreurs de création de vue sur base.SaveChanges () qui se trouve dans le bloc try. Il ne saute jamais dans le bloc de capture. J'ai obtenu votre code pour contourner SaveChanges, mais il n'entre jamais dans le bloc de capture en cas d'erreur.
JustJohn
7
Vous devez définir l'exception interne pour conserver la trace de la pile.
dotjoe
48

Comme Martin l'a indiqué, il y a plus d'informations dans le DbEntityValidationResult. J'ai trouvé utile d'obtenir à la fois le nom de ma classe POCO et le nom de la propriété dans chaque message, et je voulais éviter d'avoir à écrire des ErrorMessageattributs personnalisés sur toutes mes [Required]balises juste pour cela.

La modification suivante du code de Martin a pris soin 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);
    }
}
Eric Hirst
la source
1
Utilisation SelectMany and Aggregatedans github par Daring Coders
Kiquenet
43

Pour afficher la EntityValidationErrorscollection, ajoutez l'expression Watch suivante à la fenêtre Watch.

((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors

J'utilise Visual Studio 2013

Shehab Fawzy
la source
L'exception est géniale! cela signifie que dans la fenêtre immédiate, je peux faire $ exception.EntityValidationErrors.SelectMany (x => x.ValidationErrors) .Select (x => x.ErrorMessage);
chrispepper1989
13

Pendant que vous êtes en mode débogage dans le catch {...}bloc, ouvrez la fenêtre "QuickWatch" ( ctrl+ alt+ q) et collez-y:

((System.Data.Entity.Validation.DbEntityValidationException)ex).EntityValidationErrors

Cela vous permettra de forer dans l' ValidationErrorsarbre. C'est le moyen le plus simple que j'ai trouvé pour obtenir un aperçu instantané de ces erreurs.

Pour les utilisateurs de Visual 2012+ qui ne se soucient que de la première erreur et qui pourraient ne pas avoir de catchbloc, vous pouvez même faire:

((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors.First().ValidationErrors.First().ErrorMessage
GONeale
la source
9

Pour trouver rapidement un message d'erreur significatif en inspectant l'erreur pendant le débogage:

  • Ajoutez une veille rapide pour:

    ((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors
  • Accédez à EntityValidationErrors comme ceci:

    (élément de collection, par exemple [0])> ValidationErrors> (élément de collection, par exemple [0])> ErrorMessage

Chris Halcrow
la source
5

En fait, ce n'est que le 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. Ainsi, EF vérifiera si la valeur de la propriété est hors limites, comme lorsque vous avez conçu 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. Ainsi, dans le processus de validation, vous devez définir une valeur dans la colonne non nulle, que vous y apportiez ou non la modification. Personnellement, j'aime la réponse de Leniel Macaferi. Il peut vous montrer le détail des problèmes de validation

Calvin
la source
4

Je pense que "les erreurs de validation réelles" peuvent contenir des informations sensibles, et cela pourrait être la raison pour laquelle Microsoft a choisi de les mettre à un autre endroit (propriétés). La solution indiquée ici est pratique, mais elle doit être prise avec prudence.

Je préférerais créer une méthode d'extension. Plus de raisons à cela:

  • Conserver la trace de pile d'origine
  • Suivre le principe ouvert / fermé (par exemple: je peux utiliser différents messages pour différents types de journaux)
  • Dans les environnements de production, il pourrait y avoir d'autres endroits (par exemple: un autre dbcontext) où une exception DbEntityValidationException pourrait être levée.
Luis Toapanta
la source
1

Pour Azure Functions, nous utilisons cette simple extension de 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);
}
Juri
la source
0

Utilisez le bloc try 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 également vérifier les détails ici

  1. http://mattrandle.me/viewing-entityvalidationerrors-in-visual-studio/

  2. La validation a échoué pour une ou plusieurs entités. Voir la propriété 'EntityValidationErrors' pour plus de détails

  3. http://blogs.infosupport.com/improving-dbentityvalidationexception/

Atta H.
la source
Votre troisième lien est une copie du blog de la réponse acceptée, mais sur un site différent. Le deuxième lien est une question de dépassement de pile qui fait déjà référence à votre premier lien.
Eris
Donc, essayer d'aider quelqu'un avec une référence appropriée est un problème ici?
Atta H.
Oui, votre réponse ne doit pas contenir uniquement des liens. Faites un résumé qui répond à la question, puis affichez le lien à la fin pour toute lecture supplémentaire.
ChrisO