Falha na validação de uma ou mais entidades ao salvar alterações no Banco de Dados SQL Server usando o Entity Framework

337

Quero salvar minha Edição no banco de dados e estou usando o Entity FrameWork Code-First no ASP.NET MVC 3 / C #, mas estou recebendo erros. Na minha classe Event, eu tenho tipos de dados DateTime e TimeSpan, mas no meu banco de dados, tenho Date e hora, respectivamente. Poderia ser este o motivo? Como posso transmitir para o tipo de dados apropriado no código antes de salvar as alterações no banco de dados.

public class Event
{
    public int EventId { get; set; }
    public int CategoryId { get; set; }
    public int PlaceId { get; set; }
    public string Title { get; set; }
    public decimal Price { get; set; }
    public DateTime EventDate { get; set; }
    public TimeSpan StartTime { get; set; }
    public TimeSpan EndTime { get; set; }
    public string Description { get; set; }
    public string EventPlaceUrl { get; set; }
    public Category Category { get; set; }
    public Place Place { get; set; }
}

Método no controlador >>>> Problema em storeDB.SaveChanges ();

// POST: /EventManager/Edit/386        
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
    var theEvent = storeDB.Events.Find(id);

    if (TryUpdateModel(theEvent))
    {
        storeDB.SaveChanges();
        return RedirectToAction("Index");
    }
    else
    {
        ViewBag.Categories = storeDB.Categories.OrderBy(g => g.Name).ToList();
        ViewBag.Places = storeDB.Places.OrderBy(a => a.Name).ToList();
        return View(theEvent);
    }
}

com

public class EventCalendarEntities : DbContext
{
    public DbSet<Event> Events { get; set; }
    public DbSet<Category> Categories { get; set; }
    public DbSet<Place> Places { get; set; } 
}

Banco de Dados SQL Server 2008 R2 / T-SQL

EventDate (Datatype = date)  
StartTime (Datatype = time)  
EndTime (Datatype = time)  

Formulário HTTP

EventDate (Datatype = DateTime) e.g. 4/8/2011 12:00:00 AM  
StartTime (Datatype = Timespan/time not sure) e.g. 08:30:00  
EndTime (Datatype = Timespan/time not sure) e.g. 09:00:00  

Erro de Servidor na '/' Aplicação.

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

Descrição: ocorreu uma exceção não tratada durante a execução da solicitação da web atual. Revise o rastreamento de pilha para obter mais informações sobre o erro e onde ele se originou no código.

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

Erro de origem:

Line 75:             if (TryUpdateModel(theEvent))
Line 76:             {
Line 77:                 storeDB.SaveChanges();
Line 78:                 return RedirectToAction("Index");
Line 79:             }

Arquivo de origem: C: \ sep \ MvcEventCalendar \ MvcEventCalendar \ Controllers \ EventManagerController.cs Linha: 77

Rastreio de pilha:

[DbEntityValidationException: falha na validação para uma ou mais entidades. Consulte a propriedade 'EntityValidationErrors' para obter mais detalhes.]

user522767
fonte
8
provavelmente um dos seus campos obrigatórios tem um valor nulo. Como EventDate,
Hora de Início
Você examinou as variáveis ​​de formulário sendo postadas para garantir que cada uma corresponda ao tipo definido pelo banco de dados? Ou como o que Daveo disse, um dos valores do formulário necessário está faltando ...
timothyclifford
Nem todas as variáveis ​​de formulário postadas correspondem ao tipo definido pelo banco de dados. Eu usei data e hora no banco de dados, mas não há tipo de dados direto equivalente no .NET. Portanto, usei DateTime e TimeSpan. Agora eu preciso converter os dois em data e hora, respectivamente.
user522767
A fonte de erro para mim era um campo de cadeia que tem mais de 30 caracteres e meu tamanho do campo no banco de dados foi 30.
Mojtaba

Respostas:

840

Você pode extrair todas as informações do DbEntityValidationExceptioncom o seguinte código (você precisa adicionar os espaços para nome: System.Data.Entity.Validatione System.Diagnosticssua usinglista):

