要专业系统地学习EF推荐《你必须掌握的Entity Framework 6.x与Core 2.0》。这本书作者(汪鹏,Jeffcky)的博客:https://www.cnblogs.com/CreateMyself/

继续来弄EF中的并发,虽然上一篇也弄了,但是总觉得不得要领,这次继续书中的学习,回顾上次的学习,可能还是会理解的不太准确。

并发分为乐观并发和悲观并发,乐观就不用管了,客户端随便去修改。所以我们一直弄的是悲观并发。

这次所学的东西主要是针对悲观并发我们所采取的有哪些策略。

客户端获胜

数据库获胜

客户端数据库合并获胜

首先我有这样一个疑问,这几个概念是程序员给的,还是官方就有这个说法,乍一听感觉“获胜”带有很强烈的感情色彩啊,计算机术语一般给人的感觉比较高冷吧。

幸好我在看他人博客的时候发现了EF中有一个枚举,呵呵,还真是

EF6学习笔记二十八:并发冲突(二)-LMLPHP

在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();
    }
}

EF6学习笔记二十八:并发冲突(二)-LMLPHP

最后虽然得到了想要的结果但还是感觉不行 ,后面还有一节高级版的解析,利用Polly库来实现重试策略。后面接着学。

02-18 07:04