ADO.NET Entity Framework 深入分析, Part 6 – 处理并发(Concurrency Handling)
 
2008-12-10 来源:EntLib.com
 

前面的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提交的修改覆盖。

其实,这两种方法均不完美,总会导致一部分修改丢失。但是,这总比在不知情的情况下的覆盖要好。 另外,需要说明,上面的方法,只是对并发冲突的一种模拟,这样的模式,在处理并发冲突时会有问题。一般的处理方法是,当检测到并发冲突时,提示用户会重新从数据库载入数据,然后,让用户在新数据的情况下重新修改后再次提交,直到不再有并发冲突发生。


火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。
资源网站: UML软件工程组织