• EF6学习笔记二十九:并发冲突(三)


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

    这里学到并发冲突的高级解析,就是基于前面的内容,我们来封装一个比较完整的解决方案。

    这次主要get到这些知识点,有新的,也有对之前一些知识点的刷新

    1、在并发冲突(一)随笔中我开头举的例子不恰当

    2、打破了必须要配置并发才能捕获并发冲突,之前怎么就没想到并发删除呢?

    3、在并发冲突(二)中我们看到EF中有个枚举认识到客户端获胜和数据库获胜这些东西确实出自官方,并且我说到这个没有必要用到。在高级版这里,作者将他扩展了

    4、tracking.Reload()方法的认识

         当实体被删除时,重新加载,设置追踪状态为Detached

         当实体被更新时,重新加载,设置追踪状态为Unchanged

    5、when对我来说是一个新关键字,是的,我之前不知道C#中还有这个关键字。它可以配合try/catch、switch/case来使用。详细学习参考MSDN:https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/when

    6、nameof()对我来说也是一个新的方法,以前碰到过这样的问题,不过当时是在JS中。就是一个变量str,我怎么得到“str”字符串?那么就用nameof.Console.WriteLine(nameof(str));  //  result:str

    7、客户端数据库合并获胜还是按照我自己的来的 ,和作者的代码不一样

    并发冲突(一)中我举了这样一个例子

    using (EFDbContext ctx1 = new EFDbContext())
    using (EFDbContext ctx2 = new EFDbContext())
             {
                  var stu1 = ctx1.Students.FirstOrDefault();
                  var stu2 = ctx2.Students.FirstOrDefault();
                  stu1.Name = "李四";
                  stu1.Score = 99;
                  stu2.Name = "张三";
                  stu2.Score = 100;
                  try
                  {
                      ctx2.SaveChanges();
                  }
                  catch (DbUpdateConcurrencyException ex)
                  {
                      throw ex;
                  }
              }

    虽然确实是并发进来,但是因为数据的问题又不会造成并发冲突异常。因为第二次修改的值和查询出来的实体值是一样的。EF认为没有做修改。

    因为数据是从数据库中拿过来的,不能保证内存中的值和数据库一致。这里就纪念一下吧,我对这个问题没有去想处理办法,我觉得不用去处理。

    前面的一直肯定这一点:想要捕获并发冲突就一定要配置,但其实如果是并发删除进来就不必了。只不过这种情况我们平时不会这么想方设法地去写。

    下面就来对并发冲突慢慢封装一个完整的解决方案出来吧。

    还是按照从简单的到复杂的来

    这是一个简单的Student类,使用rowversion的方式配置并发

    public class BaseEntity
    {
        public BaseEntity()
        {
            this.Id = Guid.NewGuid().ToString();
            this.AddTime = DateTime.Now;
        }
    
        public string Id { get; set; }
        public DateTime AddTime { get; set; }
    }
    
    
    public class Student:BaseEntity
    {
        public string Name { get; set; }
        public int Score { get; set; }
        public byte[] RowVersion { get; set; }
    }

    简单的重试策略,扩展DBcontext类

     简单重试策略
    public static partial class DbContextExtensions
    {
        public static int SaveChanges(this DbContext context, Action<IEnumerable<DbEntityEntry>> handler, int retryCount = 3)
        {
            if (retryCount < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(retryCount),"不能小于0");
            }
            for (int retry = 0; retry < retryCount; retry++)
            {
                try
                {
                    return context.SaveChanges();
                }
                catch (DbUpdateConcurrencyException ex)
                {
                    handler(ex.Entries);                    
                }
            }
            return context.SaveChanges();
        }
    }

    现在来调用就简洁了很多

    using (EFDbContext ctx1 = new EFDbContext())
    using (EFDbContext ctx2 = new EFDbContext())
    {
        var stu1 = ctx1.Students.FirstOrDefault();
        var stu2 = ctx2.Students.FirstOrDefault();   
        stu1.Name = "李四";
        stu1.Score = 99;
        stu2.Name = "王五";
        stu2.Score = 88;
        ctx1.SaveChanges();
        ctx2.SaveChanges(entites =>
        {
            var tracking = entites.Single();
            tracking.OriginalValues.SetValues(tracking.GetDatabaseValues());
        });
    }

    现在来用Polly库,更简洁

    Polly使用分三步

    1、指定要捕获的异常类型

    2、指定异常处理的策略:重试、断路、超时……

    3、委托一个给polly可能造成该异常的方法,让polly去执行

    public static partial class DbContextExtensions
        {
            public static int SaveChanges(this DbContext context, Action<IEnumerable<DbEntityEntry>> handle, int retryCount = 3)
            {
                var retryPolicy = Policy.Handle<DbUpdateConcurrencyException>()
                    .Retry(retryCount, (exception, count) =>
                    {
                        handle(((DbUpdateConcurrencyException)exception).Entries);
                    });
                return retryPolicy.Execute(context.SaveChanges);
            }
        }

    到现在我们还没有加入客户端获胜、数据库获胜、合并获胜这些情况的处理

    我们想到的是不是应该封装一个方法供外部去调用,只需要告诉我谁获胜就行了?

    先来一个枚举

    public enum RefreshReflict
        {
            ClientWins =10,
            StoreWins = 20,
            MergeClientAndStore = 30
        }

    这里就说到Refresh了,作者对RefreshEFState类进行了如下的扩展,在Refresh中分别去实现客户端获胜这些……

    public static class RefreshEFStateExtension
    {
        public static DbEntityEntry Refresh(this DbEntityEntry tracking, RefreshReflict refreshMode)
        {
            switch (refreshMode)
            {
                case RefreshReflict.ClientWins:
                    break;
                case RefreshReflict.StoreWins:
                    break;
                case RefreshReflict.MergeClientAndStore:
                    break;
                default:
                    break;
            }
            return tracking;
        }

    最后再次对Dbcontext进行扩展,提供一个最终供外部调用的SaveChanges方法

    public static partial class DbContextExtensions
    {
        public static int SaveChanges(this DbContext context, RefreshReflict refreshMode, int retryCount = 3)
        {
            if (retryCount < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(retryCount),"不能小于0");
            }
            return context.SaveChanges(entities =>
            {
                entities.ToList().ForEach(tracking => tracking.Refresh(refreshMode));
            }, retryCount);
        }
    }

    基本就是这样。最后因为我和作者对合并获胜写的不一样,贴出来看下

    我按照自己的写法来需要采用DataAnnotations的方式来配置并发属性,所以直到现在我都不认为我的是对的,但按照作者的写法来确实也出不来结果,还是没有真正理解?

    public class Teacher:BaseEntity
        {
            [ConcurrencyCheck]
            public string Name { get; set; }
            public int Score { get; set; }
            [ConcurrencyCheck]
            public string Subject { get; set;}
        }
    View Code
    public static class RefreshEFStateExtensions
        {
            public static DbEntityEntry Refresh(this DbEntityEntry tracking, RefreshReflict refreshMode)
            {
                switch (refreshMode)
                {
                    case RefreshReflict.ClientWins:
                        {
                            Console.WriteLine("ClientWins");
                            var databaseValues = tracking.GetDatabaseValues();
                            if (databaseValues == null)
                            {
                                tracking.State = EntityState.Deleted;
                            }
                            else
                            {
                                tracking.OriginalValues.SetValues(tracking.GetDatabaseValues());
                            }
                            break;
                        }
                    case RefreshReflict.StoreWins:
                        {
                            Console.WriteLine("StoreWins");
                            //  当实体被删除时,重新加载,设置追踪状态为Detached
                            //  当实体被更新时,重新加载,设置追踪状态为Unchanged
                            //  这里对并发更新,只是简单的忽略,如果有其他的操作自行补吧
                            tracking.Reload();
                        }
                        break;
                    case RefreshReflict.MergeClientAndStore:
                        {
                            Console.WriteLine("MergeClientAndStore");
                            var databaseValues = tracking.GetDatabaseValues();
                            if (databaseValues == null)
                            {
                                tracking.State = EntityState.Deleted;
                            }
                            else
                            {
                                var originalValues = tracking.OriginalValues;
                                var currentValues = tracking.CurrentValues;
                                originalValues.SetValues(databaseValues);
    
                                object obj = tracking.Entity;
                                List<string> ConcurrencyProperties = new List<string>();
                                obj.GetType().GetProperties().ToList().ForEach(property =>
                                {
                                    if (Attribute.IsDefined(property, typeof(ConcurrencyCheckAttribute)))
                                    {
                                        ConcurrencyProperties.Add(property.Name);
                                    }
                                });
                                //  ConcurrencyProperties.ForEach(property => Console.WriteLine(property)); //  Name Subject
                                foreach (var item in ConcurrencyProperties)
                                {
                                    tracking.Property(item).IsModified = false;
                                }
                            }
    
                            //  作者是这样写的
                            //var originalValues = tracking.OriginalValues.Clone();
                            //tracking.OriginalValues.SetValues(databaseValues);
                            //databaseValues.PropertyNames.Where(property => !object.Equals(originalValues[property], databaseValues[property]))
                            //    .ToList()
                            //    .ForEach(property => tracking.Property(property).IsModified = false);
                        }
                        break;
                }
                return tracking;
            }
        }
    View Code

    EF中的并发内容就到这里结束了,后面的就是性能优化和实战了,继续学吧。

  • 相关阅读:
    如何在局域网安装Redmine(转贴)
    介绍开源的项目管理系统-Redmine
    【转】ios开发证书,描述文件,bundle ID的关系
    【转】Spring+Mybatis+SpringMVC+Maven+MySql搭建实例
    【转】 Tomcat+redis+nginx配置
    【转】Java做服务器开发语言
    【转】程序员的五大陷阱
    一个简单的网页服务器
    【转】Eclipse中创建并运行Servlet项目
    【转】在Eclipse环境下配置Servlet开发环境
  • 原文地址:https://www.cnblogs.com/jinshan-go/p/10409623.html
Copyright © 2020-2023  润新知