【问题标题】:DbEntityValidationException - How can I easily tell what caused the error?DbEntityValidationException - 如何轻松判断导致错误的原因?
【发布时间】:2013-03-27 01:49:43
【问题描述】:

我有一个使用实体框架的项目。在我的DbContext 上调用SaveChanges 时,出现以下异常:

System.Data.Entity.Validation.DbEntityValidationException:验证 一个或多个实体失败。请参阅“EntityValidationErrors”属性 了解更多详情。

这一切都很好,但我不想每次发生此异常时都附加调试器。此外,在生产环境中,我无法轻松附加调试器,因此我必须竭尽全力重现这些错误。

如何查看隐藏在DbEntityValidationException 中的详细信息?

【问题讨论】:

    标签: c# entity-framework


    【解决方案1】:

    最简单的解决方案是在您的实体类上覆盖SaveChanges。您可以捕获DbEntityValidationException,解开实际错误并使用改进的消息创建一个新的DbEntityValidationException

    1. 在您的 SomethingSomething.Context.cs 文件旁边创建一个分部类。
    2. 使用本文底部的代码。
    3. 就是这样。您的实现将自动使用覆盖的 SaveChanges,无需任何重构工作。

    您的异常消息现在将如下所示:

    System.Data.Entity.Validation.DbEntityValidationException:验证 一个或多个实体失败。请参阅“EntityValidationErrors”属性 更多细节。验证错误是:PhoneNumber 字段 必须是最大长度为 '12' 的字符串或数组类型;这 姓氏字段是必需的。

    您可以在继承自 DbContext 的任何类中删除覆盖的 SaveChanges:

    public partial class SomethingSomethingEntities
    {
        public override int SaveChanges()
        {
            try
            {
                return base.SaveChanges();
            }
            catch (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);
        
                // Throw a new DbEntityValidationException with the improved exception message.
                throw new DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
            }
        }
    }
    

    DbEntityValidationException 还包含导致验证错误的实体。因此,如果您需要更多信息,可以更改上述代码以输出有关这些实体的信息。

    另请参阅:http://devillers.nl/improving-dbentityvalidationexception/

    【讨论】:

    • 生成的 Entities 类已经继承自 DbContext,因此您不必在部分类上再次添加它。通过将其添加到部分类中,您不会破坏或更改任何内容。事实上,如果你添加从 DbContext 的继承,Resharper 会建议你删除它:“Base type 'DbContext' is already specific in other parts.”
    • 为什么这不是 SaveChanges 的默认行为?
    • "为什么这不是 SaveChanges 的默认行为?" - 这是一个非常好的问题。这是一个了不起的解决方案,它为我节省了几个小时!我不得不抛出using System.Linq;
    • 我在 try 块中的 base.SaveChanges() 上的创建视图错误。它永远不会跳入 catch 块。我让你的代码覆盖了 SaveChanges,但它永远不会在出错时进入 Catch 块。
    • 您应该设置内部异常以保留堆栈跟踪。
    【解决方案2】:

    正如 Martin 所指出的,DbEntityValidationResult 中有更多信息。我发现在每条消息中获取我的 POCO 类名称和属性名称很有用,并且希望避免为此在我的所有 [Required] 标签上编写自定义 ErrorMessage 属性。

    以下对 Martin 代码的调整为我处理了这些细节:

    // Retrieve the error messages as a list of strings.
    List<string> errorMessages = new List<string>();
    foreach (DbEntityValidationResult validationResult in ex.EntityValidationErrors)
    {
        string entityName = validationResult.Entry.Entity.GetType().Name;
        foreach (DbValidationError error in validationResult.ValidationErrors)
        {
            errorMessages.Add(entityName + "." + error.PropertyName + ": " + error.ErrorMessage);
        }
    }
    

    【讨论】:

    • Daring Codersgithub 中使用SelectMany and Aggregate
    【解决方案3】:

    要查看 EntityValidationErrors 集合,请将以下 Watch 表达式添加到 Watch 窗口。

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

    我正在使用 Visual Studio 2013

    【讨论】:

    • $exception 太棒了!这意味着在即时窗口中我可以执行 $exception.EntityValidationErrors.SelectMany(x => x.ValidationErrors).Select(x => x.ErrorMessage);
    【解决方案4】:

    当您在catch {...} 块内处于调试模式时,打开“QuickWatch”窗口(ctrl+alt+q)并粘贴在那里:

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

    这将允许您深入了解ValidationErrors 树。这是我发现即时了解这些错误的最简单方法。

    对于只关心第一个错误并且可能没有 catch 块的 Visual 2012+ 用户,您甚至可以这样做:

    ((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors.First().ValidationErrors.First().ErrorMessage
    

    【讨论】:

      【解决方案5】:

      通过在调试过程中检查错误来快速找到有意义的错误消息:

      • 添加快速观看:

        ((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors
        
      • 像这样深入到 EntityValidationErrors:

        (集合项,例如 [0])> ValidationErrors >(集合项,例如 [0])> ErrorMessage

      【讨论】:

        【解决方案6】:

        实际上,这只是验证问题,EF 会在对数据库进行任何更改之前先验证实体属性。 因此,EF 会检查属性的值是否超出范围,就像您设计表格时一样。 Table_Column_UserName 是 varchar(20)。但是,在 EF 中,您输入的值大于 20。 或者,在其他情况下,如果该列不允许为 Null。 因此,在验证过程中,您必须为非空列设置一个值,无论您是否要对其进行更改。 我个人喜欢 Leniel Macaferi 的回答。它可以向您显示验证问题的详细信息

        【讨论】:

          【解决方案7】:

          我认为“实际验证错误”可能包含敏感信息,这可能是微软选择将它们放在另一个地方(属性)的原因。此处标记的解决方案是实用的,但应谨慎使用。

          我更愿意创建一个扩展方法。更多原因:

          • 保留原始堆栈跟踪
          • 遵循开闭原则(即:我可以为不同类型的日志使用不同的消息)
          • 在生产环境中,可能会在其他地方(即:其他 dbcontext)引发 DbEntityValidationException。

          【讨论】:

            【解决方案8】:

            对于 Azure Functions,我们将这个简单的扩展用于 Microsoft.Extensions.Logging.ILogger

            public static class LoggerExtensions
            {
                public static void Error(this ILogger logger, string message, Exception exception)
                {
                    if (exception is DbEntityValidationException dbException)
                    {
                        message += "\nValidation Errors: ";
                        foreach (var error in dbException.EntityValidationErrors.SelectMany(entity => entity.ValidationErrors))
                        {
                            message += $"\n * Field name: {error.PropertyName}, Error message: {error.ErrorMessage}";
                        }
                    }
            
                    logger.LogError(default(EventId), exception, message);
                }
            }
            

            和示例用法:

            try
            {
                do something with request and EF
            }
            catch (Exception e)
            {
                log.Error($"Failed to create customer due to an exception: {e.Message}", e);
                return await StringResponseUtil.CreateResponse(HttpStatusCode.InternalServerError, e.Message);
            }
            

            【讨论】:

              【解决方案9】:

              在您的代码中使用 try 块,例如

              try
              {
                  // Your code...
                  // Could also be before try if you know the exception occurs in SaveChanges
              
                  context.SaveChanges();
              }
              catch (DbEntityValidationException e)
              {
                  foreach (var eve in e.EntityValidationErrors)
                  {
                      Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
                          eve.Entry.Entity.GetType().Name, eve.Entry.State);
                      foreach (var ve in eve.ValidationErrors)
                      {
                          Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
                              ve.PropertyName, ve.ErrorMessage);
                      }
                  }
                  throw;
              }
              

              您也可以在这里查看详细信息

              1. http://mattrandle.me/viewing-entityvalidationerrors-in-visual-studio/

              2. Validation failed for one or more entities. See 'EntityValidationErrors' property for more details

              3. http://blogs.infosupport.com/improving-dbentityvalidationexception/

              【讨论】:

              • 您的第三个链接是已接受答案博客的副本,但位于不同的站点上。第二个链接是一个堆栈溢出问题,它已经引用了您的第一个链接。
              • 那么,试图通过适当的参考帮助某人有什么问题吗?
              • 是的,您的答案不应该只包含链接。做一个回答问题的摘要,然后在末尾发布链接以供进一步阅读。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-11-07
              • 2011-11-26
              • 2012-01-14
              • 1970-01-01
              • 1970-01-01
              • 2012-06-19
              相关资源
              最近更新 更多