已经有一个打开的 DataReader 与这个没有 ToList() 的命令相关联
There is already an open DataReader associated with this Command without ToList()
我有下面的方法从导航加载相关数据 属性。但是,它会产生错误。我可以通过添加 ToList()
或 ToArray()
来消除错误,但出于性能原因我宁愿不这样做。我也无法在我的 web.config 文件中设置 MARS
属性,因为它会导致连接的其他 类 出现问题。
如何在不使用扩展方法或编辑我的 web.config
的情况下解决这个问题?
public override void Load(IEnumerable<Ques> data)
{
if (data.Any())
{
foreach (var pstuu in data)
{
if (pstuu?.Id_user != null)
{
db.Entry(pstuu).Reference(q => q.Users).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 的数据 reader 将保持打开状态,因此进一步调用 db
,我假设是注入的 DbContext 的模块级变量,无法创建。
替换这个:
db.Entry(qst).Reference(q => q.AspNetUsers).Load();
有了这个:
db.Entry(qst).Reference(q => q.AspNetUsers).LoadAsync();
...实际上不起作用。这只是异步委托加载调用,如果不等待它,它也会失败,只是不会在延续线程上引发异常。
如您问题的评论中所述,这是处理加载引用的非常糟糕的设计选择。如果您不打算通过预加载或投影正确地实现初始提取,那么启用延迟加载并进行 Select n+1 命中 if/when 确实需要一个参考,这要好得多.
像这样的代码强制整个代码中的Select n+1 模式。
一个加载“问题”及其关联的用户急切加载的好例子:
var ques = db.Ques
.Include(x => x.AspNetUsers)
.Where(x => x.SomeCondition == someCondition)
.ToList();
无论“SomeCondition”导致返回 1 个 Ques 还是返回 1000 个 Ques,数据将执行一次对 DB 的查询。
Select n+1 种情况很糟糕,因为在调用获取依赖项时返回 1000 个问题的情况下,您会得到:
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 的数千或更多。这是延迟加载将是两个 Select n+1 邪恶中较小者的地方,就像延迟加载一样,您知道 AspNetUsers 只会被选择 if 任何返回的问题实际上引用了它,并且仅适用于实际引用它的那些问题。因此,如果分页仅“触及”25 行,延迟加载将导致 26 次查询。然而,延迟加载是一个陷阱,因为后来的代码更改可能会无意中导致性能问题出现在看似不相关的区域,因为新的引用或代码更改会导致更多的引用被“触及”并启动查询。
如果您打算使用 LoadDependent()
类型的方法,那么您需要确保它被调用的时间越晚越好,一旦您知道要加载的集合大小,因为您将需要具体化集合加载具有相同 DbContext 实例的相关实体。 (即分页后)尝试使用分离实例(AsNoTracking()
)或使用全新的 DbContext 实例来解决它可能会给你带来一些进展,但以后总会导致更多问题,因为你将混合跟踪一个未跟踪的实体,或者更糟的是,由不同的 DbContext 跟踪的实体,具体取决于这些加载的实体的使用方式。
替代团队追求的是 IncludeReference() 类型方法,而不是 LoadReference() 类型方法。这里的目标是将 .Include
语句构建到 IQueryable
中。这可以通过两种方式完成,要么通过魔术字符串(属性 名称),要么通过传递要包含的引用的表达式。同样,在处理嵌套更深的引用时,这可能会变成一个小问题。 (即构建 .Include().ThenInclude()
链。)这通过预先加载所需的相关数据避免了 Select n+1 问题。
我已经通过删除方法 Load
解决了问题,并且我在第一次查询数据时使用 Include()
在导航中显示参考数据 属性
我有下面的方法从导航加载相关数据 属性。但是,它会产生错误。我可以通过添加 ToList()
或 ToArray()
来消除错误,但出于性能原因我宁愿不这样做。我也无法在我的 web.config 文件中设置 MARS
属性,因为它会导致连接的其他 类 出现问题。
如何在不使用扩展方法或编辑我的 web.config
的情况下解决这个问题?
public override void Load(IEnumerable<Ques> data)
{
if (data.Any())
{
foreach (var pstuu in data)
{
if (pstuu?.Id_user != null)
{
db.Entry(pstuu).Reference(q => q.Users).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 的数据 reader 将保持打开状态,因此进一步调用 db
,我假设是注入的 DbContext 的模块级变量,无法创建。
替换这个:
db.Entry(qst).Reference(q => q.AspNetUsers).Load();
有了这个:
db.Entry(qst).Reference(q => q.AspNetUsers).LoadAsync();
...实际上不起作用。这只是异步委托加载调用,如果不等待它,它也会失败,只是不会在延续线程上引发异常。
如您问题的评论中所述,这是处理加载引用的非常糟糕的设计选择。如果您不打算通过预加载或投影正确地实现初始提取,那么启用延迟加载并进行 Select n+1 命中 if/when 确实需要一个参考,这要好得多.
像这样的代码强制整个代码中的Select n+1 模式。
一个加载“问题”及其关联的用户急切加载的好例子:
var ques = db.Ques
.Include(x => x.AspNetUsers)
.Where(x => x.SomeCondition == someCondition)
.ToList();
无论“SomeCondition”导致返回 1 个 Ques 还是返回 1000 个 Ques,数据将执行一次对 DB 的查询。
Select n+1 种情况很糟糕,因为在调用获取依赖项时返回 1000 个问题的情况下,您会得到:
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 的数千或更多。这是延迟加载将是两个 Select n+1 邪恶中较小者的地方,就像延迟加载一样,您知道 AspNetUsers 只会被选择 if 任何返回的问题实际上引用了它,并且仅适用于实际引用它的那些问题。因此,如果分页仅“触及”25 行,延迟加载将导致 26 次查询。然而,延迟加载是一个陷阱,因为后来的代码更改可能会无意中导致性能问题出现在看似不相关的区域,因为新的引用或代码更改会导致更多的引用被“触及”并启动查询。
如果您打算使用 LoadDependent()
类型的方法,那么您需要确保它被调用的时间越晚越好,一旦您知道要加载的集合大小,因为您将需要具体化集合加载具有相同 DbContext 实例的相关实体。 (即分页后)尝试使用分离实例(AsNoTracking()
)或使用全新的 DbContext 实例来解决它可能会给你带来一些进展,但以后总会导致更多问题,因为你将混合跟踪一个未跟踪的实体,或者更糟的是,由不同的 DbContext 跟踪的实体,具体取决于这些加载的实体的使用方式。
替代团队追求的是 IncludeReference() 类型方法,而不是 LoadReference() 类型方法。这里的目标是将 .Include
语句构建到 IQueryable
中。这可以通过两种方式完成,要么通过魔术字符串(属性 名称),要么通过传递要包含的引用的表达式。同样,在处理嵌套更深的引用时,这可能会变成一个小问题。 (即构建 .Include().ThenInclude()
链。)这通过预先加载所需的相关数据避免了 Select n+1 问题。
我已经通过删除方法 Load
解决了问题,并且我在第一次查询数据时使用 Include()
在导航中显示参考数据 属性