DbEntityValidationException - Come posso sapere facilmente cosa ha causato l'errore?


217

Ho un progetto che utilizza Entity Framework. Quando chiamo SaveChangesil mio DbContext, ottengo la seguente eccezione:

System.Data.Entity.Validation.DbEntityValidationException: convalida non riuscita per una o più entità. Vedi la proprietà 'EntityValidationErrors' per maggiori dettagli.

Tutto questo va bene e dandy, ma non voglio collegare un debugger ogni volta che si verifica questa eccezione. Inoltre, negli ambienti di produzione non riesco a collegare facilmente un debugger, quindi devo fare di tutto per riprodurre questi errori.

Come posso vedere i dettagli nascosti all'interno di DbEntityValidationException?

Risposte:


429

La soluzione più semplice è quella di sostituire SaveChangesla classe delle entità. Puoi catturare DbEntityValidationException, scartare gli errori reali e crearne uno nuovo DbEntityValidationExceptioncon il messaggio migliorato.

  1. Crea una classe parziale accanto al tuo file SomethingSomething.Context.cs.
  2. Usa il codice in fondo a questo post.
  3. Questo è tutto. L'implementazione utilizzerà automaticamente i SaveChanges sostituiti senza alcun refactoring.

Il tuo messaggio di eccezione ora apparirà così:

System.Data.Entity.Validation.DbEntityValidationException: convalida non riuscita per una o più entità. Vedi la proprietà 'EntityValidationErrors' per maggiori dettagli. Gli errori di convalida sono: Il campo PhoneNumber deve essere una stringa o un tipo di array con una lunghezza massima di '12'; Il campo LastName è obbligatorio.

Puoi eliminare i SaveChanges sovrascritti in qualsiasi classe che eredita da 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);
        }
    }
}

Il DbEntityValidationExceptioncontiene anche le entità che hanno causato gli errori di validazione. Quindi, se hai bisogno di ulteriori informazioni, puoi modificare il codice sopra per ottenere informazioni su queste entità.

Vedi anche: http://devillers.nl/improving-dbentityvalidationexception/


6
La classe Entities generata eredita già da DbContext, quindi non è necessario aggiungerla nuovamente alla classe parziale. Non romperai o cambierai nulla aggiungendolo alla classe parziale. Infatti, se aggiungi l'eredità da DbContext, Resharper ti suggerirà di rimuoverla: "Il tipo di base 'DbContext' è già specificato in altre parti."
Martin Devillers,

15
Perché non è questo il comportamento predefinito di SaveChanges?
John Shedletsky,

4
"Perché non è questo il comportamento predefinito di SaveChanges?" - Questa è veramente una bella domanda. Questa è stata una soluzione straordinaria, mi ha risparmiato ore! Ho dovuto buttarlo dentrousing System.Linq;
John,

1
My Create View errori su base.SaveChanges () che si trova nel blocco try. Non salta mai nel blocco di cattura. Ho il tuo codice per superare SaveChanges ma non entra mai nel Catch Block in caso di errore.
JustJohn,

7
È necessario impostare l'eccezione interna per preservare la traccia dello stack.
dotjoe,

48

Come ha indicato Martin, ci sono ulteriori informazioni nel DbEntityValidationResult. Ho trovato utile ottenere sia il nome della mia classe POCO che il nome della proprietà in ciascun messaggio e volevo evitare di dover scrivere ErrorMessageattributi personalizzati su tutti i miei [Required]tag proprio per questo.

La seguente modifica al codice di Martin si è occupata di questi dettagli per me:

// 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);
    }
}

1
Usando SelectMany and Aggregatein github da Daring Coders
Kiquenet il

43

Per visualizzare la EntityValidationErrorsraccolta, aggiungere la seguente espressione Watch nella finestra Watch.

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

Sto usando Visual Studio 2013


L'eccezione $ è eccezionale! ciò significa che nella finestra immediata posso fare $ exception.EntityValidationErrors.SelectMany (x => x.ValidationErrors) .Select (x => x.ErrorMessage);
chrispepper1989,

13

Mentre sei in modalità debug all'interno del catch {...}blocco, apri la finestra "QuickWatch" ( ctrl+ alt+ q) e incolla lì:

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

Ciò ti consentirà di eseguire il drill down ValidationErrorssull'albero. È il modo più semplice che ho trovato per ottenere una visione istantanea di questi errori.

Per gli utenti di Visual 2012+ che si preoccupano solo del primo errore e potrebbero non avere un catchblocco, puoi persino fare:

((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors.First().ValidationErrors.First().ErrorMessage

9

Per trovare rapidamente un messaggio di errore significativo esaminando l'errore durante il debug:

  • Aggiungi un orologio rapido per:

    ((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors
  • Esegui il drill down in EntityValidationErrors in questo modo:

    (elemento di raccolta ad es. [0])> ValidationErrors> (elemento di raccolta ad es. [0])> ErrorMessage


5

In realtà, questo è solo il problema di convalida, EF convaliderà le proprietà dell'entità prima di apportare modifiche al database. Pertanto, EF verificherà se il valore della proprietà non è compreso nell'intervallo, ad esempio durante la progettazione della tabella. Table_Column_UserName è varchar (20). Ma, in EF, hai inserito un valore superiore a 20. Oppure, in altri casi, se la colonna non consente di essere Null. Pertanto, nel processo di convalida, è necessario impostare un valore sulla colonna non nulla, indipendentemente dal fatto che si intenda apportare la modifica. Personalmente, mi piace la risposta di Leniel Macaferi. Può mostrarti i dettagli dei problemi di convalida


4

Penso che "Gli errori di convalida effettivi" possano contenere informazioni riservate e questo potrebbe essere il motivo per cui Microsoft ha scelto di metterli in un altro posto (proprietà). La soluzione qui indicata è pratica, ma deve essere presa con cautela.

Preferirei creare un metodo di estensione. Altri motivi per questo:

  • Mantieni traccia stack originale
  • Segui il principio aperto / chiuso (es .: posso usare messaggi diversi per diversi tipi di log)
  • Negli ambienti di produzione potrebbero esserci altri luoghi (es .: altri dbcontext) in cui è possibile generare un DbEntityValidationException.

1

Per Funzioni di Azure usiamo questa semplice estensione per 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);
    }
}

ed esempio di utilizzo:

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);
}

0

Usa try block nel tuo codice come

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;
}

Puoi controllare anche i dettagli qui

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

  2. Convalida non riuscita per una o più entità. Vedi la proprietà 'EntityValidationErrors' per maggiori dettagli

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


Il terzo link è una copia del blog della risposta accettata, ma su un sito diverso. Il secondo collegamento è una domanda di overflow dello stack che fa già riferimento al primo collegamento.
Eris,

Quindi, provare ad aiutare qualcuno con riferimento adeguato è un problema qui?
Atta H.

Sì, la tua risposta non dovrebbe contenere solo link. Fai un riepilogo che risponda alla domanda, quindi pubblica il link alla fine per ogni ulteriore lettura.
ChrisO,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.