EF Code First:如何从NuGet包控制台中查看“EntityValidationErrors”属性?

128
我很困惑这个问题:
我已经定义了我的类,用于实体框架(4.1.3)的代码优先方法。一切都很好(我正在创建表等),直到我开始进行种子数据填充。
现在,当我执行...
Add-Migration "remigrate" ; Update-Database;

在控制台包中,出现了错误:“Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.”

我在 Seed() 方法中设置了断点,但由于我在控制台上运行项目时,并非正在运行该项目,因此我不知道如何获取详细信息(PS - 我看过这个帖子Validation failed for one or more entities while saving changes to SQL Server Database using Entity Framework,其中显示了如何查看属性。)

我知道我的 Seed() 方法存在问题,因为如果我在方法调用后立即返回,错误就消失了。那么我该如何设置断点以查看验证错误呢?有点懵逼。或者还有其他方法可以在NuGet控制台中跟踪它吗?


快速更新:我通过系统地跟踪我的方法中的每个变量,直到找到导致错误的原因来解决了我的问题。不过,我仍然想知道我的问题的答案,因为那样会更快! - jeremy
我认为你可以以编程方式运行迁移,然后捕获异常并迭代处理错误。虽然不是理想的解决方案,但可以提供所需的详细信息。 - Pawel
当错误答案位于顶部并获得所有荣誉时,这令人沮丧。这是 StackOverflow 明显不足之处! - jwize
如果您使用 Entity Framework,您可以查看我在“Validation failed for one or more entities. See 'EntityValidationErrors' property for more details”解决方案上的答案。希望这能有所帮助... - Murat Yıldız
6个回答

218

最近我也被这个问题困扰。我通过在Seed方法中的Configuration类中放置一个包装函数并将SaveChanges调用替换为对我的函数的调用来解决它。这个函数只需枚举EntityValidationErrors集合中的错误,并重新抛出一个异常,其中异常消息列出了问题的详细信息。这样可以在NuGet包管理器控制台中显示输出。

以下是代码:

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

只需在种子方法中将对 context.SaveChanges() 的调用替换为 SaveChanges(context) 即可。


1
Richard,终于有人有想法了。我会在尝试后回来回答这个问题。 - jeremy
1
这真的帮助追踪到那些讨厌的东西 :) - Eminem
3
我使用了这种技术,但是在上下文中覆盖了savechanges方法。在上下文中写入public override int SaveChanges() - Kirsten
5
如下所述,使用部分类会更有效。 - jwize
1
如果您在种子方法中执行UserManager操作,则此更改不会将验证错误包含在输出中,您需要根据@jwize的答案覆盖DBContext SaveChanges、SaveChangesAsync和SaveChangesAsync(CT)方法。 - Carl
显示剩余4条评论

119

使用部分类定义扩展DBContext类!

如果您查看DbContext的类定义,它将类似于以下内容:

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

所以,在另一个文件中,您可以创建相同的定义并覆盖您想要的部分。
// partialDatabaseContext.cs  -- you can safely make changes 
// that will not be overwritten in here.
public partial class [DatabaseContextName] : DbContext { // Override defaults here } 

整体想法是使用部分类 -- 你是否注意到 DbContext 是一个部分类 --,从而扩展已生成的类(或将类组织到多个文件中),在我们的情况下,我们还希望从添加到 DbContext 的部分类中覆盖 SaveChanges 方法。
这样,我们可以从任何现有的DbContext/SaveChanges调用中获得错误调试信息,而无需更改种子代码或开发代码。
以下是我的做法(注意:不同之处在于我只在我们自己编写的DbContext部分类中重写了SaveChanges方法,而不是生成的那个)。此外,请确保你的部分类使用正确的命名空间,否则你会感到非常困扰。
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
        }
    }
}

你真是个天才! - Florian F.
4
您还需要重写SaveChangesAsync和SaveChangesAsync(CancellationToken)方法——至少对于Code First来说是这样,不确定Model/DB First是否也适用。 - Carl
@jwize。你的回答帮助了我解决了数据库建模异常处理问题。非常好的答案。 - 3355307
1
@jwize,我不知道DbContext文件可以自动生成。但是如果没有生成,你可以在完整的类中使用try/catch例程。我已经这样做了。 - Michael D. O'Connor
2
当使用CodeFirst时,DbContext显然不会被生成。但是,当您使用设计器时,DbContext和实体类将被生成,并且必须使用部分类进行重写。 - jwize
显示剩余2条评论

36

我将Richard的答案转换为扩展方法:

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

像这样进行调用:

context.SaveChangesWithErrors();

4

我将craigvl的版本转换为C#,但是为了让它对我起作用,我必须添加context.SaveChanges();如下。

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

3

Richard,感谢您帮助我找到正确的解决方案(我也遇到了同样的问题)。下面是一个替代方案,在迁移配置种子方法中不需要包装器就可以正常工作:

 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

然后我能够在包管理器控制台中看到异常。希望这能对某人有所帮助。


-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


1
当您格式化代码时,最好不要将文本放在代码块中。 - jmattheis
这可能是我见过的格式最糟糕的stackoverflow答案了。 - crazy_crank

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接