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 »уже указан в других частях».
Мартин Девиллерс
15
Почему это не поведение по умолчанию SaveChanges?
Джон Шедлецкий
4
«Почему это не поведение по умолчанию SaveChanges?» - Это действительно хороший вопрос. Это было удивительное решение, оно сэкономило мне часы! Я должен был бросить вusing System.Linq;
Джон Август
1
Мой Create View ошибки на base.SaveChanges (), который находится в блоке try. Он никогда не прыгает в блоке улова. Я получил ваш код для перегрузки 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 по Дерзкие Кодеры
Kiquenet
44

Чтобы просмотреть EntityValidationErrorsколлекцию, добавьте следующее выражение Watch в окно Watch.

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

Я использую Visual Studio 2013

Шехаб Фаузи
источник
Исключение $ блестящее! это означает, что в непосредственном окне я могу сделать $ exception.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
GONeale
источник
9

Чтобы быстро найти значимое сообщение об ошибке, проверив ошибку во время отладки:

  • Добавьте быстрые часы для:

    ((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors
  • Разверните EntityValidationErrors как это:

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

Крис Хэлкроу
источник
5

На самом деле, это проблема проверки, EF сначала проверит свойства объекта, прежде чем вносить какие-либо изменения в базу данных. Таким образом, EF проверит, находится ли значение свойства вне диапазона, как при создании таблицы. Table_Column_UserName - это varchar (20). Но в EF вы ввели значение, превышающее 20. Или, в других случаях, если столбец не позволяет быть пустым. Таким образом, в процессе проверки вы должны установить значение в ненулевой столбец, независимо от того, собираетесь ли вы вносить в него изменения. Мне лично нравится ответ Лениеля Макафери. Он может показать вам детали вопросов проверки

Кальвин
источник
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);
}
Juri
источник
0

Используйте Try Block в вашем коде, как

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/

Атта Х.
источник
Третья ссылка - это копия принятого ответа в блоге, но на другом сайте. Вторая ссылка - это вопрос переполнения стека, который уже ссылается на вашу первую ссылку.
Эрис
Итак, попытка помочь кому-то с правильной ссылкой - это какая-то проблема здесь?
Atta H.
Да, ваш ответ не должен содержать только ссылки. Сделайте резюме, которое отвечает на вопрос, а затем опубликуйте ссылку в конце для дальнейшего чтения.
ChrisO