DbEntityValidationException - Jak mogę łatwo stwierdzić, co spowodowało błąd?

217

Mam projekt, który korzysta z Entity Framework. Podczas wywoływania SaveChangesmój DbContextotrzymuję następujący wyjątek:

System.Data.Entity.Validation.DbEntityValidationException: Walidacja nie powiodła się dla jednego lub więcej podmiotów. Aby uzyskać więcej informacji, zobacz właściwość „EntityValidationErrors”.

Wszystko jest w porządku i eleganckie, ale nie chcę dołączać debugera za każdym razem, gdy wystąpi ten wyjątek. Co więcej, w środowiskach produkcyjnych nie mogę łatwo podłączyć debugera, więc muszę bardzo się starać, aby odtworzyć te błędy.

Jak mogę zobaczyć szczegóły ukryte w DbEntityValidationException?

Martin Devillers
źródło

Odpowiedzi:

430

Najłatwiejszym rozwiązaniem jest zastąpienie SaveChangesklasy encji. Możesz złapać DbEntityValidationException, rozpakować rzeczywiste błędy i utworzyć nowy DbEntityValidationExceptionz ulepszonym komunikatem.

  1. Utwórz klasę częściową obok pliku SomethingSomething.Context.cs.
  2. Użyj kodu na dole tego postu.
  3. Otóż ​​to. Twoja implementacja automatycznie użyje zastąpionego SaveChanges bez żadnej pracy refaktoryzacyjnej.

Twoja wiadomość o wyjątku będzie teraz wyglądać następująco:

System.Data.Entity.Validation.DbEntityValidationException: Walidacja nie powiodła się dla jednego lub więcej podmiotów. Aby uzyskać więcej informacji, zobacz właściwość „EntityValidationErrors”. Błędy sprawdzania poprawności to: Pole PhoneNumber musi być ciągiem lub tablicą o maksymalnej długości „12”; Pole LastName jest wymagane.

Możesz upuścić zastąpione SaveChanges w dowolnej klasie, która dziedziczy 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);
        }
    }
}

DbEntityValidationExceptionZawiera również podmioty, które spowodowały błędy sprawdzania poprawności. Jeśli więc potrzebujesz jeszcze więcej informacji, możesz zmienić powyższy kod, aby wyświetlać informacje o tych obiektach.

Zobacz także: http://devillers.nl/improving-dbentityvalidationexception/

Martin Devillers
źródło
6
Wygenerowana klasa Entities już dziedziczy z DbContext, więc nie musisz dodawać jej ponownie do klasy częściowej. Nie zepsujesz ani nie zmienisz niczego, dodając je do klasy częściowej. W rzeczywistości, jeśli dodasz dziedzictwo z DbContext, Resharper zasugeruje, aby je usunąć: „Typ podstawowy„ DbContext ”jest już określony w innych częściach.”
Martin Devillers,
15
Dlaczego nie jest to domyślne zachowanie SaveChanges?
John Shedletsky
4
„Dlaczego nie jest to domyślne zachowanie SaveChanges?” - To naprawdę dobre pytanie. To było niesamowite rozwiązanie, pozwoliło mi zaoszczędzić godziny! Musiałem się wtrącićusing System.Linq;
John August
1
Błędy My Create View błędy w base.SaveChanges (), który znajduje się w bloku try. Nigdy nie wskakuje do bloku zaczepu. Mam twój kod, aby przesłonić SaveChanges, ale nigdy nie dostaje się do Catch Blocka w wyniku błędu.
JustJohn
7
Należy ustawić wewnętrzny wyjątek, aby zachować ślad stosu.
dotjoe 27.04.16
48

Jak wskazał Martin, więcej informacji znajduje się w DbEntityValidationResult. Uznałem, że przydatne jest uzyskanie zarówno nazwy mojej klasy POCO, jak i nazwy właściwości w każdej wiadomości, i chciałem w tym celu uniknąć konieczności pisania niestandardowych ErrorMessageatrybutów na wszystkich moich [Required]tagach.

Poniższe poprawki w kodzie Martina zadbały o te szczegóły:

