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 解决并发冲突的方案已经讲完了,上面这几种方案都是固定的写法,大家可以直接将上面的代码复制进项目中使用。

目录
相关文章
|
5月前
|
SQL 数据处理 数据库
提升数据处理效率:深入探讨Entity Framework Core中的批量插入与更新操作及其优缺点
【8月更文挑战第31天】在软件开发中,批量插入和更新数据是常见需求。Entity Framework Core 提供了批处理功能,如 `AddRange` 和原生 SQL 更新,以提高效率。本文通过对比这两种方法,详细探讨它们的优缺点及适用场景。
162 0
|
5月前
|
API 数据库 开发者
掌握数据完整性的关键:全面解析Entity Framework Core中的事务管理策略及其应用
【8月更文挑战第31天】在数据库操作中,确保数据完整性至关重要。Entity Framework Core(EF Core)作为一款强大的ORM工具,提供了丰富的API支持事务管理,帮助开发者实现数据的一致性和完整性。
55 0
|
5月前
|
存储 SQL 测试技术
Entity Framework Core 中的存储过程超厉害!从定义到调用全攻略,提升性能与安全性!
【8月更文挑战第31天】在现代软件开发中,数据库操作效率至关重要。Entity Framework Core(EF Core)作为强大的对象关系映射(ORM)框架,支持存储过程,可提升数据库操作的性能、安全性和可维护性。本文详细介绍如何在 EF Core 中定义、配置及调用存储过程,并提供最佳实践建议,包括性能优化、安全性增强、代码可维护性提升以及参数化查询等。通过遵循这些指导原则,开发者能够充分利用存储过程的优势,显著提高应用程序质量和性能。附带完整示例代码,展示从定义实体类到调用存储过程的全过程。
320 0
|
5月前
|
存储 测试技术 数据库
Entity Framework Core Migrations 超厉害!轻松实现数据库版本控制,让你的开发更顺畅!
【8月更文挑战第31天】数据库的演变是软件开发中不可或缺的部分。随着应用发展,数据库需不断调整以适应新功能。Entity Framework Core Migrations 作为数据库的守护者,提供强大的版本控制手段,确保不同环境下的数据库一致性。通过创建和管理迁移脚本,开发者可以有序地管理数据库变更,避免混乱和数据丢失。安装并配置好 EF Core 后,可以通过命令行工具轻松创建、应用及回滚迁移,如 `dotnet ef migrations add InitialMigration` 和 `dotnet ef database update`。
71 0
|
5月前
|
存储 缓存 数据库连接
Entity Framework Core 跨数据库查询超厉害!多数据库连接最佳实践,让你的开发更高效!
【8月更文挑战第31天】在现代软件开发中,跨数据库查询是常见需求。Entity Framework Core(EF Core)作为强大的ORM框架,支持多种方法实现这一功能。本文介绍了在EF Core中进行跨数据库查询的最佳实践,包括:理解数据库上下文、使用多个上下文进行查询、处理数据库连接与事务,以及性能优化策略。通过创建独立的数据库上下文如`UserContext`和`OrderContext`,并在业务逻辑中同时使用它们,可以轻松实现跨库查询。此外,利用`TransactionScope`可确保事务一致性,从而提高系统的可靠性和效率。
285 0
|
5月前
|
测试技术 数据库
确保数据访问层的可靠性:详细解析使用Entity Framework Core进行隔离的单元测试方法
【8月更文挑战第31天】在软件开发中,单元测试是确保代码质量的关键。本文通过一个在线商店的商品查询功能案例,介绍了如何使用EF Core和Moq框架实现数据访问层的隔离测试。通过模拟`ApplicationDbContext`,我们能够在不访问真实数据库的情况下对`ProductService`进行单元测试,提高测试效率并保证测试稳定性。这种方法是实现高效、可靠单元测试的重要手段。
58 0
|
5月前
|
存储 弹性计算 Java
java依赖冲突解决问题之应用依赖过于复杂如何解决
java依赖冲突解决问题之应用依赖过于复杂如何解决
|
5月前
|
存储 缓存 Java
Java本地高性能缓存实践问题之使用@CachePut注解来更新缓存中数据的问题如何解决
Java本地高性能缓存实践问题之使用@CachePut注解来更新缓存中数据的问题如何解决
120 0
|
5月前
|
存储 缓存 Java
Java本地高性能缓存实践问题之使用@CachePut注解来更新缓存中的数据的问题如何解决
Java本地高性能缓存实践问题之使用@CachePut注解来更新缓存中的数据的问题如何解决
119 0
|
数据库连接 数据库 C++
entity framework core在独立类库下执行迁移操作
entity framework core在独立类库下执行迁移操作
118 0