La validación falló para una o más entidades al guardar los cambios en la base de datos de SQL Server usando Entity Framework

337

Quiero guardar mi edición en la base de datos y estoy usando Entity FrameWork Code-First en ASP.NET MVC 3 / C # pero obtengo errores. En mi clase de evento, tengo los tipos de datos DateTime y TimeSpan pero en mi base de datos, tengo fecha y hora respectivamente. Podría ser ésta la razón? ¿Cómo puedo transmitir al tipo de datos apropiado en el código antes de guardar los cambios en la base de datos?

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 en el controlador >>>> Problema en 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; } 
}

Base de datos SQL Server 2008 R2 / T-SQL

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

Http Form

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  

Error del servidor en la aplicación '/'.

La validación falló para una o más entidades. Vea la propiedad 'EntityValidationErrors' para más detalles.

Descripción: se produjo una excepción no controlada durante la ejecución de la solicitud web actual. Revise el seguimiento de la pila para obtener más información sobre el error y dónde se originó en el código.

Detalles de la excepción: System.Data.Entity.Validation.DbEntityValidationException: la validación falló para una o más entidades. Vea la propiedad 'EntityValidationErrors' para más detalles.

Error de fuente:

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

Archivo de origen: C: \ sep \ MvcEventCalendar \ MvcEventCalendar \ Controllers \ EventManagerController.cs Línea: 77

Seguimiento de pila:

[DbEntityValidationException: error de validación para una o más entidades. Vea la propiedad 'EntityValidationErrors' para más detalles.]

usuario522767
fuente
8
probablemente uno de sus campos obligatorios tiene un valor nulo. Como EventDate, StartTime, Price, Category, etc.
Daveo
¿Ha inspeccionado las variables de formulario que se publican para garantizar que cada una coincida con el tipo definido de la base de datos? O como lo que dijo Daveo, falta uno de los valores de formulario requeridos ...
timothyclifford
No todas las variables de formulario publicadas coinciden con el tipo definido de la base de datos. He usado fecha y hora en la base de datos, pero no hay ningún tipo de datos directo equivalente en .NET. Por lo tanto, usé DateTime y TimeSpan. Ahora necesito convertir los dos a fecha y hora respectivamente.
user522767
El origen del error para mí fue un campo de cadena que tiene más de 30 caracteres y el tamaño de mi campo en la base de datos fue 30.
Mojtaba

Respuestas:

840

Puede extraer toda la información del DbEntityValidationExceptioncon el siguiente código (debe agregar los espacios de nombres: System.Data.Entity.Validationy System.Diagnosticsa su 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
fuente
77
También puede obtener este error si algún dato inicial no satisface completamente las reglas de atributos del modelo (como Required). He agregado una respuesta con un poco más de información.
dan richardson
8
La respuesta podría mejorarse al explicar dónde puede ver realmente la salida de rastreo.
mkataja
1
Encontré que este artículo de msdn sobre trace es muy útil
Borik,
2
Usted señor, ganó internet.
Leandro
8
La salida de seguimiento se puede ver en la ventana Salida. Haga clic en Depurar en la parte superior -> Windows -> Salida
reggaeguitar
249

No se requiere cambio de código:

Mientras está en modo de depuración dentro del catch {...}bloque, abra la ventana "QuickWatch" ( Ctrl+ Alt+ Q) y pegue allí:

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

o:

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

Si no está en un try / catch o no tiene acceso al objeto de excepción.

Esto le permitirá profundizar en el ValidationErrorsárbol. Es la forma más fácil que he encontrado para obtener información instantánea sobre estos errores.

GONeale
fuente
66
Tú, mi amigo, eres un genio, me ayudaste a rastrear el error ET que recibía, ¡Gracias!
Mark Kram
44
No hay problema :) Pero no es un genio, simplemente ama QuickWatch :)
GONeale
44
Acabo de actualizar la respuesta para aquellos que no tienen acceso al objeto / variable de excepción.
GONeale
77
Cada vez que recibo esta excepción busco el error, luego vengo aquí (segundo resultado en Google), encuentro esta publicación y uso la segunda solución en el panel Debug> Watch. Lo hizo docenas de veces. Gracias GONeale
Ata S.
1
¿Soy el único que obtiene "El nombre '$ excepción' no existe en el contexto actual" al ejecutar la segunda consulta?
Alex Klaus
37

En el caso de que tenga clases con los mismos nombres de propiedades, aquí hay una pequeña extensión de la respuesta 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
fuente
22

Como una mejora tanto para Praveen como para Tony, uso una anulación:

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...
        }
    }
}
Trueno
fuente
6

Esta implementación ajusta la entidad de excepción a excepción con texto de detalle. Maneja DbEntityValidationException, DbUpdateException, datetime2Errores de distancia (MS SQL), e incluyen clave de la entidad válida en el mensaje (útil cuando muchas entidades savind en una SaveChangesllamada).

Primero, anule SaveChangesen la clase 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);
        }
    }

Clase 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
fuente
Debe esperar la llamada SaveChangesAsync, si desea manejar la excepción.
Arnab Chakraborty
Gracias, Arnab Chakraborty ! Respuesta corregida
Sel
5

