我一直在玩LINQ-SQL,试图获得可重用的表达式块,我可以热插入其他查询。所以,我从这样的事情开始:
Func<TaskFile, double> TimeSpent = (t =>
t.TimeEntries.Sum(te => (te.DateEnded - te.DateStarted).TotalHours));
然后,我们可以在LINQ查询中使用上面的内容,如下面所示(LINQPad示例):
TaskFiles.Select(t => new {
t.TaskId,
TimeSpent = TimeSpent(t),
})
这会产生预期的输出,除了为插入的表达式生成每行的查询。这在LINQPad中可见。不好。
无论如何,我注意到了CompiledQuery.Compile
方法。虽然这需要DataContext
作为参数,但我认为我会包含忽略它,并尝试使用相同的Func
。所以我最终得到了以下内容:
static Func<UserQuery, TaskFile, double> TimeSpent =
CompiledQuery.Compile<UserQuery, TaskFile, double>(
(UserQuery db, TaskFile t) =>
t.TimeEntries.Sum(te => (te.DateEnded - te.DateStarted).TotalHours));
请注意,我没有使用db
参数。但是,现在当我们使用此更新参数时,仅生成 1 SQL查询。 Expression已成功转换为SQL并包含在原始查询中。
所以我的最终问题是,是什么让CompiledQuery.Compile
如此特别?似乎根本不需要DataContext
参数,此时我认为它更像是生成完整查询的便利参数。
使用这样的CompiledQuery.Compile
方法会被认为是个好主意吗?这似乎是一个大黑客,但它似乎是LINQ重用的唯一可行途径。
更新
使用Func
声明中的第一个Where
,我们会看到以下异常,如下所示:
NotSupportedException: Method 'System.Object DynamicInvoke(System.Object[])' has no supported translation to SQL.
如下所示:
.Where(t => TimeSpent(t) > 2)
但是,当我们使用Func
生成的CompiledQuery.Compile
时,查询成功执行并生成正确的SQL。
我知道这不是重用Where
语句的理想方式,但它显示了表达式树的生成方式。
答案 0 :(得分:3)
执行摘要:
Expression.Compile
生成一个CLR方法,而CompiledQuery.Compile
生成一个作为SQL占位符的委托。
到目前为止,您没有得到正确答案的原因之一是示例代码中的某些内容不正确。没有数据库或通用样本,其他人可以玩机会进一步减少(我知道很难提供,但通常是值得的。)
关于事实:
Expression<Func<TaskFile, double>> TimeSpent = (t => t.TimeEntries.Sum(te => (te.DateEnded - te.DateStarted).TotalHours));
然后,我们可以在LINQ查询中使用上面的内容,如下所示:
TaskFiles.Select(t => new { t.TaskId, TimeSpent = TimeSpent(t), })
(注意:也许你在TimeSpent中使用了Func<>
类型。这会产生与下面段落中概述的场景相同的情况。尽管如此,请务必阅读并理解它。
不,这不会编译。无法调用表达式(TimeSpent
是表达式)。他们需要先编译成代理。调用Expression.Compile()
时发生的事情是表达式树被编译成IL,然后注入到DynamicMethod
中,然后你会得到一个委托。
以下方法可行:
var q = TaskFiles.Select(t => new {
t.TaskId,
TimeSpent = TimeSpent.Compile().DynamicInvoke()
});
这会产生预期的输出,但每行查询除外 为插入的表达式生成。这在LINQPad中可见。 不好。
为什么会这样?好吧,Linq To Sql将需要获取所有TaskFiles
,脱水TaskFile
个实例,然后在内存中运行你的选择器 。您可以获得每个TaskFile的查询,因为它们包含一个或多个1:m映射。
虽然LTS允许在内存中进行选择投射,但对于Wheres却没有这样做(需要引用,这是我所知的最佳信息)。当你考虑它时,这是完全合理的:你可能会通过过滤内存中的整个数据库来传输更多数据,然后通过在内存中转换它的一个子集来传输更多数据。 (虽然它会在您看到时创建查询性能问题,但在使用ORM时需要注意一些事项)。
CompiledQuery.Compile()
做了不同的事情。它将查询编译为SQL,它返回的委托只是Linq to SQL将在内部使用的占位符。您不能在CLR中“调用”此方法,它只能用作另一个表达式树中的节点。
那么为什么LTS使用CompiledQuery.Compile
'd表达式生成有效查询呢?因为它知道这个表达式节点的作用,因为它知道它背后的SQL。在Expression.Compile
的情况下,正如我之前解释的那样,InvokeExpression
仅调用DynamicMethod
。
为什么需要DataContext参数?是的,创建完整查询更方便,但这也是因为表达式树编译器需要知道用于生成SQL的映射。如果没有这个参数,找到这个映射会很痛苦,所以这是一个非常合理的要求。
答案 1 :(得分:1)
我很惊讶为什么到目前为止你还没有答案。 CompiledQuery.Compile
编译并缓存查询。这就是为什么您只看到生成一个查询的原因。
这不仅仅是一个黑客,这是推荐的方式!
查看这些MSDN文章以获取详细信息和示例:
Compiled Queries (LINQ to Entities)
How to: Store and Reuse Queries (LINQ to SQL)
更新:(超出评论限制)
我做了一些挖掘反射器和放大器我确实看到正在使用DataContext。在您的示例中,您根本就不使用它。
话虽如此,两者之间的主要区别在于前者创建了一个委托(用于表达式树),后者创建了一个缓存的SQL并实际返回一个函数(排序)。当您在它们上面调用Invoke
时,前两个表达式会生成查询,这就是您看到多个表达式的原因。
如果您的查询没有更改,只有DataContext
和Parameters
,并且您打算重复使用,CompiledQuery.Compile
会有所帮助。编译是昂贵的,因此对于一次性查询,没有任何好处。
答案 2 :(得分:0)
TaskFiles.Select(t => new {
t.TaskId,
TimeSpent = TimeSpent(t),
})
这不是LinqToSql查询,因为没有DataContext实例。很可能你正在查询一些没有实现IQueryable的EntitySet。
请发布完整的陈述,而不是陈述片段。 (我看到无效的逗号,没有分号,没有分配)。
另外,试试这个:
var query = myDataContext.TaskFiles
.Where(tf => tf.Parent.Key == myParent.Key)
.Select(t => new {
t.TaskId,
TimeSpent = TimeSpent(t)
});
// where myParent is the source of the EntitySet and Parent is a relational property.
// and Key is the primary key property of Parent.