DbEntityValidationException - Як я можу легко визначити, що спричинило помилку?


217

У мене є проект, який використовує Entity Framework. Під час дзвінка SaveChangesна моєму DbContext, я отримую таке виняток:

System.Data.Entity.Validation.DbEntityValidationException: Не вдалося перевірити для однієї або декількох сутностей. Докладніше див. У властивості EntityValidationErrors.

Це все добре і безглуздо, але я не хочу приєднувати налагоджувач кожного разу, коли трапляється такий виняток. Більше того, у виробничих умовах я не можу легко приєднати налагоджувач, тому мені доводиться робити великі зусилля, щоб відтворити ці помилки.

Як я можу побачити деталі, приховані всередині DbEntityValidationException?

Відповіді:


430

Найпростішим рішенням є перекриття SaveChangesвашого класу сутностей. Ви можете знайти DbEntityValidationException, розкрутити фактичні помилки та створити нове DbEntityValidationExceptionз покращеним повідомленням.

  1. Створіть частковий клас поруч із вашим файлом SomethingSomething.Context.cs.
  2. Скористайтеся кодом внизу цієї публікації.
  3. Це воно. Ваша реалізація автоматично використовуватиме замінені SaveChanges без жодної роботи рефактора.

Тепер ваше повідомлення про виключення виглядатиме так:

System.Data.Entity.Validation.DbEntityValidationException: Не вдалося перевірити для однієї або декількох сутностей. Докладніше див. У властивості EntityValidationErrors. Помилками перевірки є: Поле PhoneNumber має бути типом рядка або масиву з максимальною довжиною '12'; Поле LastName обов'язкове.

Ви можете залишити замінені SaveChanges у будь-якому класі, який успадковує 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також містить об'єкти, які спричинили помилки перевірки. Отже, якщо вам потрібна ще більше інформації, ви можете змінити вищевказаний код, щоб вивести інформацію про ці сутності.

Дивіться також: http://devillers.nl/improving-dbentityvalidationexception/


6
Створений клас Entities вже успадковує DbContext, тому вам не доведеться додавати його знову в частковий клас. Ви нічого не зламаєте і не зміните, додавши його до часткового класу. Насправді, якщо ви додасте спадщину з DbContext, Resharper запропонує вам її видалити: "Тип бази" DbContext "вже вказаний в інших частинах."
Martin Devillers

15
Чому це не поведінка SaveChanges за замовчуванням?
Іоан Шедлецький

4
"Чому це не поведінка SaveChanges за замовчуванням?" - Це справді гарне запитання. Це було дивовижне рішення, це врятувало мені години! Мені довелося кинутиusing System.Linq;
Джон Серпень

1
Мої помилки Create View в базі base.SaveChanges (), яка знаходиться в блоці спробу. Він ніколи не стрибає в блок ловлі. Я отримав ваш код, щоб переїхати SaveChanges, але він ніколи не потрапляє в Catch Block з помилки.
JustJohn

7
Ви повинні встановити внутрішній виняток, щоб зберегти слід стека.
dotjoe

48

Як зазначив Мартін, у програмі "Білорусь" є більше інформації DbEntityValidationResult. Мені здалося корисним отримати ім’я мого класу POCO та ім’я властивості в кожному повідомленні, і хотів уникнути необхідності писати власні ErrorMessageатрибути на всіх моїх [Required]тегах саме для цього.

Наступні зміни до коду Мартіна подбали про ці деталі для мене:

// 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
Використання SelectMany and Aggregateв github від Daring
Coders

43

Щоб переглянути EntityValidationErrorsколекцію, додайте наступний вираз Watch у вікно Watch.

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

Я використовую візуальну студію 2013 року


$ виняток - геніальне! це означає, що в безпосередньому вікні я можу зробити $ исключение.EntityValidationErrors.SelectMany (x => x.ValidationErrors) .Select (x => x.ErrorMessage);
chrispepper1989

13

Поки ви перебуваєте в режимі налагодження в catch {...}блоці, відкрийте вікно "QuickWatch" ( ctrl+ alt+ q) і вставте туди:

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

Це дозволить вам спуститися в ValidationErrorsдерево. Це найпростіший спосіб я зрозуміти ці помилки.

Для користувачів Visual 2012+, які турбуються лише про першу помилку і можуть не мати catchблоку, ви навіть можете:

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

9

Щоб швидко знайти змістовне повідомлення про помилку, перевіривши помилку під час налагодження:

  • Додайте швидкий годинник для:

    ((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors
  • Ознайомтеся з подібними помилками до EntityValidationErrors:

    (елемент колекції, наприклад [0])> ValidationErrors> (елемент колекції, наприклад [0])> ErrorMessage


5

Власне, це лише проблема перевірки, EF спочатку перевірить властивості сутності перед тим, як внести будь-які зміни в базу даних. Таким чином, EF перевірить, чи не відповідає значення властивості, наприклад, коли ви проектували таблицю. Table_Column_UserName - це varchar (20). Але в EF ви ввели значення, яке перевищує 20. Або в інших випадках, якщо стовпець не дозволяє бути Null. Отже, у процесі перевірки ви повинні встановити значення стовпцю, що не є нульовим, незалежно від того, чи збираєтесь ви внести зміни на ньому. Мені особисто подобається відповідь Леніеля Макафері. Він може показати вам деталізацію питань перевірки


4

Я думаю, що "Фактичні помилки перевірки" можуть містити конфіденційну інформацію, і це може бути причиною, чому Microsoft вирішила розмістити їх в іншому місці (властивості). Позначене тут рішення є практичним, але його слід приймати обережно.

Я вважаю за краще створити метод розширення. Більше причин цьому:

  • Зберігайте оригінальний слід стека
  • Дотримуйтесь принципу відкритого / закритого типу (тобто: я можу використовувати різні повідомлення для різних типів журналів)
  • У виробничих середовищах можуть бути інші місця (тобто: інший dbcontext), куди може бути кинуто DbEntityValidationException.

1

Для функцій Azure ми використовуємо це просте розширення до 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);
    }
}

та приклад використання:

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

Використовуйте спробу блоку в коді, як

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

Ви також можете перевірити деталі тут

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

  2. Не вдалося перевірити для однієї чи декількох організацій. Докладніше див. У властивості EntityValidationErrors

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


Ви третє посилання - це копія блогу прийнятої відповіді, але на іншому веб-сайті. Друга посилання - це питання про переповнення стека, яке вже посилається на ваше перше посилання.
Еріс

Отож, спроба допомогти комусь із належним посиланням тут є якесь питання?
Атта Х.

Так, ваша відповідь не повинна містити лише посилань. Складіть резюме, яке відповідає на питання, а потім опублікуйте посилання в кінці для подальшого читання.
ChrisO
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.