请考虑以下事项:
public class ItemSmall
{
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
public string D { get; set; }
public string E { get; set; }
}
public class ItemSmaller
{
public string A { get; set; }
public string B { get; set; }
}
假设我有一种方法将数据投影到第一类:
IQueryable<ItemSmall> Function1() {
return Context.Items.Select(i => new ItemSmall { A = i.A, B = i.B, C = i.RelatedTable.FirstOrDefautl(), D = i.RelatedTable.FirstOrDefautl(), E = i.E });
}
如果我然后实例化这个查询它可以很好地工作 - 只查询A到E列。我很高兴。
现在考虑下一个功能:
IQueryable<ItemSmaller> Function2() {
return Function1().Select(i => new ItemSmaller { A = i.A, B = i.B });
}
这就是魔术似乎失败的地方。仍然会查询A到D列。我想我有点期望L2S足够聪明,可以省略C到J的列,因为它们没有被投射到最终对象上。
是否有任何方法可以以有效的方式进行菊花链式投影? (我更愿意只编写一次数据库到对象的映射,因为一些预测非常复杂。)
修改
感谢谢尔盖验证上述原始场景完全符合要求。我的实际场景的不同之处在于属性C和D实际上是更复杂的表达式,它对相关的一对多表利用FirstOrDefault
操作。
似乎First
/ FirstOrDefault
/ SingleOrDefault
会导致他们的依赖列被无关紧要地查询。
我已相应更新了这个问题。
答案 0 :(得分:1)
根据Classification of Standard Query Operators by Manner of Execution FirstOrDefault()
运算符立即执行,即您在编写FirstOrDefault()
时强制执行数据库查询。这就是您看到所有列都被下载的原因。进一步的查询发生在内存中。
答案 1 :(得分:0)
我的理解是,在第二种情况下,你仍然会投射到较大的类中,然后投射到较小的类中,因此需要来自DB的所有字段。
实际上,如果你要做的是缓存第一个查询的结果,那么你可能想要在Function1返回值上调用ToList(),并在Function2中使用该List。这样,DB只会被查询一次。
希望这有帮助。
编辑:在阅读谢尔盖的评论之后,我也在LinqPad中测试过,并且还运行了SQL Profiler,看起来我错了,并且实际上只在DB上查询了较小的字段集。你能解释一下你是如何得出所有字段都被查询的结论吗?答案 2 :(得分:0)
IQueryable<ItemSmall> Function1() {
return Context.Items.Select(i => new ItemSmall { A = i.A, B = i.B, C = i.RelatedTable.FirstOrDefautl(), D = i.RelatedTable.FirstOrDefautl(), E = i.E });
}
通过调用i.RelatedTable.FirstOrDefault()或者喜欢;您已明确指示框架查询RelatedTable的第一行(如果有),因为它们是急切加载的。
在你的情况下,你会想要一个懒惰的想法,尝试使用Take(1),但这意味着你要重新模拟你的类,它看起来有点hacky。