Ano*_*ous 4 c# concurrency asynchronous entity-framework asp.net-core
我有一个带有标准 DI DbContext 的标准 ASP.NET Core 2.0 Web API,如下所示:
services.AdDbContext<TestContext>( ... );
Run Code Online (Sandbox Code Playgroud)
我知道我的上下文将添加一个范围生命周期,这实质上意味着每次我的 API 收到新请求时,它都会创建一个新的上下文实例。
到现在为止还挺好。
现在,我创建了一个名为TestRepository的存储库,其中包含以下内容:
public class TestRepository : ITestRepository {
public TestContext _context;
public TestRepository(TestContext context) {
_context = context;
}
public async Task IncrementCounter() {
var row = await _context.Banks.FirstOrDefaultAsync();
row.Counter += 1;
await _context.SaveChangesAsync();
}
}
Run Code Online (Sandbox Code Playgroud)
所以它基本上只是以 1异步方式递增列中的一个值。ITestRepository 被添加到服务 IoC 并在需要的地方注入。
设想:
用户 A调用 API,获取 TestContext 的新实例,然后调用 IncrementCounter 方法。
在调用 SaveChangesAsync之前,用户 B调用了 API,获得了新的 TestContext,并且还调用了 IncrementCounter 方法。
现在用户 A将值1保存到列中,而用户 B也认为它应该保存值1但实际上应该是2。
即使在阅读了相关文档之后,我仍然不确定如何保证IncrementCounter方法正确递增,即使多个用户使用他们自己的TestContext实例调用 API 。
我可能让自己更困惑,但有人可以澄清如何处理同一上下文的不同实例之间的并发性吗?
相关文档:
更新 1
我曾考虑在新的“rowVersion”属性上实现 [TimeStamp] 数据注释,然后捕获 DbConcurrencyException,正如相关文档所解释的那样,但这是否解决了它是一个作用域 DbContext 以及正在尝试的不同上下文的问题SaveChangesAsync()?
这实际上与对象生命周期、范围或其他方面无关。并发就是并发。执行诸如递增计数器之类的操作本质上不是线程安全的,因此您必须使用锁或乐观并发,就像任何其他线程不安全的任务一样。数据库锁通常不受欢迎,因此乐观并发绝对是首选方法。
这在数据库级别的工作原理是您本质上有一个时间戳列,它会随着每次更改而更新。EF 足够聪明,可以观察此列,如果它在更新时的值与您第一次查询该行时的值不同,则它会中止更新。此时,您的代码会捕获异常并通过重新查询该行并尝试再次更新它来处理它。
例如,用户 A 和用户 B 尝试同时保存值 1。假设用户 B 提前几毫秒进入并成功更新列。然后这也会更新时间戳列。当用户 A 的更新到达时,更新失败,因为时间戳列不再匹配。这会冒泡回 EF,在那里它抛出一个DbUpdateConcurrencyException
. 您的代码捕获此异常,并通过重新查询行来响应,其中列现在是 1,而不是 0(并获取最新的时间戳),增加到 2,然后再次尝试保存。这一次,没有其他用户做任何事情,所以它成功了。但是,您也可能有另一个并发异常。在这种情况下,您需要冲洗并重复,最终尝试将 3 保存到列中,依此类推。
您已经链接到问题本身中的所有相关文档,因此我建议您只需花一些时间来完成所有这些并真正理解它。您需要向存储增量值的实体添加一个新属性:
[Timestamp]
public byte[] Version { get; set; }
Run Code Online (Sandbox Code Playgroud)
有了这个(并且在您显然迁移之后),EF 将在发生冲突时开始在保存时抛出异常,如上所述。为了捕捉和处理这些,我建议使用像Polly这样的库。它允许您设置重试策略,从而轻松处理这种重复的查询-增量-保存逻辑。
归档时间: |
|
查看次数: |
1403 次 |
最近记录: |