![](/img/trans.png)
[英]How to include multiple navigation properties of navigation property in EF6 in ASP.NET MVC with eager loading?
[英]How to make EF eager load a collection navigation property through a GroupJoin?
我正在尝试使用IQueryable
GroupJoin
一些数据并将数据投影到匿名类型。 我是GroupJoin
的原始实体具有ICollection
导航属性(即one:many)。 我想急切加载该属性,以便我可以在组加入后访问它而不用EF返回到数据库。 我知道当你使用GroupJoin
时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实际加载集合数据的副作用。 如果我评论该行,它会在我尝试访问ContactRoomRoles
时进入数据库。 是否有一种不太常见的方式使EF eager加载导航属性?
注意:我想使用导航属性而不是将其投影到匿名类型的新属性中,因为AutoMapper在映射到DTO对象时想要访问Room.ContactRoomRoles
。
这不是一个黑客。 这是一个抽象泄漏。 我们应该准备好使用ORM工具(以及任何其他内部DSL)来解决抽象泄漏问题。
在ToList()
您不仅要执行实际的sql调用(并将数据加载到内存中),还要交叉到其他Linq风格 - “Linq for objects”。 在此之后,所有对Count()
调用都不会因为你开始使用内存集合而生成sql(不是表达式树被IQueryable
隐藏 - GroupBy
语句的返回类型,但是使用List
集合 - 返回类型ToList )。
如果没有ToList()
你将继续使用“Linq for sql”,EF会将IQuerybale上Count()
每次调用转换为sql; 三个Conut()调用=三个带下划线的Sql语句。
没有办法避免这种情况,否则在一个复杂查询中计算服务器端的所有count(*)
值。 如果您尝试使用Linq编写此类查询(构建expression tree
) - 您将再次遇到抽象泄漏。 ORM工具旨在将对象映射到“RDBS实体”,保持CRUD(创建读取更新删除)操作 - 如果语句变得更复杂 - 您将无法预见生成的sql(以及所有运行时异常,如'无法生成sql对于这样的linq')。 因此,不要将linq用于复杂的“报告”,例如“查询”(在某些情况下,您可以 - 这取决于您的重复使用要求和测试可能性)。 使用旧的好SQL并通过ADO或EF ADO“sql扩展”调用它,如EF Core FromSql
:
var blogs = context.Blogs
.FromSql("EXECUTE dbo.GetMostPopularBlogsForUser {0}", user)
.ToList();
更新:如果您不使用可重复使用的EF工具,建议您避免使用延迟加载和手动实体加载。 它们在某种意义上与linq查询相反 - 表达式树。 它们是重要的(如果不是只有一个)选项,用于在“旧”平台上实现引用实体加载,其中语言中没有“表达式树”,但在.NET / EF中,完整查询可以“声明方式”写为表达式树而不执行(但推迟解释)应该有非常强烈的理由返回“手动”加载。
这些都是关于标记为已加载或未加载的集合。
这条线
foundRooms.ToList();
(或foundRooms.Load()
)
将所有Room
及其ContactRoomRoles
集合加载到上下文中。 由于使用了Include
语句,因此这些集合被标记为由EF加载。 您可以通过查看来检查
context.Entry(Rooms.Local.First()).Collection(r => r.ContactRoomRoles).IsLoaded
哪个应该返回true
。
如果省略foundRooms.ToList();
,每次访问Room.ContactRoomRoles
集合时,EF都会注意到它尚未标记为已加载,并且会延迟加载它。 之后,集合被标记为已加载,但需要额外的查询。
集合仅在标记为已加载时 -
Include
-ed 由Load()
语句Load()
,如
context.Entry(Rooms.Local.First()).Collection(r => r.ContactRoomRoles).Load();
不是它是投影到另一个属性的一部分(如查询中的ContactRoomRoles = rm.ContactRoomRole
部分)。
但是,在语句var roomsData = foundRooms (...).ToList()
所有Room.ContactRoomRoles
都被填充 ,因为查询确实将它们加载到上下文中,并且EF始终执行关系修复过程,该过程自动填充导航属性。
因此,总而言之,在查询之后,您的roomsData
包含具有ContactRoomRoles
集合的房间对象,这些集合已填充但未标记为已加载 。
知道了这一点,现在很明显,唯一要做的就是:防止延迟加载发生。
实现这一目标的最佳方法是防止EF创建能够延迟加载的实体对象,即代理 。 你可以通过添加线来实现
context.Configuration.ProxyCreationEnabled = false;
就在using
声明的下方。
现在你会注意到这一行
var numberOfRoles1 = roomsData.ElementAt(1).Room.ContactRoomRoles.Count();
不会触发额外的查询,但会返回正确的计数。
这称为抽象泄漏 ,它意味着您的抽象暴露了一些实现细节。
当你调用.ToList()
并在Linq到sql和Linq到对象之间切换(我不喜欢单词cross)时会发生这种情况。
我建议你阅读“漏洞抽象法则”以便更好地掌握,因为单脚解释起来相当复杂。
它背后的主要思想是,一切都会按计划运行,但是当你试图提供底层不可靠层的完整抽象时,它会慢一些,但有时,层会通过抽象泄漏,你会感觉到抽象可以做的事情。非常保护你免受伤害。
编辑澄清:
调用ToList()
强制linq-to-entities评估并将结果作为列表返回。
意思是,例如从上面的答案:
var blogs = context.Blogs
.FromSql("EXECUTE dbo.GetMostPopularBlogsForUser {0}", user)
.ToList();
将评估相应的上下文模型 - 博客模型。
换句话说,在你调用ToList()
的那一刻,它就被懒惰地执行了。
在ToList()
调用之前,C#不进行SQL调用。 实际上,它不是内存操作。
所以是的,它将这些数据作为上下文的一部分放入内存中,并在相同的上下文中读取它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.