多线程 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/awaitTask.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&lt;TResult&gt;,只是等待最终的ToListAsync()?并使用导航属性而不是这些“手动:加入。 1.您得到异常的原因是因为 EF 不能保证它返回的数据仍然有效,因为另一个线程可能已经更新/删除了数据库中的数据。 2. 在异步函数上调用.Result 比只调用函数的同步版本更糟糕(即ToList 而不是ToListAsync)。 3. 是的,但这样做会破坏异步编程的全部目的。如果您使用外键设置数据库表,您应该更喜欢导航属性而不是从上下文手动加载(正如@IvanStoev 所说) 【参考方案1】:

为什么会这样?

DbContext 不允许在同一个数据库连接上进行多个操作。在这种情况下,您有一个呼叫 (ToListAsync),然后是多个并发呼叫 (Select)。

“固定”代码是否干净?如果没有,请您提出更好的方法吗?

没有。你不应该使用.Result

您的选择是:

    (理想)更改 LINQ 查询,使其在 one 查询中包含所有必要信息,例如,使用连接或包含。这是理想的解决方案,因为只有一个查询,数据库服务器可以最有效地处理它。 一次只能执行一项操作,因为您只有一个连接。这就是.Result 工作的原因,但更好的解决方案是使用await,一次只做一个,而不是使用SelectTask.WhenAll。这种方法的缺点是一次只能执行一项操作。 保留多个操作,每个操作打开一个数据库连接。这种方法的缺点是它需要多个数据库连接。

我可以只使用 .ToList() 而不是异步变体吗?

ToListAsync 不是问题所在。问题是Select + WhenAll

【讨论】:

非常感谢!有时我想它只是退后一步来看看更广泛的问题,而不是直接的错误。我现在就试试第一个选项:)

以上是关于多线程 DbContext 操作的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法将已经打开的 MySQL 连接传递给 EF Core DbContext ?用于多线程目的

实体框架:如何防止 dbcontext 被多个线程访问?

多线程理解

什么是JAVA的多线程?

什么是Java多线程编程?

Java多线程(多线程基本操作,多线程安全问题等)