第四节:EF Core的并发处理

1.说明

  和EF版本的并发处理方案一致,需要知道乐观并发和悲观并发的区别,EF Core只支持乐观并发;监控并发的两种方案:监测单个字段和监测整条数据,DataAnnotations 和 FluentApi的两种配置方式。

 (PS:EF Core中的并发处理模式和EF中的基本类似,其他相关概念参考:第二十节: 深入理解并发机制以及解决方案(锁机制、EF自有机制、队列模式等):

 2. 两个概念

(1).悲观并发:比如有两个用户A,B,同时登录系统修改一个文档,如果A先进入修改,则系统会把该文档锁住,B就没办法打开了, 只有等A修改完,完全退出的时候B才能进入修改。

(2).乐观并发:A,B两个用户同时登录,如果A先进入修改紧跟着B也进入了。A修改文档的同时B也在修改,如果在A保存之后/ B再保存他的修改,此时系统检测到数据库中文档记录与B刚进入时不一致,B保存时会抛出异常,修改失败。

3. 监测单个字段

用到的表结构:

(1). 监测那个字段就配置哪个字段,有两种配置方式,如下:

  A. DataAnnotations配置:在对应字段上加特性 [ConcurrencyCheck]

  B. FluentApi配置: entity.Property(p => p.age).IsConcurrencyToken();

提交数据库的时候,发现该字段中的值已经被篡改了,就会进入DbUpdateConcurrencyException并发异常中,这里面可以获取三个值:数据库原始值、 数据库现在值、内存中当前值。

通常的处理方式:

  ①. Reload一下,放弃当前内存中的实体,重新到数据库中加载当前实体,然后用当前数据库中的值进行相应的业务处理。

  ②. 直接提示用户,信息被修改,请重新操作一遍(可以更友好的提示一下)

(2).案例:

  保证T_Concurrency01有一条id=01的记录(age=10),分别用上面的两种方式进行配置,测试的结果最终age=4,执行正确。

复制代码
 1             {
 2                 //保证T_Concurrency01有一条id=01的记录(age=10)
 3                 ypfContext db1 = new ypfContext();
 4                 ypfContext db2 = new ypfContext();
 5 
 6                 try
 7                 {
 8                     var data1 = db1.T_Concurrency01.Where(u => u.id == "01").FirstOrDefault();
 9                     var data2 = db2.T_Concurrency01.Where(u => u.id == "01").FirstOrDefault();
10 
11                     data1.age = data1.age - 2;
12                     int result1 = db1.SaveChanges();
13 
14                     data2.age = data2.age - 4;
15                     int result2 = db2.SaveChanges();  //发现age的值和原先查出来的不一致,会抛异常进入cache
16                 }
17                 catch (DbUpdateConcurrencyException ex)
18                 {
19                     var entityEntry = ex.Entries.Single();
20                     var original = entityEntry.OriginalValues.ToObject() as T_Concurrency01; //数据库原始值  10
21                     var database = entityEntry.GetDatabaseValues().ToObject() as T_Concurrency01; //数据库现在值 8
22                     var current = entityEntry.CurrentValues.ToObject() as T_Concurrency01; //当前内存值 6
23                     entityEntry.Reload(); //放弃当前内存中的实体,重新到数据库中加载当前实体
24 
25                     current.age = database.age - 4;  //应该拿着当前数据库实际的值去处理,即8-4=4 
26                     entityEntry.CurrentValues.SetValues(current);
27                     int result3 = db2.SaveChanges();
28                 }
29             }
复制代码

4. 监测整条数据

用到的表结构:

 

(1). 首先要给该表在数据库中加一个字段(任意命名即可),通常叫“timeStamp”或“rowVersion”,然后进行配置:

  A. DataAnnotations配置:在对应字段上加特性[Timestamp]

  B. FluentApi配置: entity.Property(e => e.rowVersion).IsRowVersion();

提交数据库的时候,发现该表中任意字段中的值被篡改了,就会进入DbUpdateConcurrencyException并发异常中,这里面可以获取三个值:数据库原始值、 数据库现在值、内存中当前值。

通常的处理方式:

  ①. Reload一下,放弃当前内存中的实体,重新到数据库中加载当前实体,然后用当前数据库中的值进行相应的业务处理。

  ②. 直接提示用户,信息被修改,请重新操作一遍(可以更友好的提示一下)

(2). 案例

  保证T_Concurrency02有一条id=01的记录(age=10),没别用上面的两种方式进行配置,还是用age来进行测试,测试的结果最终age=4,执行正确。

复制代码
 1             {
 2                 //保证T_Concurrency02有一条id=01的记录(age=10)
 3                 ypfContext db1 = new ypfContext();
 4                 ypfContext db2 = new ypfContext();
 5 
 6                 try
 7                 {
 8                     var data1 = db1.T_Concurrency02.Where(u => u.id == "01").FirstOrDefault();
 9                     var data2 = db2.T_Concurrency02.Where(u => u.id == "01").FirstOrDefault();
10 
11                     data1.age = data1.age - 2;
12                     int result1 = db1.SaveChanges();
13 
14                     data2.age = data2.age - 4;
15                     int result2 = db2.SaveChanges();  //发现age的值和原先查出来的不一致,会抛异常进入cache
16                 }
17                 catch (DbUpdateConcurrencyException ex)
18                 {
19                     var entityEntry = ex.Entries.Single();
20                     var original = entityEntry.OriginalValues.ToObject() as T_Concurrency02; //数据库原始值  10
21                     var database = entityEntry.GetDatabaseValues().ToObject() as T_Concurrency02; //数据库现在值 8
22                     var current = entityEntry.CurrentValues.ToObject() as T_Concurrency02; //当前内存值 6
23                     entityEntry.Reload(); //放弃当前内存中的实体,重新到数据库中加载当前实体
24 
25                     current.age = database.age - 4;  //应该拿着当前数据库实际的值去处理,即8-4=4 
26                     entityEntry.CurrentValues.SetValues(current);
27                     int result3 = db2.SaveChanges();
28                 }
29             }
复制代码

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 本人才疏学浅,用郭德纲的话说“我是一个小学生”,如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @   Yaopengfei  阅读(2710)  评论(3编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
点击右上角即可分享
微信分享提示