Convalida non riuscita per una o più entità durante il salvataggio delle modifiche al database SQL Server mediante Entity Framework


337

Voglio salvare la mia modifica nel database e sto usando Entity FrameWork Code-First in ASP.NET MVC 3 / C # ma sto riscontrando errori. Nella mia classe Event, ho i tipi di dati DateTime e TimeSpan ma nel mio database ho rispettivamente la data e l'ora. Potrebbe essere questa la ragione? Come posso trasmettere il tipo di dati appropriato nel codice prima di salvare le modifiche al database.

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

Metodo nel controller >>>> Problema su 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);
    }
}

con

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

Database SQL Server 2008 R2 / T-SQL

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

Modulo 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  

Errore del server nell'applicazione '/'

Convalida non riuscita per una o più entità. Vedi la proprietà 'EntityValidationErrors' per maggiori dettagli.

Descrizione: si è verificata un'eccezione non gestita durante l'esecuzione della richiesta Web corrente. Esaminare la traccia dello stack per ulteriori informazioni sull'errore e sulla sua origine nel codice.

Dettagli eccezione: System.Data.Entity.Validation.DbEntityValidationException: convalida non riuscita per una o più entità. Vedi la proprietà 'EntityValidationErrors' per maggiori dettagli.

Errore sorgente:

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

File di origine: C: \ sep \ MvcEventCalendar \ MvcEventCalendar \ Controllers \ EventManagerController.cs Riga: 77

Stack Trace:

[DbEntityValidationException: convalida non riuscita per una o più entità. Vedi la proprietà 'EntityValidationErrors' per maggiori dettagli.]


8
probabilmente uno dei campi obbligatori ha un valore nullo. Come EventDate, StartTime, Price, Category etc
Daveo

Hai ispezionato le variabili di modulo inviate per assicurarti che corrispondano al tipo definito dal database? O come quello che ha detto Daveo, manca uno dei valori del modulo richiesto ...
timothyclifford

Non tutte le variabili di modulo pubblicate corrispondono al tipo definito dal database. Ho usato data e ora nel database ma non esiste un equivalente di tipo di dati diretto in .NET. Pertanto, ho usato DateTime e TimeSpan. Ora devo convertire i due in data e ora rispettivamente.
user522767

La fonte dell'errore per me era un campo stringa che ha più di 30 caratteri e la mia dimensione del campo nel database era 30.
Mojtaba,

Risposte:


840

Puoi estrarre tutte le informazioni da DbEntityValidationExceptioncon il seguente codice (devi aggiungere gli spazi dei nomi: System.Data.Entity.Validatione System.Diagnosticsal tuo usingelenco):

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

7
È inoltre possibile ottenere questo errore se i dati seed non soddisfano completamente le regole degli attributi del modello (come Required). Ho aggiunto una risposta con qualche informazione in più.
dan richardson,

8
La risposta potrebbe essere migliorata spiegando dove è possibile vedere l'output della traccia.
mkataja,

1
Ho trovato questo articolo msdn su trace molto utile
Borik

2
Signore, ho vinto Internet.
Leandro,

8
L'output della traccia può essere visualizzato nella finestra Output. Fai clic su Debug in alto -> Windows -> Output
reggaeguitar

249

Nessuna modifica del codice richiesta:

Mentre sei in modalità debug all'interno del catch {...}blocco apri la finestra "QuickWatch" ( Ctrl+ Alt+ Q) e incolla lì:

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

o:

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

Se non ci si trova in try / catch o non si ha accesso all'oggetto eccezione.

Ciò ti consentirà di eseguire il drill down ValidationErrorssull'albero. È il modo più semplice che ho trovato per ottenere una visione istantanea di questi errori.


6
Amico mio, sei un genio, che mi hai aiutato a rintracciare l'errore ET che stavo ricevendo, grazie!
Mark Kram,

4
Nessun problema :) Ma non un genio, adoro QuickWatch :)
GONeale,

4
Ho appena aggiornato la risposta per coloro che non hanno accesso all'oggetto / alla variabile di eccezione.
GONeale,

7
Ogni volta che ricevo questa eccezione cerco l'errore, quindi vengo qui (secondo risultato su Google), trovo questo post e utilizzo la seconda soluzione nel pannello Debug> Guarda. Ha fatto decine di volte. Grazie GONeale.
Ata S.

1
Sono l'unico che ottiene "Il nome '$ exception' non esiste nel contesto corrente" durante l'esecuzione della seconda query?
Alex Klaus,

37

Nel caso in cui tu abbia classi con gli stessi nomi di proprietà, ecco una piccola estensione alla risposta di 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);
       }
    }
 }

22

Come miglioramento sia per Praveen che per Tony, utilizzo una sostituzione:

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

6

Questa implementazione include l'eccezione dell'entità all'eccezione con il testo dettagliato. Gestisce DbEntityValidationException, DbUpdateException, datetime2errori di intervallo (MS SQL), e comprendono chiave di entità non valida in un messaggio (utile quando savind molte entità in una sola SaveChangeschiamata).

Innanzitutto, sovrascrivi SaveChangesnella 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);
    }
}

