EF6学习笔记二十八:并发冲突(二)
要专业系统地学习EF推荐《你必须掌握的Entity Framework 6.x与Core 2.0》。这本书作者(汪鹏,Jeffcky)的博客:https://www.cnblogs.com/CreateMyself/
继续来弄EF中的并发,虽然上一篇也弄了,但是总觉得不得要领,这次继续书中的学习,回顾上次的学习,可能还是会理解的不太准确。
并发分为乐观并发和悲观并发,乐观就不用管了,客户端随便去修改。所以我们一直弄的是悲观并发。
这次所学的东西主要是针对悲观并发我们所采取的有哪些策略。
客户端获胜
数据库获胜
客户端数据库合并获胜
首先我有这样一个疑问,这几个概念是程序员给的,还是官方就有这个说法,乍一听感觉“获胜”带有很强烈的感情色彩啊,计算机术语一般给人的感觉比较高冷吧。
幸好我在看他人博客的时候发现了EF中有一个枚举,呵呵,还真是
在ObjectContext定义了一个方法Refresh,就是要求传递上面枚举作为参数。之前我们了解到早起的EF提供的是ObjectContext供程序员们去派生,现在是改良后的DbContext,他们之间可以互相转换,来眼熟下
// 这里写法可能毫无意义,纯粹只是温习下DbContext转换ObjectContext,认识下这个枚举 EFDbContext ctx = new EFDbContext(); var context = ((IObjectContextAdapter)ctx).ObjectContext; var stu1 = context.CreateQuery<Student3>("select * from tb_Students3"); context.Refresh(RefreshMode.ClientWins, stu1);
关于ObjectContext和这个枚举就到这里了,后面我没有去弄到,那到底有没有必要用到,我觉得没有,并且作者在书中也是如此
继续说回上面三个概念,其实理解起来非常简单。客户端获胜就是随便客户端请求更新数据;数据库获胜,那就是数据库说了算的,比如数据库中某张表某个字段设置了阈值;客户端数据库合并获胜,那就是商量着来,下面的内容主要说这个。
回顾一下上次的内容,要想捕获并发冲突需要做配置,两种方式:并发Token、行版本(RowVersion)
其实这里就有两个问题
1、为什么要额外配置才能捕获到并发异常,我用1除以0,就会报异常:System.DivideByZeroException: 尝试除以零。这个为什么就不需要配置才能捕获呢?
2、并发Token和RowVersion有什么区别?
第一个问题,如果我们不为属性或者实体配置并发的话,那么其实就是属于乐观并发,数据库中保留最后一次更新的内容,这个完全没有问题,对吧?所以如果你不需要做并发处理的话,那么对你来说并发就不算异常。
我想想看我上一家公司有没有做并发异常的处理,应该是没有。最主要的是用户少,而且还有权限,各个功能模块都由不同的角色去操作,这就又降低了并发的可能。
第二个问题,其实我是没太弄清楚。并发Token是为某个属性配置,而RowVersion是在实体中新添加的一个属性,那是不是rowVersion是作用这个实体的所有属性呢?
前面用到EF提供的DbUpdateConcurrencyException类来处理并发冲突。那客户端获胜和数据库获胜我就不说了,下面来说客户端数据库合并获胜。
怎么合并,来看看作者怎么说
1、如果原始值与数据库中的值不同,意味着数据库中的值已被其他并发客户端更新,就放弃更新此属性,并保留数据库中的值。
2、如果原始值与数据库中的值相同,意味着此属性不会产生并发冲突,就会正常处理
我知道这两句话肯定是重点,但是我理解不了,而且我原封不动地按照作者的代码写并没有得到想要的结果,所以我就不去理会了,来说说我自己的理解
这里有一个student4类,我为Name属性配置了并发Token
public class Student4:BaseEntity { public string Name { get; set; } public int Score { get; set; } }
还有一个Score属性没有配置并发Token,数据库中student原始值为{name:"张三",score=100},并发进来修改的内容分别为{name:"李四",score=99}、{name:"王五",score=88},最后我想要的结果是数据库中修改为{name:"李四",score=88}
如果有两个请求进来同时修改同一个Student,我想要的结果就是Name属性只会第一次更新成功,Score因为没有做并发,两次更新都可以。
那么思路是不是就是,找到实体中设置了并发的属性,将它的IsModified = false,我是这样想的,我觉得就应该就这样啊,并发的不更新,非并发的更新,有毛病吗?
但是如何知道这个属性有没有设置并发,这真的很让人抓狂。因为我没找到,按理说EF应该提供了某个方法的。
你说我们用Fluent API对实体或者属性做的那些配置,那到底怎么去获取这些配置呢?
我只能想到DataAnnotations方式配置,因为是通过特性的方式来的,那么首先就要知道如何通过DataAnnotations的方式来配置并发属性,还好我找到了
public class Student4:BaseEntity { [ConcurrencyCheck] public string Name { get; set; } public int Score { get; set; } }
那么最后我是这样写的
using (EFDbContext ctx1 = new EFDbContext()) using (EFDbContext ctx2 = new EFDbContext()) { var stu1 = ctx1.Students4.FirstOrDefault(); var stu2 = ctx2.Students4.FirstOrDefault(); stu1.Name = "李四"; stu1.Score = 99; stu2.Name = "王五"; stu2.Score = 88; ctx1.SaveChanges(); try { ctx2.SaveChanges(); } catch (DbUpdateConcurrencyException ex) { var tracking = ex.Entries.Single(); var originalValues = tracking.OriginalValues; var databaseValues = tracking.GetDatabaseValues(); var currentValues = tracking.CurrentValues; Console.WriteLine($"original-name:{originalValues.GetValue<string>("Name")},original-score:{originalValues.GetValue<int>("Score")}"); // original-name:张三,original-score:100 Console.WriteLine($"database-name:{databaseValues.GetValue<string>("Name")},database-score:{databaseValues.GetValue<int>("Score")}"); // database-name:李四,database-score:99 Console.WriteLine($"current-name:{currentValues.GetValue<string>("Name")},current-score:{currentValues.GetValue<int>("Score")}"); // current-name:王五,current-score:88 originalValues.SetValues(databaseValues); var tracking2 = ex.Entries.Single(); var originalValues2 = tracking2.OriginalValues; var databaseValues2 = tracking2.GetDatabaseValues(); var currentValues2 = tracking2.CurrentValues; Console.WriteLine($"original2-name:{originalValues2.GetValue<string>("Name")},original2-score:{originalValues2.GetValue<int>("Score")}"); // original2-name:李四,original2-score:99 Console.WriteLine($"database2-name:{databaseValues2.GetValue<string>("Name")},database2-score:{databaseValues2.GetValue<int>("Score")}"); // database2-name:李四,database2-score:99 Console.WriteLine($"current2-name:{currentValues2.GetValue<string>("Name")},current2-score:{currentValues2.GetValue<int>("Score")}"); // current2-name:王五,current2-score:88 var concurrencyProp = ((Student4)databaseValues.ToObject()).GetType().GetProperties().Where(x => Attribute.IsDefined(x, typeof(ConcurrencyCheckAttribute))).Single(); Console.WriteLine(concurrencyProp.Name); tracking.Property(concurrencyProp.Name).IsModified = false; ctx2.SaveChanges(); } }
最后虽然得到了想要的结果但还是感觉不行 ,后面还有一节高级版的解析,利用Polly库来实现重试策略。后面接着学。