更新记录
转载请注明出处:
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();
}
}