È necessario attendere la chiamata SaveChangesAsync, se si desidera gestire l'eccezione.
Arnab Chakraborty il

Grazie, Arnab Chakraborty ! Risposta risolta
Sel

5

Questo codice mi ha aiutato a trovare il mio problema quando ho avuto problemi con il mio Entity VAlidation Erros. Mi ha detto l'esatto problema con la mia definizione di entità. Prova a seguire il codice in cui è necessario coprire storeDB.SaveChanges (); nel seguito prova a prendere il blocco.

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

4

Oggi stavo ricevendo questo errore e non riuscivo a risolverlo per un po ', ma mi sono reso conto che dopo aver aggiunto alcuni RequireAttributes ai miei modelli e che alcuni dati di seed di sviluppo non stavano popolando tutti i campi richiesti.
Quindi, basta notare che se si verifica questo errore durante l'aggiornamento del database tramite una sorta di strategia init come DropCreateDatabaseIfModelChangesallora, è necessario assicurarsi che i dati di seed soddisfino e soddisfino tutti gli attributi di convalida dei dati del modello .

So che questo è leggermente diverso dal problema nella domanda, ma è una domanda popolare, quindi ho pensato di aggiungere un po 'di più alla risposta per gli altri che hanno lo stesso problema.
Spero che questo aiuti gli altri :)


4

Penso che l'aggiunta di try / catch per ogni SaveChanges()operazione non sia una buona pratica, è meglio centralizzare questo:

Aggiungi questa classe alla DbContextclasse principale :

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

Questo sovrascriverà il SaveChanges()metodo del tuo contesto e otterrai un elenco separato da virgole contenente tutti gli errori di convalida dell'entità.

anche questo può essere migliorato, per registrare gli errori nell'ambiente di produzione, anziché semplicemente lanciare un errore.

spero che sia utile


Dove posso trovare la "Classe DbContext"? Sono un po 'nuovo in tutto questo roba MVC. Sto usando MVC 5 ed Entity Framework 6. Solo non so come vorrebbe il nome nel mio Solution Explorer.
JustJohn,

@JustJohn è nel tuo modello di database (file di classe), se non sai dove si trova puoi fare una ricerca completa della parola chiave DbContextrispetto al tuo progetto.
Chtiwi Malek,

1
Mi piace questo approccio al meglio in quanto è la soluzione universale nell'app e rivela facilmente cosa c'è sotto la superficie senza cambiare molto per altri moduli nel sistema
Korayem,

3

Ecco un'estensione per l'estensione di Tony ... :-)

Per Entity Framework 4.x, se si desidera ottenere il nome e il valore del campo chiave in modo da sapere quale istanza dell'entità (record DB) presenta il problema, è possibile aggiungere quanto segue. Ciò fornisce l'accesso ai membri della classe ObjectContext più potenti dall'oggetto 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;

3

Non mi piacciono le eccezioni, ho registrato OnSaveChanges e ho questo

var validationErrors = model.GetValidationErrors();

var h = validationErrors.SelectMany(x => x.ValidationErrors
                                          .Select(f => "Entity: " 
                                                      +(x.Entry.Entity) 
                                                      + " : " + f.PropertyName 
                                                      + "->" + f.ErrorMessage));

Cosa intendi dicendo "Ho registrato gli OnSaveChanges"?
Akira Yamamoto,

Mi sono collegato a OnSaveChanges sul contesto come gli altri, ma non ho raggiunto la fase di eccezione. \
Mickey Perlstein il

2

Questo errore si verifica anche quando si tenta di salvare un'entità con errori di convalida. Un buon modo per causare ciò è dimenticare di controllare ModelState.IsValid prima di salvare sul DB.


2

Assicurati che se hai nvarchar (50) nella riga DB non cerchi di inserire più di 50 caratteri al suo interno. Stupido errore, ma mi ci sono volute 3 ore per capirlo.


1

Ciò potrebbe essere dovuto al numero massimo di caratteri consentito per una colonna specifica, come in sql un campo potrebbe avere il seguente tipo di dati, nvarchar(5)ma il numero di caratteri immessi dall'utente è maggiore di quello specificato, quindi si verifica l'errore.


1

Ho riscontrato lo stesso problema un paio di giorni fa durante l'aggiornamento del database. Nel mio caso, sono state aggiunte alcune nuove colonne non annullabili per manutenzione che non sono state fornite nel codice che causa l'eccezione. Ho capito quei campi e fornito valori per loro e risolto.


0

Grazie per le tue risposte, mi aiuta molto. come codice in Vb.Net, questo codice Bolt per 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

0

può essere causato da Proprietà che non è popolata dal modello .. invece è popolata da Controller .. che può causare questo errore .. la soluzione a ciò è assegnare la proprietà prima di applicare la validazione ModelState. e questo secondo presupposto è. potresti avere già dei dati nel tuo database e provare ad aggiornarli ma ora li stanno recuperando.


0

Nel mio caso ho un nome di colonna della colonna Path il cui tipo di dati ho impostato era varchar (200). Dopo averlo aggiornato su nvarchar (max), ho eliminato la tabella da edmx e poi ho aggiunto nuovamente la tabella e si è svegliata correttamente per me.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.