实体框架慢速过滤关联实体

时间:2014-04-03 08:13:37

标签: c# entity-framework ria

在实体上填充已过滤的子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应用程序中发现了类似的行为并且可以建议解决方法?

2 个答案:

答案 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())