多线程 DbContext 操作
Posted
技术标签:
【中文标题】多线程 DbContext 操作【英文标题】:Multithreaded DbContext Operations 【发布时间】:2021-10-06 17:49:29 【问题描述】:以下代码(在单个 DbContext 上)导致“在前一个操作完成之前在此上下文上启动了第二个操作”。
[HttpGet]
[Route("api/[controller]/circuit")]
public async Task<IEnumerable<object>> GetAllCircuits()
var circuits = await Task.WhenAll((await _context.Circuits.ToListAsync()).Select(async x => new
x.Id,
x.FastestLap,
x.LengthInMiles,
Country = await _context.Countries.FirstOrDefaultAsync(c => c.Id == x.CountryId),
Map = await _context.Maps.FirstOrDefaultAsync(m => m.Id == x.MapId),
Locations = await _context.Locations.Where(l => l.CircuitId == x.Id).ToListAsync()
));
return circuits;
我能够通过删除async/await
和Task.WhenAll
部分并用.Result
替换它们来解决这个问题,这在.NET 中似乎是一个很大的禁忌。固定示例如下:
[HttpGet]
[Route("api/[controller]/circuit")]
public async Task<IEnumerable<object>> GetAllCircuits()
var circuits = (await _context.Circuits.ToListAsync()).Select(x => new
x.Id,
x.FastestLap,
x.LengthInMiles,
Country = _context.Countries.FirstOrDefaultAsync(c => c.Id == x.CountryId).Result,
Map = _context.Maps.FirstOrDefaultAsync(m => m.Id == x.MapId).Result,
Locations = _context.Locations.Where(l => l.CircuitId == x.Id).ToListAsync().Result
);
return circuits;
我的三个问题是:
-
为什么会这样?
“固定”代码是否干净?如果没有,您能否提出更好的方法?
我可以只使用
.ToList()
而不是异步变体吗?
谢谢!
【问题讨论】:
你为什么不写一个“普通”的 LINQ to Entities 查询——里面没有异步的东西,IQueryable<TResult>
,只是等待最终的ToListAsync()
?并使用导航属性而不是这些“手动:加入。
1.您得到异常的原因是因为 EF 不能保证它返回的数据仍然有效,因为另一个线程可能已经更新/删除了数据库中的数据。 2. 在异步函数上调用.Result
比只调用函数的同步版本更糟糕(即ToList
而不是ToListAsync
)。 3. 是的,但这样做会破坏异步编程的全部目的。如果您使用外键设置数据库表,您应该更喜欢导航属性而不是从上下文手动加载(正如@IvanStoev 所说)
【参考方案1】:
为什么会这样?
DbContext
不允许在同一个数据库连接上进行多个操作。在这种情况下,您有一个呼叫 (ToListAsync
),然后是多个并发呼叫 (Select
)。
“固定”代码是否干净?如果没有,请您提出更好的方法吗?
没有。你不应该使用.Result
。
您的选择是:
-
(理想)更改 LINQ 查询,使其在 one 查询中包含所有必要信息,例如,使用连接或包含。这是理想的解决方案,因为只有一个查询,数据库服务器可以最有效地处理它。
一次只能执行一项操作,因为您只有一个连接。这就是
.Result
工作的原因,但更好的解决方案是使用await
,一次只做一个,而不是使用Select
和Task.WhenAll
。这种方法的缺点是一次只能执行一项操作。
保留多个操作,每个操作打开一个数据库连接。这种方法的缺点是它需要多个数据库连接。
我可以只使用 .ToList() 而不是异步变体吗?
ToListAsync
不是问题所在。问题是Select
+ WhenAll
。
【讨论】:
非常感谢!有时我想它只是退后一步来看看更广泛的问题,而不是直接的错误。我现在就试试第一个选项:)以上是关于多线程 DbContext 操作的主要内容,如果未能解决你的问题,请参考以下文章