catch (DbEntityValidationException dbEx)
{
    foreach (var validationErrors in dbEx.EntityValidationErrors)
    {
        foreach (var validationError in validationErrors.ValidationErrors)
        {
            Trace.TraceInformation("Property: {0} Error: {1}", 
                                    validationError.PropertyName, 
                                    validationError.ErrorMessage);
        }
    }
}
Praveen Prasad
fonte
7
Você também pode obter esse erro se algum dado inicial não estiver satisfazendo totalmente as regras de atributo do modelo (como Required). Adicionei uma resposta com um pouco mais de informação.
Dan Richardson
8
A resposta pode ser melhorada explicando onde você pode realmente ver a saída do rastreio.
Mkataja
11
Eu encontrei este artigo MSDN sobre traço sendo muito útil
Borik
2
Você ganhou a internet.
Leandro
8
A saída de rastreamento pode ser vista na janela Saída. Clique em Debug na parte superior -> Windows -> Saída
reggaeguitar
249

Nenhuma alteração de código é necessária:

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

ou:

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

Se você não estiver em uma tentativa / captura ou não tiver acesso ao objeto de exceção.

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

GONeale
fonte
6
Você, meu amigo, é um gênio, por ter me ajudado a localizar o erro de ET que estava recebendo, obrigado!
Mark Kram
4
Não há problema :) Mas não um gênio, adoro QuickWatch :)
GONeale
4
Apenas atualizei a resposta para aqueles que não têm acesso ao objeto / variável de exceção.
GONeale
7
Toda vez que recebo essa exceção, procuro o erro, então venho para cá (segundo resultado no Google), encontro esta postagem e uso a segunda solução no painel Debug> Watch. Fez dezenas de vezes. Obrigado GONeale.
Ata S.
11
Eu sou o único que obtém "O nome '$ exception' não existe no contexto atual" ao executar a segunda consulta?
Alex Klaus #
37

No caso de você ter aulas com os mesmos nomes de propriedades, aqui está uma pequena extensão da resposta de Praveen:

 catch (DbEntityValidationException dbEx)
 {
    foreach (var validationErrors in dbEx.EntityValidationErrors)
    {
       foreach (var validationError in validationErrors.ValidationErrors)
       {
          Trace.TraceInformation(
                "Class: {0}, Property: {1}, Error: {2}",
                validationErrors.Entry.Entity.GetType().FullName,
                validationError.PropertyName,
                validationError.ErrorMessage);
       }
    }
 }
Tony
fonte
22

Como uma melhoria para Praveen e Tony, eu uso uma substituição:

public partial class MyDatabaseEntities : DbContext
{
    public override int SaveChanges()
    {
        try
        {
            return base.SaveChanges();
        }
        catch (DbEntityValidationException dbEx)
        {
            foreach (var validationErrors in dbEx.EntityValidationErrors)
            {
                foreach (var validationError in validationErrors.ValidationErrors)
                {
                    Trace.TraceInformation("Class: {0}, Property: {1}, Error: {2}",
                        validationErrors.Entry.Entity.GetType().FullName,
                        validationError.PropertyName,
                        validationError.ErrorMessage);
                }
            }

            throw;  // You can also choose to handle the exception here...
        }
    }
}
Bolt Thunder
fonte
6

Essa implementação quebra a exceção de entidade a exceção com texto detalhado. Ele lida DbEntityValidationException, DbUpdateException, datetime2erros alcance (MS SQL), e incluem chave de entidade inválido na mensagem (útil quando savind muitas entidades em umSaveChanges chamada).

Primeiro, substitua SaveChangesna classe DbContext:

public class AppDbContext : DbContext
{
    public override int SaveChanges()
    {
        try
        {
            return base.SaveChanges();
        }
        catch (DbEntityValidationException dbEntityValidationException)
        {
            throw ExceptionHelper.CreateFromEntityValidation(dbEntityValidationException);
        }
        catch (DbUpdateException dbUpdateException)
        {
            throw ExceptionHelper.CreateFromDbUpdateException(dbUpdateException);
        }
    }   

    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken)
    {
        try
        {
            return await base.SaveChangesAsync(cancellationToken);
        }
        catch (DbEntityValidationException dbEntityValidationException)
        {
            throw ExceptionHelper.CreateFromEntityValidation(dbEntityValidationException);
        }
        catch (DbUpdateException dbUpdateException)
        {
            throw ExceptionHelper.CreateFromDbUpdateException(dbUpdateException);
        }
    }

Classe ExceptionHelper:

public class ExceptionHelper
{
    public static Exception CreateFromEntityValidation(DbEntityValidationException ex)
    {
        return new Exception(GetDbEntityValidationMessage(ex), ex);
    }

