前面的Part 1-4的文章,介绍了Entity Data Model、Entity
SQL、ObjectQuery、EntityCommand、LINQ to
Entities等等及其代码演示。Part 4主要演示如何通过相关技术或Debug工具,如SQL
Server Profiler、ToTraceString 方法、eSqlBlast
工具、LINQPad工具等等,来查看生成的T-SQL脚本。Part 5
演示如何新增、更新和删除数据实体,并相应更新数据库。本篇文章Part 6
演示如何处理并发更新。
ADO.NET Entity Framework 系列文章由EntLib.com
开源论坛、小组翻译、编写。欢迎交流、分享。
ADO.NET
Entity Framework 深入分析, Part 5
Entity Framework 实现了乐观的并发模式(Optimistic
Concurrency Model)。默认情况下,在实体更新数据提交到数据库时,并不会检查并发。对于高频率的并发属性,你需要设置属性的并发模式为Fixed。
这些属性将会加入到T-SQL脚本的WHERE子句部分,用来比较客户端的值和数据库端的值。
示例代码:
public
void
UpdateProduct()
{
Product
product = context.Product.FirstOrDefault(p
=> p.ProductID == 1004);
if (product
!= null)
{
product.Color =
"White";
product.StandardCost
= 200;
product.ListPrice = 250;
}
context.SaveChanges();
}
正常情况下,不检查并发情况,产生的T-SQL脚本如下:
exec sp_executesql N'update [SalesLT].[Product]
set [Color] = @0, [StandardCost] =
@1, [ListPrice] = @2
where ([ProductID] = @3)',
N'@0 nvarchar(5), @1 decimal(19,4),
@2 decimal(19,4), @3 int', @0=N'White',
@1=1000.0000, @2=2000.0000,@3=1004
在设置Product实体的Color属性的Concurrency Mode
为Fixed,你会发现生成的T-SQL脚本中,Where子句中增加了对Color
字段的检查:
exec sp_executesql N'update [SalesLT].[Product]
set [Color] = @0, [StandardCost] =
@1, [ListPrice] = @2
where (([ProductID] = @3) and ([Color]
= @4))',
N'@0 nvarchar(5), @1 decimal(19,4),
@2 decimal(19,4), @3 int, @4 nvarchar(5)',
@0=N'White', @1=200.0000, @2=250.0000,
@3=1004, @4=N'White'
解决并发冲突
如下的示例代码演示如何解决并发冲突。当发生并发冲突时,将抛出OptimisticConcurrencyException
异常,可以通过调用Object Context 的Refresh() 方法,传入RefreshMode.ClientsWins
选项,来解决冲突。这将重新执行LINQ to Entities 查询,并覆盖数据库的值,随后再次调用SaveChanges()
方法。
模拟并发冲突及其脚本:
public
void
UpdateProduct()
{
Product
product1 = context.Product.FirstOrDefault(p
=> p.ProductID == 1004);
if (product1
!= null)
{
product1.Color = "Black";
product1.StandardCost = 20;
product1.ListPrice = 25;
}
AdventureWorksLTEntities
context2 = new
AdventureWorksLTEntities();
Product
product2 = context2.Product.FirstOrDefault(p
=> p.ProductID == 1004);
if (product2
!= null)
{
product2.Color = "Blue";
product2.StandardCost = 18;
product2.ListPrice = 30;
}
// Save changes
of first DataContext
context.SaveChanges();
try
{
context2.SaveChanges();
}
catch
(OptimisticConcurrencyException
ex)
{
Console.WriteLine(ex.ToString());
// The concurrently
conflict can be resolved by refreshing
the DataContext
context2.Refresh(RefreshMode.ClientWins,
product2);
//
And saving the changes again
context2.SaveChanges();
}
}
Refresh的第一个参数值得注意一下,它是一个枚举值,有两个选项:StoreWins或者是ClientWins。如果是StoreWins,那么,Refresh以后,product2的值将与数据库里的对应记录的值一致(修改会丢失);而如果ClientWins,则product2的值保持,并且提交以后,会把context提交的修改覆盖。
其实,这两种方法均不完美,总会导致一部分修改丢失。但是,这总比在不知情的情况下的覆盖要好。
另外,需要说明,上面的方法,只是对并发冲突的一种模拟,这样的模式,在处理并发冲突时会有问题。一般的处理方法是,当检测到并发冲突时,提示用户会重新从数据库载入数据,然后,让用户在新数据的情况下重新修改后再次提交,直到不再有并发冲突发生。