Este código ayudó a encontrar mi problema cuando tuve un problema con mi Entity VAlidation Erros. Me dijo el problema exacto con mi definición de entidad. Intente seguir el código donde necesita cubrir storeDB.SaveChanges (); en el siguiente intento intente bloquear.

  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;
}
Рахул Маквана
fuente
4

Recibí este error hoy y no pude resolverlo por un tiempo, pero me di cuenta de que fue después de agregar algunos correos RequireAttributeelectrónicos a mis modelos y que algunos datos iniciales de desarrollo no poblaban todos los campos requeridos.
Solo una nota de que si obtiene este error mientras actualiza la base de datos a través de algún tipo de estrategia de inicio, DropCreateDatabaseIfModelChangesentonces debe asegurarse de que sus datos iniciales cumplan y satisfagan cualquier atributo de validación de datos del modelo .

Sé que esto es ligeramente diferente al problema en la pregunta, pero es una pregunta popular, así que pensé en agregar un poco más a la respuesta para otros que tienen el mismo problema que yo.
Espero que esto ayude a otros :)

dan richardson
fuente
4

Creo que agregar try / catch para cada SaveChanges()operación no es una buena práctica, es mejor centralizar esto:

Agregue esta clase a la DbContextclase 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);
    }
}

Esto sobrescribirá el SaveChanges()método de su contexto y obtendrá una lista separada por comas que contiene todos los errores de validación de la entidad.

Esto también puede mejorar, para registrar errores en el entorno de producción, en lugar de simplemente arrojar un error.

Espero que esto sea útil.

Chtiwi Malek
fuente
¿Dónde puedo encontrar la "Clase DbContext"? Soy un poco nuevo en todas estas cosas de MVC. Estoy usando MVC 5 y Entity Framework 6. Simplemente no sé cómo le gustaría el nombre en mi Explorador de soluciones.
JustJohn
@JustJohn está en su modelo de base de datos (archivo de clase), si no sabe dónde está, puede hacer una búsqueda completa de la palabra clave DbContexten su proyecto.
Chtiwi Malek
1
Este enfoque me gusta más, ya que es una solución universal en la aplicación y revela lo que hay debajo de la superficie fácilmente sin cambiar mucho para ningún otro módulo en el sistema
Korayem
3

Aquí hay una extensión de la extensión de Tony ... :-)

Para Entity Framework 4.x, si desea obtener el nombre y el valor del campo clave para saber qué instancia de entidad (registro DB) tiene el problema, puede agregar lo siguiente. Esto proporciona acceso a los miembros de clase ObjectContext más potentes desde su 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
fuente
3

No me gustan las excepciones, registré OnSaveChanges y tengo esto

var validationErrors = model.GetValidationErrors();

var h = validationErrors.SelectMany(x => x.ValidationErrors
                                          .Select(f => "Entity: " 
                                                      +(x.Entry.Entity) 
                                                      + " : " + f.PropertyName 
                                                      + "->" + f.ErrorMessage));
Mickey Perlstein
fuente
¿Qué quiere decir con "Registré los OnSaveChanges"?
Akira Yamamoto
Me conecté a OnSaveChanges en el contexto igual que los demás, simplemente no llegué a la etapa de excepción. \
Mickey Perlstein
2

Este error también ocurre cuando intenta guardar una entidad que tiene errores de validación. Una buena manera de causar esto es olvidarse de comprobar ModelState.IsValid antes de guardar en su base de datos.

Laylarenee
fuente
2

Asegúrese de que si tiene nvarchar (50) en la fila DB no intente insertar más de 50 caracteres en ella. Estúpido error, pero me llevó 3 horas resolverlo.

usuario4030144
fuente
1

Esto puede deberse a la cantidad máxima de caracteres permitidos para una columna específica, como en sql un campo puede tener el siguiente tipo de datos, nvarchar(5)pero la cantidad de caracteres ingresados ​​por el usuario es mayor que la especificada, por lo tanto, surge el error.

lokopi
fuente
1

Me enfrenté al mismo problema hace un par de días al actualizar la base de datos. En mi caso, se agregaron algunas nuevas columnas no anulables para mantenimiento que no se proporcionaron en el código que está causando la excepción. Calculo esos campos y los valores proporcionados para ellos y está resuelto.

Jhabar
fuente
0

Gracias por sus respuestas, me ayuda mucho. como codifico en Vb.Net, este código Bolt para 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
fuente
0

puede ser causado por una propiedad que no está poblada por el modelo ... en cambio, está llena por el controlador ... lo que puede causar este error ... la solución a esto es asignar la propiedad antes de aplicar la validación ModelState. y esta segunda suposición es. Es posible que ya tenga datos en su base de datos e intente actualizarlos, pero ahora los vaya a buscar.

Muhammad Mustafa
fuente
0

En mi caso, tengo una ruta de nombre de columna de tabla cuyo tipo de datos configuré fue varchar (200) .Después de actualizarlo a nvarchar (max), eliminé la tabla de edmx y luego agregué nuevamente la tabla y funcionó correctamente para mí.

Saket Singh
fuente