    public static string GetDbEntityValidationMessage(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);
        return exceptionMessage;
    }

    public static IEnumerable<Exception> GetInners(Exception ex)
    {
        for (Exception e = ex; e != null; e = e.InnerException)
            yield return e;
    }

    public static Exception CreateFromDbUpdateException(DbUpdateException dbUpdateException)
    {
        var inner = GetInners(dbUpdateException).Last();
        string message = "";
        int i = 1;
        foreach (var entry in dbUpdateException.Entries)
        {
            var entry1 = entry;
            var obj = entry1.CurrentValues.ToObject();
            var type = obj.GetType();
            var propertyNames = entry1.CurrentValues.PropertyNames.Where(x => inner.Message.Contains(x)).ToList();
            // check MS SQL datetime2 error
            if (inner.Message.Contains("datetime2"))
            {
                var propertyNames2 = from x in type.GetProperties()
                                        where x.PropertyType == typeof(DateTime) ||
                                            x.PropertyType == typeof(DateTime?)
                                        select x.Name;
                propertyNames.AddRange(propertyNames2);
            }

            message += "Entry " + i++ + " " + type.Name + ": " + string.Join("; ", propertyNames.Select(x =>
                string.Format("'{0}' = '{1}'", x, entry1.CurrentValues[x])));
        }
        return new Exception(message, dbUpdateException);
    }
}
Sel
fonte
Você deve aguardar a chamada SaveChangesAsync, se desejar lidar com a exceção.
Arnab Chakraborty
Obrigado, Arnab Chakraborty ! Resposta fixada
Sel
5

Esse código ajudou a encontrar meu problema quando tive um problema com meus erros de validação de entidade. Ele me disse o problema exato com minha definição de entidade. Tente seguir o código onde você precisa cobrir storeDB.SaveChanges (); na seguinte tentativa, pegue o bloco.

  try
{
         if (TryUpdateModel(theEvent))
         {
             storeDB.SaveChanges();
             return RedirectToAction("Index");
         }
}
catch (System.Data.Entity.Validation.DbEntityValidationException dbEx)
{
    Exception raise = dbEx;
    foreach (var validationErrors in dbEx.EntityValidationErrors)
    {
        foreach (var validationError in validationErrors.ValidationErrors)
        {
            string message = string.Format("{0}:{1}", 
                validationErrors.Entry.Entity.ToString(),
                validationError.ErrorMessage);
            // raise a new exception nesting
            // the current instance as InnerException
            raise = new InvalidOperationException(message, raise);
        }
    }
    throw raise;
}
Рахул Маквана
fonte
4

Eu estava recebendo esse erro hoje e não consegui resolvê-lo por um tempo, mas percebi que foi depois de adicionar alguns RequireAttributes aos meus modelos e que alguns dados de sementes de desenvolvimento não estavam preenchendo todos os campos obrigatórios.
Portanto, basta observar que, se você está recebendo esse erro ao atualizar o banco de dados através de algum tipo de estratégia init, DropCreateDatabaseIfModelChangesdeve certificar-se de que seus dados iniciais cumpram e satisfaçam todos os atributos de validação de dados do modelo .

Sei que isso é um pouco diferente do problema da pergunta, mas é uma pergunta popular, então pensei em acrescentar um pouco mais à resposta de outras pessoas com o mesmo problema que eu.
Espero que isso ajude os outros :)

dan richardson
fonte
4

Eu acho que adicionar try / catch para cada SaveChanges()operação não é uma boa prática, é melhor centralizar isso:

Adicione esta classe à DbContextclasse principal :

public override int SaveChanges()
{
    try
    {
        return base.SaveChanges();
    }
    catch (DbEntityValidationException ex)
    {
        string errorMessages = string.Join("; ", ex.EntityValidationErrors.SelectMany(x => x.ValidationErrors).Select(x => x.ErrorMessage));
        throw new DbEntityValidationException(errorMessages);
    }
}

Isso substituirá o SaveChanges()método do seu contexto e você obterá uma lista separada por vírgula contendo todos os erros de validação da entidade.

isso também pode ser aprimorado, para registrar erros no ambiente de produção, em vez de apenas gerar um erro.

Espero que isso seja útil.

