LINQ-SQL重用 - CompiledQuery.Compile

时间:2011-06-25 17:56:22

标签: linq-to-sql

我一直在玩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语句的理想方式,但它显示了表达式树的生成方式。

3 个答案:

答案 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时,前两个表达式会生成查询,这就是您看到多个表达式的原因。

如果您的查询没有更改,只有DataContextParameters,并且您打算重复使用,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.