手动将现有对象分配给Entity Framework导航属性

时间:2015-12-30 11:46:05

标签: c# entity-framework

是否可以手动将现有对象分配给Entity Framework(db first)对象的导航属性?

问题的上下文是我在尝试带回一个(经过严格过滤的)对象列表时遇到问题,其中所有子项和后代都已连接,以便在处理上下文后内存中可以使用完整的图形。

我尝试通过.Include()语句使用以下内容执行此操作:

  using (var ctx = new MyEntities())
  {
    myParents = ctx.Parents
        .Where(p => MyFilter(p))
        .Include(p => p.Children)
        .Include(p => p.Children.Select(c=>c.Grandchildren))
        .Include(p => p.Children.Select(c=>c.Grandchildren.Select(g=>g.GreatGrandChildren)));

  }

但生成的查询运行速度太慢,因为使用嵌套的include语句存在已知的性能问题(如许多地方所述,包括this blog)。

我可以在没有表现问题的情况下撤回父母,子女和孙子女 - 当我包括最后一个.Include()对伟大的孩子的陈述时,我只会遇到麻烦。

通过从已经检索到的GrandChildren构建GrandChildrenId列表并执行以下操作,我可以通过第二个单独的查询轻松地从数据库中获取GreatGrandChildren对象:

greatGrandKids = ctx.GreatGrandChildren.Where(g=>ids.Contains(g.GrandChildId)).ToList();

但是现在,一旦我处理了上下文,我就不能做grandChildA.GreatGrandChildren之类的事情而不会遇到对象上下文处理异常。

我可以拥有多达几千个GrandChildren对象,所以我真的想避免往返数据库以获取GreatGrandChildren,因为每个GrandChrandChildren在每个GrandChild对象上排除使用.Load(),对吧?

我可以通过在我的后续代码中每次需要它时从greatGrandKids查找所需的伟大的孩子,或者甚至通过添加新的(非映射的)属性(例如{{1})来解决这个问题。 } .GreatGrandChildrenLocal课程并将它们全部分配到前面,但这些都感觉非常 kludgy&丑陋。我更愿意找到一种方法来访问每个GrandChild对象上的现有.GreatGrandChildren导航属性。

尝试使用类似这样的东西分配给导航属性:

GrandChild
当我尝试访问grandchild.GreatGrandChildren = greatGrandKids .Where(g=>g.GrandChildId == grandChild.Id) .ToList(); (仍然给对象处置异常)时,

也失败了。

所以我的问题是:

有没有办法可以将我已经从数据库中检索到的现有GreatGrandChdildren对象分配给GrandChild对象上的grandchild.GreatGrandChildren导航属性,以便使它们可用(仅用于处理上下文后的读取操作?

(或者确实存在不同的解决方案?)

1 个答案:

答案 0 :(得分:0)

如果您使用以下命令禁用代理创建:

ctx.Configuration.ProxyCreationEnabled = false;

然后从/向导航属性读取和写入完全按预期工作,而不是试图懒惰加载实体并抛出对象处置异常。

所以我们有类似的东西:

using (var ctx = new MyEntities())
{
    myParents = ctx.Parents
       .Where(p => MyFilter(p))
       .Include(p => p.Children)
       .Include(p => p.Children.Select(c=>c.Grandchildren));
       //skip the final GreatGrandChildren include statement

    //get the associated grandchildren & their ids:
    var grandKids = myParents.SelectMany(p=>p.Children)
        .SelectMany(c=>c.Grandchildren)
        .ToList();
    var ids = grandKids.Select(g=>g.Id)).ToList();

    //Get the great grandkids:
    var greatGrandKids = ctx.GreatGrandChildren
        .Where(g=>ids.Contains(g.GrandChildId)).ToList();

    //Assign the greatgrandchildren to the grandchildren:
    foreach (grandChild in grandKids)
    {
        grandChild.GreatGrandChildren = greatGrandKids
            .Where(g=>g.GrandChildId == grandChild.Id)
            .ToList();
    }

 }

现在我们可以访问上下文之外的.GreatGrandChildren属性,而不会遇到上下文处理异常。虽然这仍然有点混乱,但它比使用原始Include()语句或在每个GrandChild上调用.Load()便宜得多。

N.B。由于这些对象仅用于读取操作,并且我不需要延迟加载,因此在我的环境中关闭代理创建没有任何负面影响。如果还需要写操作和/或延迟加载,那么我们还需要考虑为给定的EF上下文关闭它的含义。