代码之家  ›  专栏  ›  技术社区  ›  Jez

如何通过GroupJoin加载集合导航属性?

  •  7
  • Jez  · 技术社区  · 6 年前

    GroupJoin 一些带有 IQueryable 并将数据投影到匿名类型中。我是最初的实体 群组加入 他有一个 ICollection 导航属性(即。one:many). 我想立即加载该属性,这样我就可以在组加入后访问它,而不必返回数据库。我知道 Include() 当你使用 群组加入 ,但下面的代码是我找到的唯一方法,可以使它立即加载集合( ContactRoomRoles ):

    using (var context = new MyDbContext()) {
        var foundRooms = context.Rooms.Include(rm => rm.ContactRoomRoles);
        foundRooms.ToList();  // <-- Required to make EF actually load ContactRoomRoles data!
    
        var roomsData = foundRooms
            .GroupJoin(
                context.Contacts,
                rm => rm.CreatedBy,
                cont => cont.Id,
                (rm, createdBy) => new {
                    ContactRoomRoles = rm.ContactRoomRoles,
                    Room = rm,
                    CreatedBy = createdBy.FirstOrDefault()
                }
            )
            .ToList();
    
        var numberOfRoles1 = roomsData.ElementAt(1).Room.ContactRoomRoles.Count();
        var numberOfRoles2 = roomsData.ElementAt(2).Room.ContactRoomRoles.Count();
        var numberOfRoles3 = roomsData.ElementAt(3).Room.ContactRoomRoles.Count();
    }
    

    如果我移除 foundRooms.ToList() ,EF转到数据库3次以填充我的 numberOfRoles 最后是变量,但是 foundRooms.ToList() 它没有-它只是预先在一个查询中加载数据。

    虽然这是可行的,但感觉像是一个彻头彻尾的黑客。我只是打电话来 .ToList() 对于让EF实际加载采集数据的副作用。如果我把那行注释掉,它会在我试图访问的任何时候进入数据库 联系人室角色 . 有没有一种不那么老套的方法让EF加载导航属性?

    注意:我希望使用navigation属性,而不是将其投影到匿名类型的新属性中,因为AutoMapper希望访问 Room.ContactRoomRoles 当它映射到DTO对象时。

    3 回复  |  直到 6 年前
        1
  •  3
  •   Gert Arnold    6 年前

    所有这些都与标记为已加载或未加载的集合有关。

    线路

    foundRooms.ToList();
    

    foundRooms.Load() )

    全部加载 Room ContactRoomRoles 集合到上下文中。自从 Include 语句,则这些集合被标记为已由EF加载。你可以通过看

    context.Entry(Rooms.Local.First()).Collection(r => r.ContactRoomRoles).IsLoaded
    

    它应该会回来 true

    如果你省略这行 foundRooms.ToList(); ,每次 Room.ContactRoomRoles 集合被访问时,EF会注意到它还没有被标记为已加载,并将延迟加载它。之后,集合被标记为已加载,但需要额外的查询。

    集合只有在被加载时才被标记为已加载-

    • 包括 -埃德
    • 通过延迟加载加载
    • Load() 陈述,如

      context.Entry(Rooms.Local.First()).Collection(r => r.ContactRoomRoles).Load();
      

    ContactRoomRoles = rm.ContactRoomRole 在您的查询中)。

    然而,在声明之后 var roomsData = foundRooms (...).ToList() 房间.联系人房间角色 密集的 ,因为查询确实将它们加载到上下文中,而EF总是执行 进程,自动填充导航属性。

    总之,经过你的询问 roomsData 联系人室角色 但未标记为 加载 .

    知道了这一点,很明显现在唯一要做的就是:防止发生延迟加载。

    实现这一点的最佳方法是防止EF创建能够延迟加载的实体对象,即

    context.Configuration.ProxyCreationEnabled = false;
    

    就在下面 using 声明。

    var numberOfRoles1 = roomsData.ElementAt(1).Room.ContactRoomRoles.Count();
    

        2
  •  5
  •   Roman Pokrovskij Peterdk    6 年前

    这不是黑客。这是一个抽象的漏洞。我们应该准备好使用ORM工具(和任何其他内部DSL)来解决抽象泄漏问题。

    之后 ToList() 您不仅可以执行实际的sql调用(并将数据加载到内存中),还可以交叉使用其他Linq风格——“linqforobjects”。在这之后你所有的电话 Count() 不会仅仅因为开始使用内存中的集合(而不是 这些都是隐藏的 IQueryable -的返回类型 GroupBy List collection-返回ToList的类型)。

    没有 ToList() 您将继续使用“linqforsql”,EF将翻译 计数() 从IQuerybale到sql;Three Conut()call=三条带下划线的Sql语句。

    这是没办法避免的,否则就全算了 count(*) 一个复杂查询中服务器端的值。如果您尝试用Linq(构造 expression tree )-你会再见到你的。ORM工具被设计用来将对象映射到“RDBS实体”并保留CRUD(创建-读取-更新-删除)操作-如果语句变得更复杂-您将无法预见生成的sql(以及所有运行时异常,如“不能为这样的linq生成sql”)。因此,不要将linq用于复杂的“类报告”查询(在某些情况下,您可以-这取决于您的重用需求和测试可能性)。使用老的好SQL,并通过ADO或EF-ADO“SQL扩展”调用它,比如EF-Core FromSql :

    var blogs = context.Blogs
        .FromSql("EXECUTE dbo.GetMostPopularBlogsForUser {0}", user)
        .ToList();
    

    更新:如果您不使用可重用的EF工具,那么最好避免使用延迟加载和手动实体加载。在某种意义上,它们与linq查询-表达式树相反。它们对于实现在“旧”平台上加载引用实体是很重要的(如果不是唯一的)选择,在这些平台上,语言中没有“表达式树”,但在.NET/EF中,完整的查询可以“声明式”编写为表达式树,而无需执行(但有延迟的解释),应该有非常充分的理由返回到“手动”加载。

        3
  •  1
  •   Barr J    6 年前

    这被称为 Abstraction Leak

    当你打电话给 .ToList() 你在两个词之间切换(我不喜欢cross这个词) Linq to sql Linq to objects

    我建议你读一读 The Law of Leaky Abstractions 为了更好地理解,单脚解释是相当复杂的。

    它背后的主要思想是,一切都将按计划工作,但当你试图提供一个底层不可靠层的完整抽象时,会比平常慢,但有时,该层会通过抽象泄漏,你会感觉到抽象不能完全保护你的东西。


    编辑以澄清:

    打电话 ToList()

    意思是,例如,从上面的答案:

    var blogs = context.Blogs
        .FromSql("EXECUTE dbo.GetMostPopularBlogsForUser {0}", user)
        .ToList();
    

    换言之,它是在你召唤的那一刻被懒散地执行的 .

    ToList() 调用,C#不执行SQL调用。所以实际上,它不是内存中的操作。

    是的,它将数据作为上下文的一部分放入内存,并在同一上下文中读取。