// 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
źródło
1
Wykorzystanie SelectMany and Aggregatew github przez Daring Coders
Kiquenet
43

Aby wyświetlić EntityValidationErrorskolekcję, dodaj następujące wyrażenie Watch do okna Watch.

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

Używam visual studio 2013

Shehab Fawzy
źródło
Wyjątek $ jest genialny! oznacza to, że w bezpośrednim oknie mogę zrobić $ wyjątek.EntityValidationErrors.SelectMany (x => x.ValidationErrors) .Select (x => x.ErrorMessage);
chrispepper1989,
13

W trybie debugowania w catch {...}bloku otwórz okno „QuickWatch” ( ctrl+ alt+ q) i wklej tam:

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

Pozwoli ci to zejść do ValidationErrorsdrzewa. To najłatwiejszy sposób na uzyskanie natychmiastowego wglądu w te błędy.

Dla użytkowników Visual 2012+, którym zależy tylko na pierwszym błędzie i którzy mogą nie mieć catchbloku, możesz nawet:

((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors.First().ValidationErrors.First().ErrorMessage
GONeale
źródło
9

Aby szybko znaleźć znaczący komunikat o błędzie, sprawdzając błąd podczas debugowania:

  • Dodaj szybki zegarek dla:

    ((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors
  • Przeanalizuj w EntityValidationErrors takie jak to:

    (element kolekcji np. [0])> ValidationErrors> (element kolekcji np. [0])> ErrorMessage

Chris Halcrow
źródło
5

W rzeczywistości jest to tylko kwestia sprawdzania poprawności, EF najpierw zweryfikuje właściwości encji przed wprowadzeniem jakichkolwiek zmian w bazie danych. Tak więc EF sprawdzi, czy wartość właściwości jest poza zakresem, na przykład podczas projektowania tabeli. Table_Column_UserName to varchar (20). Ale w EF wprowadzono wartość dłuższą niż 20. Lub, w innych przypadkach, jeśli kolumna nie pozwala na wartość Null. Tak więc w procesie sprawdzania poprawności musisz ustawić wartość kolumny niepustej, bez względu na to, czy zamierzasz wprowadzić w niej zmiany. Ja osobiście lubię odpowiedź Leniel Macaferi. Może pokazać szczegóły problemów z weryfikacją

Calvin
źródło
4

Myślę, że „rzeczywiste błędy sprawdzania poprawności” mogą zawierać poufne informacje, i może to być powód, dla którego Microsoft zdecydował się umieścić je w innym miejscu (właściwości). Oznaczone tutaj rozwiązanie jest praktyczne, ale należy zachować ostrożność.

Wolałbym stworzyć metodę rozszerzenia. Więcej powodów:

  • Zachowaj oryginalny ślad stosu
  • Przestrzegaj zasady otwartej / zamkniętej (tzn .: mogę używać różnych komunikatów dla różnych rodzajów logów)
  • W środowiskach produkcyjnych mogą istnieć inne miejsca (np .: inny tekst dbcontext), w których można zgłosić wyjątek DbEntityValidationException.
Luis Toapanta
źródło
1

W przypadku funkcji platformy Azure korzystamy z tego prostego rozszerzenia do 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);
    }
}

i przykładowe użycie:

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
źródło
0

Użyj try block w swoim kodzie jak

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

Możesz również sprawdzić szczegóły tutaj

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

  2. Sprawdzanie poprawności nie powiodło się dla jednego lub więcej podmiotów. Aby uzyskać więcej informacji, zobacz właściwość „EntityValidationErrors”

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

Atta H.
źródło
Twój trzeci link jest kopią blogu z zaakceptowaną odpowiedzią, ale na innej stronie. Drugi link to pytanie o przepełnienie stosu, które już odwołuje się do pierwszego linku.
Eris,
Czy próbowanie pomocy komuś z odpowiednimi informacjami jest tutaj problemem?
Atta H.
Tak, twoja odpowiedź nie powinna zawierać tylko linków. Zrób podsumowanie, które odpowiada na pytanie, a następnie opublikuj link na końcu do dalszego czytania.
ChrisO,