编辑:
好的,根据您的编辑,我认为我对您的要求有错误的想法(现在这更有意义)。我会留下以前的答案,因为我认为它可能对解释很有用,但与您的具体问题的相关性要小得多。
根据您发布的内容,您的用户对象已启用延迟加载。EF 默认启用延迟加载,但是延迟加载有一个要求,即将导航属性标记为虚拟(您已经完成)。
延迟加载通过附加到导航属性上的 get 方法并在该点执行 SQL 查询来检索外部实体来工作。导航属性也不是可查询的集合,这意味着当您执行 get 方法时,您的查询将立即执行。
在上面的示例中,在执行 .first 调用(使用普通的旧 linq 对象发生)之前枚举 User 上的 apples 集合。这意味着 SQL 将返回与用户关联的所有苹果,并在查询机器的内存中过滤它们(如您所见)。这也意味着您需要两个查询来拉下您感兴趣的苹果(一个用于用户,一个用于导航属性),如果您想要的只是苹果,这对您来说可能效率不高。
一个可能更好的方法是尽可能长时间地将整个表达式保留为查询。这方面的一个例子如下:
myDbContext.Users
.Where(u=>u.Id == userId)
.SelectMany(u=>u.Apples)
.Where(a=>a.Id == 1 && !a.Expires.HasValue);
这应该作为单个 SQL 语句执行,并且只拉下您关心的苹果。
高温高压
好的,根据我对您的问题的理解,您在问为什么 EF 似乎允许您在查询中使用导航属性,即使它们在结果集中可能为空。
在回答您的问题时,是的,这是预期的行为,原因如下:
为什么你写一个查询它被翻译成SQL,例如
myDbContext.Apples.Where(a=>a.IsRed)
会变成类似的东西
Select * from Apples
where [IsRed] = 1
类似以下内容也将直接转换为 SQL
myDbContext.Apples.Where(a=>a.Tree.Height > 100)
会变成类似的东西
Select a.* from Apples as a
inner join Tree as t on a.TreeId = t.Id
where t.Height > 100
然而,当我们实际拉下结果集时,情况就有些不同了。
为了避免提取过多数据并使其变慢,EF 提供了几种机制来指定结果集中返回的内容。一是延迟加载(如果您想避免性能问题,需要小心使用),二是包含语法。这些方法限制了我们撤回的内容,以便查询快速且不消耗不需要的资源。
例如,在上面你会注意到只返回 Apple 字段。
如果我们要像下面这样添加一个包含,您可能会得到不同的结果:
myDbContext.Apples.Include(a=>a.Tree).Where(a=>a.Tree.Height > 100)
将转换为类似于以下内容的 SQL:
Select a.*, t.* from Apples as a
inner join Tree as t on a.TreeId = t.Id
where t.Height > 100
在您上面的示例中(我很确定这在语法上不正确,因为 myContext.Users 应该是一个集合,因此不应该有一个 .Apples)您正在创建一个查询,因此所有变量都可用。当您枚举该查询时,您必须明确返回的内容。
有关导航属性及其工作方式(以及 .Include 语法)的更多详细信息,请查看我的博客:http: //blog.staticvoid.co.nz/2012/07/entity-framework-navigation-property.html