ADO.NET Entity Framework 深入分析, Part 6 – 处理并发(Concurrency Handling)
ADO.NET Entity Framework 深入分析, Part 6 – 处理并发(Concurrency Handling)
前面的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提交的修改覆盖。
其实,这两种方法均不完美,总会导致一部分修改丢失。但是,这总比在不知情的情况下的覆盖要好。 另外,需要说明,上面的方法,只是对并发冲突的一种模拟,这样的模式,在处理并发冲突时会有问题。一般的处理方法是,当检测到并发冲突时,提示用户会重新从数据库载入数据,然后,让用户在新数据的情况下重新修改后再次提交,直到不再有并发冲突发生。