zoukankan      html  css  js  c++  java
  • 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库来实现重试策略。后面接着学。

  • 相关阅读:
    zabbix (2.0.6) 历史记录处乱码
    centos 关闭防火墙
    android 带边框的圆角按钮
    Android开发如何在4.0及以上系统中自定义TitleBar
    Android配置时,点击eclipse里Window->Preferences里的android选项出错
    .net 内存泄露检测工具
    UVA 548 Tree
    UVA 536 Tree Recovery
    UVA 514 Rails
    UVA 442 Matrix Chain Multiplication
  • 原文地址:https://www.cnblogs.com/jinshan-go/p/10393583.html
Copyright © 2011-2022 走看看