繁体   English   中英

已经有一个打开的 DataReader 与此命令关联,没有 ToList()

[英]There is already an open DataReader associated with this Command without ToList()

我有以下方法从导航属性加载相关数据。 但是,它会生成错误。 我可以通过添加ToList()ToArray()来消除错误,但出于性能原因我不想这样做。 我也无法在我的 web.config 文件中设置MARS属性,因为它会导致其他连接类出现问题。

如何在不使用扩展方法或编辑我的web.config情况下解决这个问题?

public override void LoadDependent(IEnumerable<Ques> data)
{
    base.LoadDependent(data);

    if (data.Any())
    {
        foreach (Ques qst in data)
        {
            if (qst?.Id_user != null)
            {
                db.Entry(qst).Reference(q => q.AspNetUsers).Load();
            }
        }
    }
}

我从这个问题中得出你有这样的情况:

// (outside code)
var query = db.SomeEntity.Wnere(x => x.SomeCondition == someCondition);
LoadDependent(query);

机会基于此方法,它可能是构建搜索表达式等的各种方法的调用堆栈,但最终传递给LoadDependent()IQueryable<TEntity>

相反,如果你打电话:

// (outside code)
var query = db.SomeEntity.Wnere(x => x.SomeCondition == someCondition);
var data = query.ToList();
LoadDependent(data);

或者..在你的 LoadDependent 改变做这样的事情:

base.LoadDependent(data);
data = data.ToList();

或更好,

foreach (Ques qst in data.ToList())

然后您的LoadDependent()调用起作用,但在第一个示例中,您收到一个错误,指出 DataReader 已打开。 这是因为您的foreach调用将迭代IQueryable意味着 EF 的数据读取器将保持打开状态,因此无法进一步调用db ,我假设它是注入的 DbContext 的模块级变量。

替换这个:

db.Entry(qst).Reference(q => q.AspNetUsers).Load();

有了这个:

db.Entry(qst).Reference(q => q.AspNetUsers).LoadAsync();

...实际上不起作用。 这只是异步委托加载调用,如果不等待它,它也会失败,只是不会在延续线程上引发异常。

正如对您的问题的评论中所述,这是处理加载引用的一个非常糟糕的设计选择。 如果您不打算使用预先加载或投影正确实现初始获取,那么启用延迟加载并在实际需要引用时选择 n+1 命中要好得多。

像这样的代码在整个代码中强制使用 Select n+1 模式。

加载“Ques”的一个很好的例子,它与用户预加载相关联:

var ques = db.Ques
    .Include(x => x.AspNetUsers)
    .Where(x => x.SomeCondition == someCondition)
    .ToList();

无论“SomeCondition”导致返回 1 个查询还是返回 1000 个查询,数据都将执行一次对数据库的查询。

Select n+1 方案是不好的,因为在调用获取依赖项返回 1000 个 Ques 的情况下,您会得到:

var ques = db.Ques
    .Where(x => x.SomeCondition == someCondition)
    .ToList(); // 1 query.

foreach(var q in ques)
     db.Entry(q).Reference(x => x.AspNetUsers).Load(); // 1 query x 1000

运行 1001 个查询。 这与您要加载的每个参考相结合。

然后看起来有问题,后来的代码可能想要提供分页,例如只取 25 个项目,而总记录数可能会在 10 或更多的情况下运行。 这就是延迟加载将有两个选择中较小的N + 1个丑恶现象,与延迟加载,你知道,如果任何返回的疑问句实际引用它,而只对那些实际引用它疑问句AspNetUsers只会选择。 因此,如果分页仅“触及”了 25 行,则延迟加载将导致 26 次查询。 然而,延迟加载是一个陷阱,因为稍后的代码更改可能会无意中导致性能问题出现在看似无关的区域,因为新引用或代码更改导致更多引用被“触及”并启动查询。

如果您打算采用LoadDependent()类型方法,那么您需要确保尽可能晚地调用它,一旦您知道要加载的设置大小,因为您将需要具体化集合以加载具有相同DbContext 实例。 (即分页后)尝试使用分离的实例( AsNoTracking() )或使用全新的 DbContext 实例来解决它可能会给您带来一些进展,但稍后总会导致更多问题,因为您将混合跟踪和未跟踪实体,或者更糟的是,由不同 DbContext 跟踪的实体取决于这些加载的实体的使用方式。

另一种团队追求的是 IncludeReference() 类型方法而不是 LoadReference() 类型方法。 这里的目标是将.Include语句构建到IQueryable 这可以通过两种方式完成,通过魔术字符串(属性名称)或通过传入要包含的引用的表达式。 同样,在处理更深层嵌套的引用时,这可能会变成一个兔子洞。 (即构建.Include().ThenInclude()链。)这通过.Include().ThenInclude()加载所需的相关数据来避免 Select n+1 问题。

我已经通过删除方法LoadDepend解决了这个问题,并且我在我的第一个数据查询中使用了Include()来显示导航属性中的参考数据

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM