关于c#:使用Entity Framework保存对SQL Server数据库的更改时,一个或多个实体的验证失败

Validation failed for one or more entities while saving changes to SQL Server Database using Entity Framework

我想将我的编辑保存到数据库中,并且在ASP.NET MVC 3/C中首先使用实体框架代码,但我遇到了错误。在我的事件类中,我有日期时间和时间跨度数据类型,但在我的数据库中,我分别有日期和时间。这就是原因吗?在将更改保存到数据库之前,如何在代码中强制转换为适当的数据类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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; }
}

方法在storedb.savechanges()中出现问题;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 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);
    }
}

具有

1
2
3
4
5
6
public class EventCalendarEntities : DbContext
{
    public DbSet<Event> Events { get; set; }
    public DbSet<Category> Categories { get; set; }
    public DbSet<Place> Places { get; set; }
}

SQL Server 2008 R2数据库/T-SQL

1
2
3
EventDate (Datatype = date)  
StartTime (Datatype = time)  
EndTime (Datatype = time)

HTTP格式

1
2
3
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

"/"应用程序中的服务器错误。

一个或多个实体的验证失败。有关详细信息,请参阅"EntityValidationErrors"属性。

描述:执行当前Web请求期间发生未处理的异常。请查看堆栈跟踪以获取有关错误及其在代码中的来源的更多信息。

异常详细信息:System.Data.Entity.Validation.DBEntityValidationException:一个或多个实体的验证失败。有关详细信息,请参阅"EntityValidationErrors"属性。

源错误:

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

源文件:C:SEPMVCEventCalendarMVCEventCalendarControllersEventManagerController.cs行:77

堆栈跟踪:

[DBEntityValidationException:一个或多个实体的验证失败。有关详细信息,请参阅"EntityValidationErrors"属性。]


可以使用以下代码从DbEntityValidationException中提取所有信息(需要将命名空间:System.Data.Entity.ValidationSystem.Diagnostics添加到using列表中):

1
2
3
4
5
6
7
8
9
10
11
12
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);
        }
    }
}


无需更改代码:

当您在catch {...}块中处于调试模式时,打开"QuickWatch"窗口(ctrl+alt+q),并粘贴到其中:

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

或:

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

如果您不在Try/Catch中或没有访问异常对象的权限。

这将允许您深入到ValidationErrors树中。这是我发现的最简单的方法,可以立即洞察这些错误。


如果您的类具有相同的属性名,下面是praveen答案的一个小扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 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);
       }
    }
 }

作为对Praveen和Tony的改进,我使用了一个覆盖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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...
        }
    }
}

此实现将实体异常包装为带有详细文本的异常。它处理DbEntityValidationExceptionDbUpdateExceptiondatetime2范围错误(ms-sql),并在消息中包含无效实体的密钥(在一个SaveChanges调用中保存多个实体时很有用)。

首先,在dbContext类中重写SaveChanges

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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);
        }
    }

异常帮助程序类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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);
    }
}


当我的实体验证错误出现问题时,这段代码帮助我找到了问题。它告诉我实体定义的确切问题。在下面的try catch块中,尝试在需要覆盖storedb.savechanges()的地方使用以下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  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;
}


我认为,为每个SaveChanges()操作添加try/catch不是一个好的实践,最好是将其集中起来:

将该类添加到主DbContext类中:

1
2
3
4
5
6
7
8
9
10
11
12
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);
    }
}

这将覆盖上下文的SaveChanges()方法,您将得到一个包含所有实体验证错误的逗号分隔列表。

这也可以改进,在生产环境中记录错误,而不仅仅是抛出错误。

希望这是有帮助的。


我今天收到了这个错误,暂时无法解决,但我意识到这是在我的模型中添加了一些RequireAttribute之后,一些开发种子数据没有填充所有必需的字段。因此,请注意,如果您在通过某种初始化策略(如DropCreateDatabaseIfModelChanges更新数据库时遇到此错误,则必须确保您的种子数据满足并满足任何模型数据验证属性。

我知道这和问题中的问题略有不同,但这是一个很受欢迎的问题,所以我想我会为其他和我有相同问题的人的答案再加一点。希望这能帮助别人:)


这是托尼分机的分机…-)

对于Entity Framework 4.x,如果要获取键字段的名称和值,以便知道哪个实体实例(db record)有问题,可以添加以下内容。这提供了从dbContext对象访问更强大的objectContext类成员的权限。

1
2
3
4
5
// 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;

我不喜欢例外我注册了OnSaveChanges并拥有这个

1
2
3
4
5
6
7
var validationErrors = model.GetValidationErrors();

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


确保如果数据库行中有nvarchar(50),则不会尝试在其中插入超过50个字符。愚蠢的错误,但我花了3个小时才弄明白。


当试图保存具有验证错误的实体时,也会发生此错误。导致这种情况的一个好方法是在保存到数据库之前忘记检查modelstate.isvalid。


这可能是由于特定列所允许的最大字符数,如在SQL中,一个字段可能具有以下数据类型nvarchar(5),但从用户输入的字符数超过了指定的字符数,因此会出现错误。


几天前我在更新数据库时遇到了同样的问题。在我的例子中,为维护而添加的新的不可为空的列很少,而导致异常的代码中没有提供这些列。我计算出这些字段,并为它们提供值,并对其进行解析。


在我的例子中,我有一个表列名路径,我设置的数据类型是varchar(200)。在将它更新为nvarchar(max)之后,我已经从edmx中删除了表,然后再次添加了表,它为我正常工作。


它可能是由模型未填充的属性引起的。而是由控制器填充。可能导致此错误。解决方案是在应用模型状态验证之前分配属性。第二个假设是。数据库中可能已有数据尝试更新它,但现在正在获取它。


为了你的答案,它帮助了我很多。当我在vb.net中编码时,这个bolt代码用于vb.net

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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