从2.2升级到3.1后,EF Core出现问题。

3

在 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: 已经存在与此命令相关联的打开的DataReader,必须先关闭它。
但是如果我在forEach语句中添加ToList(),它就能正常工作(像这样):
        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与之正常工作?

我读了那个问题,而且从技术上讲,它解决了我的问题。我只是想知道,为什么这在2.2中不是问题,而在3.1中成为了问题。 - Jamil
@IvanStoev 怎么会呢?MARS应该允许这样做。新的管道是否避免了MARS?你在Github上找到了具体的文件吗?只是出于好奇询问。 - Panagiotis Kanavos
如果这是正确的,那么这是一个极其重要的破坏性变化,而且没有在任何地方记录。 - Ian Kemp
@IanKemp,2.2 中的一个重大变更——现在不再是边获取结果边枚举,而是必须在关闭读取器之前缓存所有结果...等等,这个变更是在 EF 6.2 中进行的??? - Panagiotis Kanavos
@PanagiotisKanavos,我猜他们完全将其实体化并在内部进行缓冲。顺便说一句,对于跟踪查询来说,这并不意外,因为它们会被添加到更改跟踪器的内部列表中。我还发现2.x版本没有跟踪查询会抛出运行时异常,尽管与MARS无关。简而言之,这是一团糟 :) - Ivan Stoev
显示剩余7条评论
2个回答

1

为什么 ToList() 起作用?

  • ToList() 强制执行选择查询。

为什么没有添加 ToList() 就会抛出异常?

  • 如果不强制执行,当迭代查询变量时才实际执行查询,而不是创建选择变量时执行查询,因此会抛出异常。

更新

在 EF 3.0 之前的旧版本中,无法将表达式转换为 SQL 或参数,并且它会自动在客户端上评估 LINQ 表达式。然而,新版本的 EF 允许在查询中的最后一个 select 调用中使用表达式,因此,如果查询的任何其他部分中的表达式无法转换为 SQL 或参数,则会引发异常。

来源:Microsoft Docs,可以在这里阅读更多


1
我不明白为什么它在2.2中可以工作,现在却抛出异常。"如果您不强制执行,则会引发异常,因为查询变量迭代时实际执行查询,而不是创建选择变量时执行。" - 我很确定我之前使用了EF Core的延迟执行,并且没有抛出任何异常。 - Jamil
3
真的吗?OP代码正在进行迭代,因此查询正在执行。因此,这个问题与延迟执行无关。 - Ivan Stoev
1
@Saif,这不是关于客户端评估的问题。OP所做的应该可以工作-foreach迭代一个查询结果,并在循环内运行另一个查询。使用MARS(默认设置)应该可以实现这一点。 - Panagiotis Kanavos
我对EF 2.2和EF 3.0之间的确切变化有些模糊,但我发现有选择地实现我的Linq表达式似乎可以解决在我的应用程序中出现的新错误。 - John M Gant
如果使用ToList时仍然失败,该怎么办? - Captain Prinny

1
由于_roleManager.Roles.Where(a => userRoles.Contains(a.Name)创建了一个保持连接开启的IEnumerator,当它再次尝试执行await _roleManager.GetClaimsAsync(role)时,它会发现已经有一个打开的DataReader相关联
3.0可能自动在客户端评估LINQ表达式,类似于自动执行Tolist(),而不是保持连接开启的IEnumerator。
为了避免这种情况,在所有未来和当前代码中都需要这样做。这样做并不重要,还可以确保不要在重新执行整个语句的IEnumerator语句上执行。
此外,这还具有在循环之前调试列表的附加好处。
在有人说这会消耗更多内存之前,请向我展示一个测试结果...这不使用yield,编译器可能足够聪明,对于任何它认为不需要的东西进行优化。
这里他们更好地解释了IEnumerator问题,但本质相同。

从6.1.x升级到6.2.0的Entity Framework会破坏某些查询,除非我启用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;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接