DbEntityValidationException - Wie kann ich leicht feststellen, was den Fehler verursacht hat?

217

Ich habe ein Projekt, das Entity Framework verwendet. Beim Aufrufen SaveChangesvon my DbContextwird folgende Ausnahme angezeigt:

System.Data.Entity.Validation.DbEntityValidationException: Die Validierung für eine oder mehrere Entitäten ist fehlgeschlagen. Weitere Informationen finden Sie unter der Eigenschaft 'EntityValidationErrors'.

Das ist alles in Ordnung und gut, aber ich möchte nicht jedes Mal, wenn diese Ausnahme auftritt, einen Debugger anhängen. Darüber hinaus kann ich in Produktionsumgebungen einen Debugger nicht einfach anhängen, sodass ich große Anstrengungen unternehmen muss, um diese Fehler zu reproduzieren.

Wie kann ich die darin verborgenen Details sehen DbEntityValidationException?

Martin Devillers
quelle

Antworten:

429

Die einfachste Lösung besteht darin, SaveChangesIhre Entitätsklasse zu überschreiben . Sie können die DbEntityValidationExceptionFehler abfangen , auspacken und DbEntityValidationExceptionmit der verbesserten Nachricht eine neue erstellen .

  1. Erstellen Sie eine Teilklasse neben Ihrer Datei SomethingSomething.Context.cs.
  2. Verwenden Sie den Code am Ende dieses Beitrags.
  3. Das ist es. Ihre Implementierung verwendet die überschriebenen SaveChanges automatisch ohne Refactor-Arbeit.

Ihre Ausnahmemeldung sieht nun folgendermaßen aus:

System.Data.Entity.Validation.DbEntityValidationException: Die Validierung für eine oder mehrere Entitäten ist fehlgeschlagen. Weitere Informationen finden Sie unter der Eigenschaft 'EntityValidationErrors'. Die Validierungsfehler sind: Das Feld PhoneNumber muss ein String- oder Array-Typ mit einer maximalen Länge von '12' sein. Das Feld Nachname ist erforderlich.

Sie können die überschriebenen SaveChanges in jeder Klasse löschen, die von Folgendem erbt 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);
        }
    }
}

Das DbEntityValidationExceptionenthält auch die Entitäten, die die Validierungsfehler verursacht haben. Wenn Sie also noch mehr Informationen benötigen, können Sie den obigen Code ändern, um Informationen zu diesen Entitäten auszugeben.

Siehe auch: http://devillers.nl/improving-dbentityvalidationexception/

Martin Devillers
quelle
6
Die generierte Entities-Klasse erbt bereits von DbContext, sodass Sie sie der Teilklasse nicht erneut hinzufügen müssen. Sie werden nichts beschädigen oder ändern, indem Sie es der Teilklasse hinzufügen. Wenn Sie die Vererbung von DbContext hinzufügen, schlägt Resharper vor, sie zu entfernen: "Der Basistyp 'DbContext' ist bereits in anderen Teilen angegeben."
Martin Devillers
15
Warum ist dies nicht das Standardverhalten von SaveChanges?
John Shedletsky
4
"Warum ist dies nicht das Standardverhalten von SaveChanges?" - Das ist eine wirklich gute Frage. Das war eine erstaunliche Lösung, sie hat mir Stunden gespart! Ich musste einwerfenusing System.Linq;
John August
1
Meine Create View-Fehler auf base.SaveChanges (), die sich im try-Block befinden. Es springt nie in den Fangblock. Ich habe Ihren Code dazu gebracht, SaveChanges zu überschreiben, aber er gelangt bei einem Fehler nie in den Catch Block.
JustJohn
7
Sie sollten die innere Ausnahme festlegen, um die Stapelverfolgung beizubehalten.
Dotjoe
48

Wie Martin angedeutet hat, gibt es weitere Informationen in der DbEntityValidationResult. Ich fand es nützlich, in jeder Nachricht sowohl meinen POCO-Klassennamen als auch meinen Eigenschaftsnamen abzurufen, und wollte vermeiden, dass ErrorMessageich [Required]nur dafür benutzerdefinierte Attribute auf alle meine Tags schreiben muss .

Die folgende Änderung an Martins Code hat sich für mich um diese Details gekümmert:

// 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
quelle
1
Verwendung SelectMany and Aggregatein Github von Daring Coders
Kiquenet
43

