在实体上填充已过滤的子EntityCollection时,EntityFramework似乎非常慢 -
我们使用带有Silverlight客户端的C#编写的数据库优先实体框架运行RIA / WCF Web App。我们收集了大约70,000' Eval'实体和类似数量的InCo'实体。每个Eval都有一个包含最多2个InCo实体的子集合。这个关系是一对多,因此Eval可以有多个InCos,但InCo与一个Eval绑定。从数据库加载实体集合后,我循环遍历Evals,如此 -
foreach(Eval eval in Evals)
if (eval.InCos.Count > 0)
// Do something
这需要很长时间(分钟)。逐步执行代码,我发现在Eval实体上生成函数 FilterInCos ,以确定InCo是否与它有关联(在生成的.Web.g.cs文件中),这是传递给Eval的InCos EntityCollection的构造函数,并在第一次引用其中一个属性时调用以填充该集合
private bool FilterInCos(InCo entity)
{
return (entity.EvalID == this.EvalID);
}
从我所看到的,InCos集合中的每个InCo为每个Eval调用FilterInCos()函数 - 对70,000个Evals中的每一个进行70,000次调用,导致大约50亿次迭代循环通过Evals并在每个上查询InCos集合。由于Eval:InCo数据库关系为1:*,应该可以遍历InCo对象,使用匹配的EvalID检索Eval,并将InCo添加到该Eval的InCos集合中--70,000次迭代。然而,在我看来,它似乎并不是一种规避生成逻辑的方法。我们也不能将EntityCollection分配给Eval的InCos属性,因为它是只读的。
这里是EF edmx文件的相关数据库关系 -
<Association Name="FK_InCo_Eval">
<End Role="Eval" Type="AspireEntityModel.Store.Eval" Multiplicity="1">
<OnDelete Action="Cascade" />
</End>
<End Role="InCo" Type="AspireEntityModel.Store.InCo" Multiplicity="*" />
<ReferentialConstraint>
<Principal Role="Eval">
<PropertyRef Name="EvalID" />
</Principal>
<Dependent Role="InCo">
<PropertyRef Name="EvalID" />
</Dependent>
</ReferentialConstraint>
</Association>
这里是使用生成的构造函数的Eval客户端实体(来自生成的.Web.g.cs)的InCos属性
/// <summary>
/// Gets the collection of associated <see cref="InCo"/> entity instances.
/// </summary>
[Association("Eval_InCo", "EvalID", "EvalID")]
[XmlIgnore()]
public EntityCollection<InCo> InCos
{
get
{
if ((this._InCos == null))
{
this._InCos = new EntityCollection<InCo>(this, "InCos", this.FilterInCos, this.AttachInCos, this.DetachInCos);
}
return this._InCos;
}
}
是否有人在数据库优先的EF应用程序中发现了类似的行为并且可以建议解决方法?
答案 0 :(得分:0)
一旦生产数据库开始获得数百万条记录,我就发现了一个非常类似的问题。长话短说: -
首先)除非我正在编写查询,否则我总是使用.ToList()强制在任何foreach()循环之前单次往返数据库。
第二)如果我可以在第一个查询中收集数据,我从不在循环中使用导航属性。
第三)我尝试获取两个单独的列表(使用.ToList)Eval和一个InCos,然后执行Linq.FirstOrDefault()将它们连接到内存中,而不是得到非常宽的结果,但是我必须考虑内存开销和数据传输时间。
在你的情况下,我会修改用于收集Evals的选项: -
var Evals = from Ev in db.Evals
select new {
Ev,
Cnt = Ev.Incos.Count()
}
foreach(var Eval in Evals)
if (Eval.Cnt > 0)
.......
and access the Eval as Eval.Ev
答案 1 :(得分:0)
首先,您可以通过在查询本身中对其进行过滤来提高查询效率:
foreach(Eval eval in Evals.Where(e => e.InCos.Any())
这会将所有过滤移动到服务器,因此不需要为每个Eval
记录添加额外的昂贵查询。
如果你需要对InCos
做些什么,你应该急切地加载它们:
foreach(Eval eval in Evals.Expand("InCos").Where(e => e.InCos.Any())