Código EF Primero: ¿Cómo veo la propiedad 'EntityValidationErrors' desde la consola del paquete nuget?

127

Estoy perdido por esto:

He definido mis clases para un primer enfoque de código de marco de entidad (4.1.3). Todo estaba bien (estaba creando las tablas, etc.) hasta que comencé a Seed.

Ahora cuando hago el

Add-Migration "remigrate" ; Update-Database;

Aparece un error en la consola del paquete "Error de validación para una o más entidades. Consulte la propiedad 'EntityValidationErrors' para obtener más detalles".

Tengo un punto de interrupción en mi método Seed (), pero debido a que estoy ejecutando esto en la consola cuando el proyecto no se está ejecutando, no tengo idea de cómo llegar a los detalles (PD: he visto que la validación del hilo falló para una o más entidades al guardar los cambios en la base de datos de SQL Server usando Entity Framework, que muestra cómo puedo ver la propiedad).

Sé que mi método Seed () tiene un problema porque si pongo un retorno justo después de la llamada al método, el error desaparece. Entonces, ¿cómo configuro mi punto de interrupción para poder ver cuál es el error de validación? Un poco perdido. ¿O hay alguna otra forma de rastrearlo en la consola nuget?

Jeremy
fuente
Actualización rápida: resolví mi problema rastreando sistemáticamente cada variable dentro de mi método hasta que descubrí qué estaba causando el error. Sin embargo, todavía me gustaría saber la respuesta a mi pregunta, ya que sería mucho más rápido.
jeremy
Creo que podría ejecutar la migración mediante programación y luego detectar la excepción e iterar sobre los errores. No es ideal, pero podría darle los detalles que necesita.
Pawel
Frustrante cuando la respuesta incorrecta está en la parte superior de las respuestas y obtiene todo el crédito. ¡Un lugar donde StackOverflow claramente se queda corto!
jwize
Si usa Entity Framework , puede echar un vistazo a mi respuesta sobre la Solución para “Error de validación para una o más entidades. Vea la propiedad 'EntityValidationErrors' para más detalles . Espero que esto ayude ...
Murat Yıldız

Respuestas:

216

Esto también me molestó recientemente. Lo arreglé colocando una función de contenedor en la clase de Configuración en el método Seed, y reemplacé llamadas a SaveChangesllamadas a mi función. Esta función simplemente enumeraría los errores dentro de la EntityValidationErrorscolección y volvería a lanzar una excepción donde el mensaje de Excepción enumera los problemas individuales. Esto hace que la salida aparezca en la consola del administrador de paquetes NuGet.

El código sigue:

/// <summary>
/// Wrapper for SaveChanges adding the Validation Messages to the generated exception
/// </summary>
/// <param name="context">The context.</param>
private void SaveChanges(DbContext context) {
    try {
        context.SaveChanges();
    } catch (DbEntityValidationException ex) {
        StringBuilder sb = new StringBuilder();

        foreach (var failure in ex.EntityValidationErrors) {
            sb.AppendFormat("{0} failed validation\n", failure.Entry.Entity.GetType());
            foreach (var error in failure.ValidationErrors) {
                sb.AppendFormat("- {0} : {1}", error.PropertyName, error.ErrorMessage);
                sb.AppendLine();
            }
        }

        throw new DbEntityValidationException(
            "Entity Validation Failed - errors follow:\n" + 
            sb.ToString(), ex
        ); // Add the original exception as the innerException
    }
}

Simplemente reemplace las llamadas context.SaveChanges()con SaveChanges(context)en su método de inicialización.

Ricardo
fuente
Richard, finalmente! Alguien con una idea. Volveré a esta pregunta una vez que lo intente.
jeremy
Esto realmente ayuda a rastrear a los malvados :)
Eminem
3
Utilicé esta técnica pero en su lugar usé una anulación de los cambios guardados dentro del contexto. public override int SaveChanges() dentro del contexto.
Kirsten Greed
55
Es más efectivo usar clases parciales como he respondido a continuación.
jwize
1
Si está realizando operaciones de UserManager en su método de inicialización, entonces este cambio no incluirá los errores de validación en la salida, deberá anular los métodos DBContext SaveChanges, SaveChangesAsync y SaveChangesAsync (CT) según la respuesta @jwize.
Carl
115

¡Extienda su clase DBContext ya con una definición de clase parcial!

