从 2.2 迁移到 3.1 后 EF Core 出现问题

Problem with EF Core after migrating from 2.2 to 3.1

在 2.2 中我可以这样写:

        List<Claim> claims = new List<Claim>();
        var userRoles = await _userManager.GetRolesAsync(user);
        foreach (var role in _roleManager.Roles.Where(a => userRoles.Contains(a.Name)))
        {
            claims.AddRange(await _roleManager.GetClaimsAsync(role));
        }

        return claims;

在 3.1 中它给我这个错误:

System.InvalidOperationException: There is already an open DataReader associated with this Command which must be closed first.

但是如果我添加

ToList()

对于 forEach 子句,它工作正常(像这样):

        List<Claim> claims = new List<Claim>();
        var userRoles = await _userManager.GetRolesAsync(user);
        foreach (var role in _roleManager.Roles.Where(a => userRoles.Contains(a.Name)).ToList())
        {
            claims.AddRange(await _roleManager.GetClaimsAsync(role));
        }

        return claims;

我应该更改代码中所有使用类似结构的地方,还是有办法让 EF 正常工作?

为什么 ToList() 有效?

  • ToList() 强制执行 select 查询。

为什么不添加 ToList() 就抛出异常?

  • 迭代查询变量时实际执行的查询不强制执行会抛出异常 不是在创建 select 变量时

Updates

3.0 之前的旧版本 EF 无法将表达式转换为 SQL 或参数,它会自动计算客户端中的 LINQ 表达式,但是新版本的 EF 允许在最后一个 select 查询中调用表达式,因此,如果查询的任何其他部分中的表达式无法转换为 SQL 或参数,则它会抛出异常

Source Microsoft Docs, can read more here

因为_roleManager.Roles.Where(a => userRoles.Contains(a.Name) 创建了一个 IEnumerator,它在尝试

时保持连接打开

再做一次await _roleManager.GetClaimsAsync(role)..它看到它已经有一个 open DataReader associated

3.0 可能做了一个内部 evaluates the LINQ expression in client side automatically 有点像自动 Tolist(),而不是保持连接打开的 IEnumerator。

为了避免这种情况,我将对所有未来和当前的代码执行以下操作。 这样没关系,还要确保不执行重新执行整个语句的 IEnumerator 语句。

此外,这还有一个额外的好处,即您可以在循环之前调试列表。

在有人说这会消耗更多内存之前,请告诉我一个测试显示... 这不使用 yield,而且编译器可能足够聪明,可以对它认为不需要的任何东西进行优化。

这里他们对 IEnumerator 问题的表述更好,但同样如此。

Entity Framework upgrade to 6.2.0 from 6.1.x breaks certain queries unless I enable MARS

List<Claim> claims = new List<Claim>();
var userRoles = await _userManager.GetRolesAsync(user);
//you could change this to use async and await
//var roles = await _roleManager.Roles.Where(a => userRoles.Contains(a.Name)).ToListAsync();
var roles = _roleManager.Roles.Where(a => userRoles.Contains(a.Name)).ToList();
foreach (var role in roles)
{
    claims.AddRange(await _roleManager.GetClaimsAsync(role));
}

return claims;