Chtiwi Malek
fonte
Onde posso encontrar a "Classe DbContext"? Eu sou meio novo em todas essas coisas do MVC. Estou usando o MVC 5 e o Entity Framework 6. Só não sei como o nome gostaria no meu Gerenciador de Soluções.
JustJohn
@JustJohn está no seu modelo de banco de dados (arquivo de classe), se você não souber onde está, poderá fazer uma pesquisa completa da palavra-chave DbContextno seu projeto.
Chtiwi Malek
11
I como este se aproxima o melhor, pois é uma solução universal no aplicativo e revela o que está abaixo da superfície facilmente sem mudar muito para quaisquer outros módulos no sistema
Korayem
3

Aqui está uma extensão da extensão do Tony ... :-)

Para o Entity Framework 4.x, se você deseja obter o nome e o valor do campo-chave para saber qual instância da entidade (registro do banco de dados) tem o problema, você pode adicionar o seguinte. Isso fornece acesso aos membros da classe ObjectContext mais poderosos do seu objeto DbContext.

// Get the key field name & value.
// This assumes your DbContext object is "_context", and that it is a single part key.
var e = ((IObjectContextAdapter)_context).ObjectContext.ObjectStateManager.GetObjectStateEntry(validationErrors.Entry.Entity);
string key = e.EntityKey.EntityKeyValues[0].Key;
string val = e.EntityKey.EntityKeyValues[0].Value;
Martin_W
fonte
3

Não gosto de exceções, registrei o OnSaveChanges e tenho este

var validationErrors = model.GetValidationErrors();

var h = validationErrors.SelectMany(x => x.ValidationErrors
                                          .Select(f => "Entity: " 
                                                      +(x.Entry.Entity) 
                                                      + " : " + f.PropertyName 
                                                      + "->" + f.ErrorMessage));
Mickey Perlstein
fonte
O que você quer dizer com "Registrei as OnSaveChanges"?
Akira Yamamoto
Liguei-me ao OnSaveChanges no contexto, assim como os outros, apenas não cheguei ao estágio de exceção. \
Mickey Perlstein
2

Este erro também ocorre quando você tenta salvar uma entidade que possui erros de validação. Uma boa maneira de fazer isso é esquecer de verificar ModelState.IsValid antes de salvar no seu banco de dados.

laylarenee
fonte
2

Certifique-se de que, se você tiver nvarchar (50) na linha do banco de dados, não tente inserir mais de 50 caracteres nela. Erro estúpido, mas levei 3 horas para descobrir.

user4030144
fonte
1

Isso pode ser devido ao número máximo de caracteres permitido para uma coluna específica, como no sql, um campo pode ter o seguinte Tipo de Dados, nvarchar(5)mas o número de caracteres digitados pelo usuário é maior que o especificado, daí o erro ocorrer.

lokopi
fonte
1

Eu enfrentei o mesmo problema alguns dias atrás ao atualizar o banco de dados. No meu caso, foram adicionadas poucas novas colunas não anuláveis ​​para manutenção que não foram fornecidas no código que está causando a exceção. Descobri esses campos e forneci valores para eles e seus resolvidos.

Jhabar
fonte
0

Thnaks por suas respostas, isso me ajuda muito. como eu codifico no Vb.Net, esse código de Bolt para o Vb.Net

Try
   Return MyBase.SaveChanges()
Catch dbEx As Validation.DbEntityValidationException
   For Each [error] In From validationErrors In dbEx.EntityValidationErrors
                       From validationError In validationErrors.ValidationErrors
                       Select New With { .PropertyName = validationError.PropertyName,
                                         .ErrorMessage = validationError.ErrorMessage,
                                         .ClassFullName = validationErrors.Entry.Entity
                                                                    .GetType().FullName}

        Diagnostics.Trace.TraceInformation("Class: {0}, Property: {1}, Error: {2}",
                                           [error].ClassFullName,
                                           [error].PropertyName,
                                           [error].ErrorMessage)
   Next
   Throw
End Try
Exatex
fonte
0

pode ser causado por Propriedade que não é preenchida pelo modelo .. em vez disso, é preenchida pelo Controller .. o que pode causar esse erro. A solução é atribuir a propriedade antes de aplicar a validação do ModelState. e esta segunda suposição é. você já pode ter dados no seu banco de dados e tentar atualizá-los, mas agora buscá-los.

Muhammad Mustafa
fonte
0

No meu caso, eu tenho um nome da coluna da tabela Caminho, que defini como varchar (200). Após atualizá-lo para nvarchar (max), excluí a tabela do edmx e, em seguida, adicionei novamente a tabela e ela funcionou corretamente para mim.

Saket Singh
fonte