我从数据库中加载了一个对象/实体:
var db = new DbContext();
//...
var item = db.Items.First();
然后我要执行两个异步任务,当返回时,更新该项目上的数据:
var task1 = Function1(db, item);
var task2 = Function2(db, item);
await Task.WhenAll(new Task[] { task1 , task2 });
这两个函数将具有一些用于获取,设置和保存项目(不同)属性的代码,如下所示:
var orderId = await CallApi();
item.OrderId = orderId;
db.Entry(item).State = EntityState.Modified;
await db.SaveChangesAsync();
但是,由于它们异步运行,因此出现错误:A second operation started on this context before a previous asynchronous operation completed
。
我尝试在Function中更新dbContext,但是随后出现错误An entity object cannot be referenced by multiple instances of IEntityChangeTracker
。
我了解为什么会同时遇到这两个错误,我的问题是:哪种编码模式可以最好地解决此问题?
编辑 好的,以上是一个简化的示例。实际上,函数中还有很多工作要做,因此很难将所有逻辑移出函数并移入调用方法。我也希望所有工作都是独立的。
item
和Function1
更新的Function2
上的属性是离散的。我正在使用this library来确保保存不会覆盖所有字段。
答案 0 :(得分:1)
您可以移动代码
db.Entry(item).State = EntityState.Modified;
await db.SaveChangesAsync();
在Task.Whenall()
之后的。这两个函数的工作是更新该对象的属性。
答案 1 :(得分:1)
不要让两个线程在同一个dbContext上执行操作。 dbContext跟踪获取的项目。如果两个线程同时在同一DbContext上获取和更改项目,则可能导致不良结果。
此外:如果您获取了Item
,则只需更改所需的属性即可。您无需将状态设置为“已修改”。 dbContext可以检查其是否具有原始值。
因此,创建创建自己的DbContext的函数,获取请求的数据,更改数据,保存数据并最终处置dbContext。
async Task Function1(...)
{
using (var dbcontext = new MyDbContext(...))
{
// Start Fetching the item that must be changed; don't await yet
var taskFetchItemToChange = dbContext.Items
.Where(...)
.FirstOrDefaultAsync();
// Start fetching the orderId that must be changed; don't await yet
var taskFetchOrderId = this.CallApiAsync();
// await until both item and orderId are fetched:
await Task.WhenAll(new Task[] {taskFetchItemToChange, taskFetchOrderId});
var fetchedItemToChange = taskFetchItemToChange.Result;
var fetchedOrderId = taskFetchOrderId.Result;
fetchedItemToChange.OrderId = fetchedOrderId;
// or do this in one big unreadable unmaintainable untestable step
await dbContext.SaveChangesAsync();
}
}
您将拥有相似的Function2,或者具有不同参数的相同Function1。
var taskFunction1 = Function1();
var taskFunction2 = Function2();
await Task.WhenAll( new Task[] {taskFunction1, taskFunction2});