更新记录
转载请注明出处:
2022年10月26日 发布。
2022年10月22日 从笔记迁移到博客。

待完善

C#综合揭秘——Entity Framework 并发处理详解 – 风尘浪子 – 博客园 (cnblogs.com)
Entity Framework 并发冲突解决方案_喵叔-CSDN博客

EF Core中的异步方法

异步方法大部分是定义在Microsoft.EntityFrameworkCore这个命名空间下EntityFrameworkQueryableExtensions等类中的扩展方法,记得using。

比如下列这些方法:

AddAsync()、AddRangeAsync()、AllAsync()、AnyAsync、
AverageAsync、ContainsAsync、CountAsync、FirstAsync、
FirstOrDefaultAsync、ForEachAsync、LongCountAsync、MaxAsync、
MinAsync、SingleAsync、SingleOrDefaultAsync、SumAsync

有的方法没有异步方法,比如IQueryable的这些异步的扩展方法都是“立即执行”方法,而GroupBy、OrderBy、Join、Where“非立即执行”方法则没有对应的异步方法。为什么?因为“非立即执行”方法并没有实际执行SQL语句,并不是消耗IO的操作。

如何异步遍历IQUERYABLE

方式1、ToListAsync()、ToArrayAsync().结果集不要太大。
方式2、await foreach (Book b in ctx.Books.AsAsyncEnumerable())
不过,一般没必要这么做。

并发说明

说明

并发控制:避免多个用户同时操作资源造成的并发冲突问题。

数据库层面的两种策略:悲观、乐观。

最好的解决方案:非数据库解决方案。

举例:统计点击量。

EF中的并发问题

比如下述的执行流程:

用户1取得表1的数据,并且正在修改中

​ 用户2在用户1后,取得表1的数据,并且正在修改中

​ 此时用户1提交数据到表1中

​ 然后用户2提交数据到表1中

​ 这时造成用户1的提交的数据全部消失

并发冲突(concurrency conflicts)

乐观并发(Optimistic concurrency):并发令牌

乐观并发控制(Optimistic concurrency):RowVersion

1、SQLServer数据库可以用一个byte[]类型的属性做并发令牌属性,然后使用IsRowVersion()把这个属性设置为RowVersion类型,这样这个属性对应的数据库列就会被设置为ROWVERSION类型。对于ROWVERSION类型的列,在每次插入或更新行时,数据库会自动为这一行的ROWVERSION类型的列其生成新值。
2、在SQLServer中,timestamp和rowversion是同一种类型的不同别名而已。
3、乐观并发控制能够避免悲观锁带来的性能、死锁等问题,因此推荐使用乐观并发控制而不是悲观锁。
4、如果有一个确定的字段要被进行并发控制,那么使用IsConcurrencyToken()把这个字段设置为并发令牌即可;
5、如果无法确定一个唯一的并发令牌列,那么就可以引入一个额外的属性设置为并发令牌,并且在每次更新数据的时候,手动更新这一列的值。如果用的是SQLServer数据库,那么也可以采用RowVersion列,这样就不用开发者手动来在每次更新数据的时候,手动更新并发令牌的值了。

悲观并发(Pessimistic concurrency)

1、悲观并发控制一般采用行锁、表锁等排他锁对资源进行锁定,确保同时只有一个使用者操作被锁定的资源。
2、EF Core没有封装悲观并发控制的使用,需要开发人员编写原生SQL语句来使用悲观并发控制。不同数据库的语法不一样。
3、锁是独占、排他的,如果系统并发量很大的话,会严重影响性能,如果使用不当的话,甚至会导致死锁。
4、不同数据库的语法不一样。

悲观并发控制的使用比较简单,使用对应数据库的锁SQL语句即可。

解决并发问题的方法

说明

使用EF提供的并发令牌实现自动解决并发问题,无需时间戳

数据表设置最后一次更新时间字段,每次提交之前手动检测是否相同

使用EF提供的令牌解决并发

使用数据注解方式设置

public class Blog
{
  //...其他属性
  [ConcurrencyCheck]
  public string Context { get; set; }
}

使用FluentAPI方式设置

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
    .ToTable("Blog")
    .Property(x=>x.Context)
    .IsConcurrencyToken();
}

使用最后一次更新时间解决并发

使用数据注解方式设置

public class Post 
{
    [Timestamp]
    public byte[] Timestamp { get; set; }
}

使用FluentAPI方式设置

public class Blog
{
    // Code removed for brevity
    public byte[] Timestamp { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
    .ToTable("Blog")
    .Property(x=>x.Timestamp)
    .ValueGeneratedOnAddOrUpdate()
    .IsConcurrencyToken();
}

处理并发冲突(Concurrency Conflicts)

处理并发冲突的三种方式

说明

Database wins(数据库优先)

Client wins(客户端优先)

User-specific custom resolution(自定义规则)

Database wins(数据库优先)

将用户提交的内容丢弃,重载Entity再提交即可

var transactions = new TransactionScope();
try
{
    // 执行代码
}
catch (DbUpdateConcurrencyException dbUpdateConcurrencyException)
{
    try
    {
        //出现异常,不保存用户数据,直接将实体进行重载再提交
        dbUpdateConcurrencyException.Entries.Single().Reload();
        await _context.SaveChangesAsync();
        return RedirectToAction("Index");
    }
    catch (Exception exception)
    {
        ExceptionDispatchInfo.Capture(exception.InnerException).Throw();
    }
}

Client wins(客户端优先)

直接将entity的原始值设置为指定的值,再进行提交到数据库

var transactions = new TransactionScope();
try
{
    //....
}
catch (DbUpdateConcurrencyException dbUpdateConcurrencyException)
{
    try
    {
        //获得
        var entry = dbUpdateConcurrencyException.Entries.Single();
        //强制将entity原始值设置为指定值
        entry.OriginalValues.SetValues(entry.GetDatabaseValues());
        await _context.SaveChangesAsync();
        return RedirectToAction("Index");
    }
    catch (Exception exception)
    {
        ExceptionDispatchInfo.Capture(exception.InnerException).Throw();
    }
}

User-specific custom resolution(自定义规则)

原文地址:http://www.cnblogs.com/cqpanda/p/16820809.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性