Si observa la definición de clase para su DbContext, será algo como lo siguiente:

// DatabaseContext.cs   -- This file is auto generated and thus shouldn't be changed. 
public partial class [DatabaseContextName] : DbContext { ... }

Por lo tanto, en otro archivo puede crear la misma definición y anular las partes que desea.

// partialDatabaseContext.cs  -- you can safely make changes 
// that will not be overwritten in here.
public partial class [DatabaseContextName] : DbContext { // Override defaults here } 

La idea completa con las clases parciales, si notó que DbContext es una clase parcial, es que puede extender una clase que se ha generado (u organizar clases en varios archivos) y en nuestro caso también queremos anular el método SaveChanges desde dentro de una clase parcial que se agrega al DbContext .

De esta manera, podemos obtener información de depuración de errores de todas las llamadas existentes de DbContext / SaveChanges en todas partes y no tener que cambiar su código de Seed o código de desarrollo.

Esto es lo que haría ( NOTA: la diferencia es que simplemente anulo el método SaveChanges en nuestra propia clase parcial DbContext creada , NO LA GENERADA ). Además, asegúrese de que la clase parcial use el espacio de nombres correcto o se golpeará la cabeza contra la pared.

public partial class Database : DbContext
{
    public override int SaveChanges()
    {
        try
        {
            return base.SaveChanges();
        }
        catch (DbEntityValidationException ex)
        {
            var sb = new StringBuilder();

            foreach (var failure in ex.EntityValidationErrors)
            {
                sb.AppendFormat("{0} failed validation\n", failure.Entry.Entity.GetType());
                foreach (var error in failure.ValidationErrors)
                {
                    sb.AppendFormat("- {0} : {1}", error.PropertyName, error.ErrorMessage);
                    sb.AppendLine();
                }
            }

            throw new DbEntityValidationException(
                "Entity Validation Failed - errors follow:\n" +
                sb.ToString(), ex
                ); // Add the original exception as the innerException
        }
    }
}
jwize
fuente
Eres un genio ...!
Florian F.
Gran solución La gente debería leer todas las respuestas antes de votar.
Guilherme de Jesus Santos
3
También debe anular SaveChangesAsync y SaveChangesAsync (CancellationToken) también, al menos ese es el caso con el código primero, no estoy seguro sobre el modelo / db primero.
Carl
@jwize. su respuesta me ayudó en mi base de datos primero a modelar problemas de manejo de excepciones. gran respuesta
3355307
1
Cuando se usa CodeFirst, el DbContext obviamente no se genera. Sin embargo, cuando usa el diseñador, las clases DbContext y Entity se generan y deben anularse usando una clase parcial.
jwize
35

Convertí la respuesta de Richards a un método de extensión:

