關於DbUpdateConcurrencyException
Building an MVC 4 App with Database First and Entity Framework
最近遇到的專案,是已經有大量的資料在資料庫裡。對於這種情況,就不適合使用
比較熟悉的code first的方式來開發,所以將採用DB First的方式來進行。當我們用
ADO.NET Entity Data Model,visual studio 將會為我們先建立好相關的類別、模
型。
在這個時候問題來了,採用這種方式的話,我們在code first中,我們會直接把
表單驗證的條件寫在model中,但是db first的方式,我們取得的model是經由範本
產生的,所以當我們資料庫有做了異動,或是我們的model有做更新的話,我們把
驗證的條件寫在自動產生的model中,將會全被清掉。
在自動產生的類別可以看到這幾行的註解。
最近遇到的專案,是已經有大量的資料在資料庫裡。對於這種情況,就不適合使用
比較熟悉的code first的方式來開發,所以將採用DB First的方式來進行。當我們用
ADO.NET Entity Data Model,visual studio 將會為我們先建立好相關的類別、模
型。
在這個時候問題來了,採用這種方式的話,我們在code first中,我們會直接把
表單驗證的條件寫在model中,但是db first的方式,我們取得的model是經由範本
產生的,所以當我們資料庫有做了異動,或是我們的model有做更新的話,我們把
驗證的條件寫在自動產生的model中,將會全被清掉。
在自動產生的類別可以看到這幾行的註解。
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//----------------------------
所以這時候我們將要用到Buddy Class的幫忙。
例如一開始我們建立的model如下 :
namespace MvcLeaning.Models
{
using System;
using System.Collections.Generic;
public partial class Blog
{
public Blog()
{
this.Posts = new HashSet<Post>();
}
public int Id { get; set; }
public string Title { get; set; }
public string BloggerName { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
}
我們再另外建立一個新的類別。這裡就先取一個BuddyClass.cs
如下。
namespace MvcLeaning.Models
{
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;//using這個lib做資料驗證
using System.Web.Mvc;
[MetadataType(typeof(BlogMetaData))]//在這加入這一行,告訴系統說我們要用這個類別來做資料驗證。
public partial class Blog //因這個類別是partial,所以我們直接在這個類別中加入我們要的方法。
{
// [Bind(Exclude = "Name")]//Exclude是用來要排除某個欄位不進行驗證。
public class BlogMetaData
{
[ConcurrencyCheck]//需加上這個,避免寫入錯誤。
public int Id { get; set; }
[Required]
public string Title { get; set; }
[Required]
public string BloggerName { get; set; }
}
}
}
在這個專案中我自已有遇到一個情況,DbUpdateConcurrencyException。解決方法參考以下的文章。
http://www.cnblogs.com/JustRun1983/archive/2012/10/10/2717891.html
作者為justrun
1. Concurrency的作用
场景
有个修改用户的页面功能,我们有一条数据User, ID是1的这个User的年龄是20, 性别是female(数据库中的原始数据)
正确的该User的年龄是25, 性别是male
这个时候A发现User的年龄不对, 就给改成25, 那么在Entity Framework中,我们会这样做。
var user = dbConext.User.Find(1);
//B用户在这里完成修改了User的性别
user.age = 25;
dbContext.SaveChanges();
但是加入在上面注释处,有个B用户发现性别不对,完成了对用户性别的修改,改成male. 会出现什么结果呢。
var user = dbConext.User.Find(1);
当A执行这个代码的时候,获取的性别是female
user.age = 25;
当A执行这个代码的时候, 不知道B已经修改了这个记录的性别,这个时候A的user的性别还是female
dbContext.SaveChanges();
保存修改的时候,会把female覆盖回去,这样B用户的修改就作废了。
但这不是A的本意,A其实只是想修改年龄而已。
Entity Framework使用[ConcurrencyCheck] 来解决这种问题, 当标记为[ConcurrencyCheck] 的Entity属性,如果发现在从数据库中取下来和提交的时候不一致,就会出现DbUpdateConcurrencyException异常,避免错误提交。
2. 如何正确处理DbUpdateConcurrencyException异常
2.1 数据库优先方式
原理是在出现异常的时候,重新加载数据库中的数据,覆盖Context本地数据
using (var context = new BloggingContext())
{
var blog = context.Blogs.Find(1);
blog.Name = "The New ADO.NET Blog";
bool saveFailed;
do
{
saveFailed = false;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// Update the values of the entity that failed to save from the store
ex.Entries.Single().Reload();
}
} while (saveFailed);
}
2.2 客户端优先方式
以Context保存的客户端数据为主,覆盖数据库中的数据
using (var context = new BloggingContext())
{
var blog = context.Blogs.Find(1);
blog.Name = "The New ADO.NET Blog";
bool saveFailed;
do
{
saveFailed = false;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// Update original values from the database
var entry = ex.Entries.Single();
entry.OriginalValues.SetValues(entry.GetDatabaseValues());
}
} while (saveFailed);
}
3.3 综合方式
有时候,不是非A即B的关系,我们希望综合数据库中的数据和context中修改的数据,再保存到数据库中
使用下面的CurrentValues, GetDatabaseValues(), 得到Context数据和数据库数据,重新构建一个正确的Entity,再更新到数据库中
using (var context = new BloggingContext())
{
var blog = context.Blogs.Find(1);
blog.Name = "The New ADO.NET Blog";
bool saveFailed;
do
{
saveFailed = false;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// Get the current entity values and the values in the database
var entry = ex.Entries.Single();
var currentValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
// Choose an initial set of resolved values. In this case we
// make the default be the values currently in the database.
var resolvedValues = databaseValues.Clone();
// Have the user choose what the resolved values should be
HaveUserResolveConcurrency(currentValues, databaseValues, resolvedValues);
// Update the original values with the database values and
// the current values with whatever the user choose.
entry.OriginalValues.SetValues(databaseValues);
entry.CurrentValues.SetValues(resolvedValues);
}
} while (saveFailed);
}
public void HaveUserResolveConcurrency(DbPropertyValues currentValues,
DbPropertyValues databaseValues,
DbPropertyValues resolvedValues)
{
// Show the current, database, and resolved values to the user and have
// them edit the resolved values to get the correct resolution.
}
对上面方法的优化
使用DbPropertyValues总是别扭,使用Enttiy对象就会方便很多,下面就是转换成Entity对象操作的方法
using (var context = new BloggingContext())
{
var blog = context.Blogs.Find(1);
blog.Name = "The New ADO.NET Blog";
bool saveFailed;
do
{
saveFailed = false;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// Get the current entity values and the values in the database
// as instances of the entity type
var entry = ex.Entries.Single();
var databaseValues = entry.GetDatabaseValues();
var databaseValuesAsBlog = (Blog)databaseValues.ToObject();
// Choose an initial set of resolved values. In this case we
// make the default be the values currently in the database.
var resolvedValuesAsBlog = (Blog)databaseValues.ToObject();
// Have the user choose what the resolved values should be
HaveUserResolveConcurrency((Blog)entry.Entity,
databaseValuesAsBlog,
resolvedValuesAsBlog);
// Update the original values with the database values and
// the current values with whatever the user choose.
entry.OriginalValues.SetValues(databaseValues);
entry.CurrentValues.SetValues(resolvedValuesAsBlog);
}
} while (saveFailed);
}
public void HaveUserResolveConcurrency(Blog entity,
Blog databaseValues,
Blog resolvedValues)
{
// Show the current, database, and resolved values to the user and have
// them update the resolved values to get the correct resolution.
}
留言
張貼留言