Entity Framework 并发冲突解决方案(下)

简介: Entity Framework 并发冲突解决方案

上面代码运行后,只有李四会被更新到数据库中,王五因为并发冲突且异常捕获后没有进行任何处理而不会存入数据库。


3. 数据库和客户端合并获胜

这种方式是最复杂的,需要合并数据库和客户端的数据,如果用到此方法我们需要谨记如下两点:


  • 如果原始值与数据库中的值不通,就说明数据库中的值已经被其他客户端更新,这时必须放弃当前的更新,保留数据库的更新;
  • 如果原始值与数据库的值相同,代表不会发生并发冲突,按照正常处理流程处理即可。


同样,我们将上面的例子按照上面两点进行修改:

class Program
{
    static void Main(string[] args)
    {
        int userId = 1;
        using (var db = new EfContext())
        {
            using (var ef = new EfContext())
            {
                User user1 = db.Users.FirstOrDefault(p => p.Id == userId);
                User user2 = ef.Users.FirstOrDefault(p => p.Id == userId);
                user1.Name = "李四";
                db.SaveChanges();
                try
                {
                    user2.Name = "王五";
                    ef.SaveChanges();
                }
                catch (DbUpdateConcurrencyException e)
                {
                    Retry(ef, handleDbUpdateConcurrencyException: exception =>
                    {
                        exception = (e as DbUpdateConcurrencyException).Entries;
                        foreach (var item in exception)
                        {
                            Object dv = item.GetDatabaseValues();
                            Object ov = item.OriginalValues();
                            item.OriginalValues.SetValues(dv);
                            dv.PropertyNames.Where(property =>
                                !object.Equals(ov[property], dv[property])).ToList().ForEach(property =>
                                item.Property(property).IsModified = false);
                        }
                    });
                }
            }
        }
    }
}

二、方法三

前面两种方法都是利用 SaveChanges 捕获并发异常,其实我们也可以自定义 SaveChanges 的扩展方法来处理并发异常。下面我们就来看一下具体的两种策略。


1. 普通策略

这个策略非常简单,就是利用循环来实现重试机制,代码如下:

public static partial class DbContextExtensions
{
    public static int SaveChanges(this DbContext dbContext, Action<IEnumerable<DbEntityEntry>> action,
        int retryCount = 3)
    {
        if (retryCount <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(retryCount), $"{retryCount}必须大于0");
        }
        for (int retry=1;retry<retryCount;retry++)
        {
            try
            {
            }
            catch (DbUpdateConcurrencyException e) when (retry < retryCount)
            {
                resolveConficts(e.Entries);
            }
        }
        return dbContext.SaveChanges();
    }
}

2. 高级策略
在 .NET 中已经有开发人员帮我们开发出了强大的工具 Polly ,Polly 是一个 .NET 弹性和瞬态故障处理库,允许开发人员以 Fluent 和线程安全的方式来实现重试、断路、超时、隔离和回退策略。

  • 首先我们需要定义一个枚举类型
public enum RefreshConflict
{
    StoreWins,
    ClientWins,
    MergeClientAndStore
}
  • 然后根据不同的获胜模式来刷新数据库的值
public static class RefreshEFStateExtensions
{
    public static EntityEntry Refresh(this EntityEntry tracking,
        RefreshConflict refreshMode)
    {
        switch (refreshMode)
        {
            case RefreshConflict.StoreWins:
            {
                tracking.Reload();
                break;
            }
            case RefreshConflict.ClientWins:
            {
                PropertyValues databaseValues = tracking.GetDatabaseValues();
                if (databaseValues == null)
                {
                    tracking.State = EntityState.Detached;
                }
                else
                {
                    tracking.OriginalValues.SetValues(databaseValues);
                }
                break;
            }
            case RefreshConflict.MergeClientAndStore:
            {
                PropertyValues databaseValues = tracking.GetDatabaseValues();
                if (databaseValues == null)
                {
                    tracking.State = EntityState.Detached;
                }
                else
                {
                    //当实体被更新时,刷新数据库原始值
                    PropertyValues originalValues = tracking.OriginalValues.Clone();
                    tracking.OriginalValues.SetValues(databaseValues);
                    //如果数据库中对于属性有不同的值保留数据库中的值
                    #if SelfDefine
                      databaseValues.PropertyNames // Navigation properties are not included.
                          .Where(property => !object.Equals(originalValues[property], databaseValues[property]))
                          .ForEach(property => tracking.Property(property).IsModified = false);
                    #else
                        databaseValues.Properties
                            .Where(property => !object.Equals(originalValues[property.Name],
                                databaseValues[property.Name]))
                            .ToList()
                            .ForEach(property =>
                                tracking.Property(property.Name).IsModified = false);
                    #endif
                }
                break;
            }
        }
        return tracking;
    }
}
  • 最后定义刷新状态的方法
public static partial class DbContextExtensions
{
    public static int SaveChanges(this DbContext context, RefreshConflict refreshMode, int retryCount = 3)
    {
        if (retryCount <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(retryCount), $"{retryCount}必须大于0");
        }
        return context.SaveChanges(
        conflicts => conflicts.ToList().ForEach(tracking => tracking.Refresh(refreshMode)), retryCount);
    }
    public static int SaveChanges(
        this DbContext context, RefreshConflict refreshMode, RetryStrategy retryStrategy) =>
            context.SaveChanges(
                conflicts => conflicts.ToList().ForEach(tracking => tracking.Refresh(refreshMode)), retryStrategy);
}

到这里 Entity Framework 解决并发冲突的方案已经讲完了,上面这几种方案都是固定的写法,大家可以直接将上面的代码复制进项目中使用。

目录
相关文章
|
12月前
|
Java 测试技术 开发者
COLA-statemachine事务失效踩坑
cola-statemachine是阿里开源项目COLA中的轻量级状态机组件。最大的特点是无状态、采用纯Java实现,用Fluent Interface(连贯接口)定义状态和事件,可用于管理状态转换场景。比如:订单状态、支付状态等简单有限状态场景。在实际使用的过程中笔者曾发现状态机内事务不生效的问题,经过排查得到解决,以此记录一下
1158 1
|
数据库连接 数据库 C++
entity framework core在独立类库下执行迁移操作
entity framework core在独立类库下执行迁移操作
80 0
|
SQL PHP 数据库
Yii2.0框架中的Active Record和数据访问对象(DAO)有什么区别?在实际开发中该如何选择?
Yii2.0框架中的Active Record和数据访问对象(DAO)有什么区别?在实际开发中该如何选择?
|
数据库
Entity Framework 并发冲突解决方案(上)
Entity Framework 并发冲突解决方案
159 0
Entity Framework 并发冲突解决方案(上)
|
存储 SQL 数据库
Entity Framework Core 捕获数据库变动
Entity Framework Core 捕获数据库变动
155 0
|
数据库 数据安全/隐私保护
【自然框架】稳定版的Demo —— 三:主从表的维护方式
  第一篇:【自然框架】稳定版beta1——源码下载,Demo说明   下载地址:还是老地方,自然框架的源代码、Demo、数据库、配置信息管理程序下载(2010.01.25更新)   (补充了一个元数据的 数据库结构说明文档,在上面的网页里下载)   在线演示:http://demo.conature.cn/       主从表,以人员管理为例,人员的基本信息,公司信息,联系方式,学历信息,工作经历等功能。
1024 0
|
SQL 缓存 数据库
GreenDao系列之(2)设计及机制介绍
# 总体设计类图 ![这里写图片描述](http://img.blog.csdn.net/20170214183411203?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYmlsbHBpZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
1736 0