  public static int SaveChangesWithErrors(this DbContext context)
    {
        try
        {
            return context.SaveChanges();
        }
        catch (DbEntityValidationException ex)
        {
            StringBuilder sb = new StringBuilder();

            foreach (var failure in ex.EntityValidationErrors)
            {
                sb.AppendFormat("{0} failed validation\n", failure.Entry.Entity.GetType());
                foreach (var error in failure.ValidationErrors)
                {
                    sb.AppendFormat("- {0} : {1}", error.PropertyName, error.ErrorMessage);
                    sb.AppendLine();
                }
            }

            throw new DbEntityValidationException(
                "Entity Validation Failed - errors follow:\n" +
                sb.ToString(), ex
            ); // Add the original exception as the innerException
        }
    }

Llama así:

context.SaveChangesWithErrors();
Gordo
fuente
4

Convertí la versión de craigvl a C # Tuve que agregar contexto. SaveChanges (); para que funcione para mí como a continuación.

try
{
    byte[] bytes = System.IO.File.ReadAllBytes(@"C:\Users\sheph_000\Desktop\Rawr.png");
    Console.WriteLine(bytes);

    context.BeverageTypes.AddOrUpdate(
        x => x.Name,
        new AATPos.DAL.Entities.BeverageType { ID = 1, Name = "Sodas" }
        );

    context.Beverages.AddOrUpdate(
        x => x.Name,
        new AATPos.DAL.Entities.Beverage { ID = 1, Name = "Coke", BeverageTypeID = 1, ImageData = bytes, IsStocked = true, StockLevel = 10, Price = 10.00M, ImageMimeType = "test" },
        new AATPos.DAL.Entities.Beverage { ID = 2, Name = "Fanta", BeverageTypeID = 1, ImageData = bytes, IsStocked = true, StockLevel = 10, Price = 10.00M, ImageMimeType = "test" },
        new AATPos.DAL.Entities.Beverage { ID = 3, Name = "Sprite", BeverageTypeID = 1, ImageData = bytes, IsStocked = true, StockLevel = 10, Price = 10.00M, ImageMimeType = "test" },
        new AATPos.DAL.Entities.Beverage { ID = 4, Name = "Cream Soda", BeverageTypeID = 1, ImageData = bytes, IsStocked = true, StockLevel = 10, Price = 10.00M, ImageMimeType = "test" },
        new AATPos.DAL.Entities.Beverage { ID = 5, Name = "Pepsi", BeverageTypeID = 1, ImageData = bytes, IsStocked = true, StockLevel = 10, Price = 10.00M, ImageMimeType = "test" }
        );

    context.SaveChanges();
}
catch (System.Data.Entity.Validation.DbEntityValidationException ex)
{
    var sb = new System.Text.StringBuilder();
    foreach (var failure in ex.EntityValidationErrors)
            {
                sb.AppendFormat("{0} failed validation", failure.Entry.Entity.GetType());
        foreach (var error in failure.ValidationErrors)
                {
            sb.AppendFormat("- {0} : {1}", error.PropertyName, error.ErrorMessage);
            sb.AppendLine();
                }
            }

    throw new Exception(sb.ToString());
}
Ben Pretorius
fuente
3

Richard, gracias por llevarme por el camino correcto (tenía el mismo problema) a continuación, es una alternativa sin el contenedor que me funcionó en el método de inicialización de la configuración de migración:

 Protected Overrides Sub Seed(context As NotificationContext)

        Try
            context.System.AddOrUpdate(
               Function(c) c.SystemName,
                New E_NotificationSystem() With {.SystemName = "System1"},
                New E_NotificationSystem() With {.SystemName = "System2"},
                New E_NotificationSystem() With {.SystemName = "System3"})

            context.SaveChanges()

        Catch ex As DbEntityValidationException

            Dim sb As New StringBuilder

            For Each failure In ex.EntityValidationErrors

                sb.AppendFormat("{0} failed validation" & vbLf, failure.Entry.Entity.[GetType]())

                For Each [error] In failure.ValidationErrors
                    sb.AppendFormat("- {0} : {1}", [error].PropertyName, [error].ErrorMessage)
                    sb.AppendLine()
                Next
            Next

            Throw New Exception(sb.ToString())

        End Try
End Sub

Luego pude ver la excepción en la consola del administrador de paquetes. Espero que esto ayude a alguien.

craigvl
fuente
-1

I Also had same model validation problem but successfully catch by myself after lot of thinking;

I use reverse engineering method to catch the problem out of Over 80 + Model Classes;

1> Made copy of dbcontext, changing the name (I add "1" at end and make respective changes in class constructor and initialization etc.

Old:
 
>public class AppDb : IdentityDbContext<ApplicationUser>
>     
> {
> public AppDb(): base("DefaultConnection", throwIfV1Schema: false)
> {
> 
> }
>     
> public static AppDb Create()
>{
>return new AppDb();
>} 

**New:**

>public class AppDb1 : IdentityDbContext<ApplicationUser>
>{
>public AppDb1()
>: base("DefaultConnection", throwIfV1Schema: false)
>{
>}
> 
>public static AppDb1 Create()
> {
> return new AppDb1();
>  }`

...
2> Make changes to Codefirst Migration Configuration from Old DbContext to my new Context.

> internal sealed class Configuration :
> DbMigrationsConfiguration<DAL.AppDb1> { public Configuration() {
> AutomaticMigrationsEnabled = false; }    protected override void
> Seed(DAL.AppDb1 context) {`

3> Comment the Dbsets in new DbContext which was doubt.
4> Apply update migration if succeeded the probelm lye in Commented section.
5> if not then commented section is clear of bug clear.
6> repeat the (4) until found the right place of bug.
7> Happy Codding

Muhammad Usama
fuente
1
Sería bueno cuando formatea su código que su texto no está dentro de un bloque de código.
jmattheis
Esta es probablemente la peor respuesta de stackoverflow formateada que he visto jamás
crazy_crank