Fügen Sie dem EntityValidationErrorsÜberwachungsfenster den folgenden Überwachungsausdruck hinzu, um die Sammlung anzuzeigen .

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

Ich benutze Visual Studio 2013

Shehab Fawzy
quelle
$ Ausnahme ist genial! Das bedeutet, dass ich im unmittelbaren Fenster $ exception.EntityValidationErrors.SelectMany (x => x.ValidationErrors) ausführen kann .Select (x => x.ErrorMessage);
chrispepper1989
13

catch {...}Öffnen Sie im Debug-Modus innerhalb des Blocks das Fenster "QuickWatch" ( ctrl+ alt+ q) und fügen Sie es dort ein:

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

Auf diese Weise können Sie einen Drilldown in den ValidationErrorsBaum durchführen. Dies ist der einfachste Weg, um sofort einen Einblick in diese Fehler zu erhalten.

Für Benutzer von Visual 2012+, die sich nur um den ersten Fehler kümmern und möglicherweise keinen catchBlock haben, können Sie sogar Folgendes tun:

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

So finden Sie schnell eine aussagekräftige Fehlermeldung, indem Sie den Fehler beim Debuggen untersuchen:

  • Fügen Sie eine schnelle Uhr hinzu für:

    ((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors
  • Führen Sie einen Drilldown in EntityValidationErrors wie folgt durch:

    (Sammlungselement zB [0])> ValidationErrors> (Sammlungselement zB [0])> ErrorMessage

Chris Halcrow
quelle
5

Tatsächlich ist dies nur das Validierungsproblem. EF validiert zuerst die Entitätseigenschaften, bevor Änderungen an der Datenbank vorgenommen werden. Daher prüft EF, ob der Wert der Eigenschaft außerhalb des Bereichs liegt, wie beim Entwerfen der Tabelle. Table_Column_UserName ist varchar (20). In EF haben Sie jedoch einen Wert eingegeben, der länger als 20 ist. In anderen Fällen, wenn die Spalte keine Null zulässt. Während des Validierungsprozesses müssen Sie also einen Wert auf die Spalte nicht null setzen, unabhängig davon, ob Sie die Änderung daran vornehmen möchten. Ich persönlich mag die Antwort von Leniel Macaferi. Es kann Ihnen die Details der Validierungsprobleme zeigen

Calvin
quelle
4

Ich denke, "Die tatsächlichen Validierungsfehler" können vertrauliche Informationen enthalten, und dies könnte der Grund sein, warum Microsoft sie an einem anderen Ort (Eigenschaften) abgelegt hat. Die hier gekennzeichnete Lösung ist praktisch, sollte jedoch mit Vorsicht angewendet werden.

Ich würde es vorziehen, eine Erweiterungsmethode zu erstellen. Weitere Gründe dafür:

  • Behalten Sie die ursprüngliche Stapelspur bei
  • Befolgen Sie das Open / Closed-Prinzip (dh: Ich kann verschiedene Nachrichten für verschiedene Arten von Protokollen verwenden).
  • In Produktionsumgebungen kann es andere Stellen geben (z. B. anderen Datenbankkontext), an denen eine DbEntityValidationException ausgelöst werden kann.
Luis Toapanta
quelle
1

Für Azure-Funktionen verwenden wir diese einfache Erweiterung für 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);
    }
}

und Verwendungsbeispiel:

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
quelle
0

Verwenden Sie try block in Ihrem Code wie

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

Sie können die Details auch hier überprüfen

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

  2. Die Validierung für eine oder mehrere Entitäten ist fehlgeschlagen. Weitere Informationen finden Sie unter der Eigenschaft 'EntityValidationErrors'

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

Atta H.
quelle
Ihr dritter Link ist eine Kopie des Blogs der akzeptierten Antwort, jedoch auf einer anderen Website. Der zweite Link ist eine Stapelüberlauffrage, die bereits auf Ihren ersten Link verweist.
Eris
Der Versuch, jemandem mit der richtigen Referenz zu helfen, ist hier also ein Problem?
Atta H.
Ja, Ihre Antwort sollte nicht nur Links enthalten. Machen Sie eine Zusammenfassung, die die Frage beantwortet, und veröffentlichen Sie den Link am Ende für weitere Informationen.
ChrisO