【发布时间】: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 所说)
标签: c# multithreading .net-core async-await entity-framework-core