DbEntityValidationException - Como posso saber facilmente o que causou o erro?

217

Eu tenho um projeto que usa o Entity Framework. Ao ligar SaveChangesno meu DbContext, recebo a seguinte exceção:

System.Data.Entity.Validation.DbEntityValidationException: falha na validação de uma ou mais entidades. Consulte a propriedade 'EntityValidationErrors' para obter mais detalhes.

Tudo isso é ótimo e elegante, mas não quero anexar um depurador toda vez que essa exceção ocorrer. Além disso, em ambientes de produção, não consigo anexar facilmente um depurador, por isso tenho que me esforçar bastante para reproduzir esses erros.

Como posso ver os detalhes ocultos no DbEntityValidationException?

Martin Devillers
fonte

Respostas:

429

A solução mais fácil é substituir SaveChangesa classe de entidades. Você pode capturar DbEntityValidationException, desembrulhar os erros reais e criar um novo DbEntityValidationExceptioncom a mensagem aprimorada.

  1. Crie uma classe parcial ao lado do seu arquivo SomethingSomething.Context.cs.
  2. Use o código na parte inferior desta postagem.
  3. É isso aí. Sua implementação usará automaticamente as SaveChanges substituídas sem nenhum trabalho de refatoração.

Sua mensagem de exceção será agora assim:

System.Data.Entity.Validation.DbEntityValidationException: falha na validação de uma ou mais entidades. Consulte a propriedade 'EntityValidationErrors' para obter mais detalhes. Os erros de validação são: O campo PhoneNumber deve ser um tipo de sequência ou matriz com um comprimento máximo de '12'; O campo Sobrenome é obrigatório.

Você pode descartar o SaveChanges substituído em qualquer classe que herda de 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);
        }
    }
}

O DbEntityValidationExceptiontambém contém as entidades que causaram os erros de validação. Portanto, se você precisar de mais informações, poderá alterar o código acima para gerar informações sobre essas entidades.

Consulte também: http://devillers.nl/improving-dbentityvalidationexception/

Martin Devillers
fonte
6
A classe Entities gerada já herda de DbContext, para que você não precise adicioná-lo novamente na classe parcial. Você não quebrará ou alterará nada adicionando-o à classe parcial. De fato, se você adicionar a herança do DbContext, o Resharper sugerirá que você a remova: "O tipo base 'DbContext' já está especificado em outras partes".
Martin Devillers
15
Por que esse não é o comportamento padrão do SaveChanges?
John Shedletsky
4
"Por que esse não é o comportamento padrão do SaveChanges?" - Essa é uma pergunta muito boa. Esta foi uma solução incrível, que me salvou horas! Eu tive que jogar dentrousing System.Linq;
John August
1
Erros My Create View em base.SaveChanges () que está no bloco try. Ele nunca pula no bloco de captura. Recebi seu código para substituir o SaveChanges, mas ele nunca entra no Catch Block por erro.
JustJohn
7
Você deve definir a exceção interna para preservar o rastreamento da pilha.
dotjoe
48

Como Martin indicou, há mais informações no DbEntityValidationResult. Achei útil obter o nome da classe POCO e o nome da propriedade em cada mensagem e queria evitar a necessidade de escrever ErrorMessageatributos personalizados em todas as minhas [Required]tags apenas para isso.

O seguinte ajuste no código de Martin cuidou desses detalhes para mim:

// 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
fonte
1
Usando SelectMany and Aggregateno github por Daring Coders
Kiquenet 29/03
43

Para visualizar a EntityValidationErrorscoleção, adicione a seguinte expressão Watch à janela Watch.

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

Eu estou usando o visual studio 2013

Shehab Fawzy
fonte
$ exceção é brilhante! isso significa que na janela imediata eu posso fazer $ exception.EntityValidationErrors.SelectMany (x => x.ValidationErrors) .Select (x => x.ErrorMessage);
precisa
13

Enquanto você estiver no modo de depuração dentro do catch {...}bloco, abra a janela "QuickWatch" ( ctrl+ alt+ q) e cole nela:

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

Isso permitirá que você faça uma busca detalhada no ValidationErrors árvore. É a maneira mais fácil que eu encontrei de obter informações instantâneas sobre esses erros.

Para usuários do Visual 2012+ que se preocupam apenas com o primeiro erro e podem não ter um catchbloqueio, você pode até fazer:

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

Para encontrar rapidamente uma mensagem de erro significativa, inspecionando o erro durante a depuração:

  • Adicione uma observação rápida para:

    ((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors
  • Faça uma busca detalhada em EntityValidationErrors assim:

    (item de coleção, por exemplo, [0])> ValidationErrors> (item de coleção, por exemplo, [0])> ErrorMessage

Chris Halcrow
fonte
5

Na verdade, esse é apenas um problema de validação, a EF validará as propriedades da entidade primeiro antes de fazer alterações no banco de dados. Portanto, a EF verificará se o valor da propriedade está fora do intervalo, como quando você projetou a tabela. Table_Column_UserName é varchar (20). Mas, no EF, você inseriu um valor maior que 20. Ou, em outros casos, se a coluna não permitir que seja Nulo. Portanto, no processo de validação, você deve definir um valor para a coluna não nula, independentemente de fazer a alteração. Pessoalmente, gosto da resposta de Leniel Macaferi. Pode mostrar os detalhes dos problemas de validação

Calvin
fonte
4

Penso que "os erros de validação reais" podem conter informações confidenciais e esse pode ser o motivo pelo qual a Microsoft optou por colocá-los em outro local (propriedades). A solução marcada aqui é prática, mas deve ser tomada com cautela.

Eu preferiria criar um método de extensão. Mais razões para isso:

  • Manter rastreamento da pilha original
  • Siga o princípio aberto / fechado (ou seja: eu posso usar mensagens diferentes para diferentes tipos de logs)
  • Nos ambientes de produção, pode haver outros locais (por exemplo, outro dbcontext) onde uma DbEntityValidationException pode ser lançada.
Luis Toapanta
fonte
1

Para o Azure Functions, usamos esta extensão simples para 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);
    }
}

e exemplo de uso:

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

Use o bloco try no seu código como

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

Você pode verificar os detalhes aqui também

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

  2. Falha na validação para uma ou mais entidades. Consulte a propriedade 'EntityValidationErrors' para obter mais detalhes

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

Atta H.
fonte
Seu terceiro link é uma cópia do blog da resposta aceita, mas em um site diferente. O segundo link é uma pergunta de estouro de pilha que já faz referência ao seu primeiro link.
Eris
Então, tentar ajudar alguém com a referência adequada, há algum problema aqui?
Atta H.
Sim, sua resposta não deve conter apenas links. Faça um resumo que responda à pergunta e publique o link no final para mais leituras.
Chriso