我一直在玩LINQ-SQL,试图获得可重用的表达式块,我可以热插入其他查询.所以,我从这样的事情开始:
Func<TaskFile, double> TimeSpent = (t =>
t.TimeEntries.Sum(te => (te.DateEnded - te.DateStarted).TotalHours));
Run Code Online (Sandbox Code Playgroud)
然后,我们可以在下面的LINQ查询中使用上面的内容(LINQPad示例):
TaskFiles.Select(t => new {
t.TaskId,
TimeSpent = TimeSpent(t),
})
Run Code Online (Sandbox Code Playgroud)
这会产生预期的输出,除了为插入的表达式生成每行的查询.这在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));
Run Code Online (Sandbox Code Playgroud)
请注意,我没有使用db
参数.但是,现在当我们使用此更新参数时,仅生成1个 SQL查询.Expression已成功转换为SQL并包含在原始查询中.
所以我的最终问题是,是什么让人CompiledQuery.Compile
如此特别?似乎DataContext
根本不需要该参数,此时我认为它更像是生成完整查询的便利参数.
使用这样的CompiledQuery.Compile
方法会被认为是个好主意吗?这似乎是一个大黑客,但它似乎是LINQ重用的唯一可行途径.
UPDATE
使用陈述中的第Func
一个Where
,我们会看到以下异常,如下所示:
NotSupportedException: Method 'System.Object DynamicInvoke(System.Object[])' has no supported translation to SQL.
Run Code Online (Sandbox Code Playgroud)
如下:
.Where(t => TimeSpent(t) > 2)
Run Code Online (Sandbox Code Playgroud)
但是,当我们使用Func
生成的by时CompiledQuery.Compile
,查询成功执行并生成正确的SQL.
我知道这不是重用Where
语句的理想方式,但它显示了表达式树的生成方式.
执行摘要:
Expression.Compile
生成一个 CLR 方法,而CompiledQuery.Compile
生成一个作为 SQL 占位符的委托。
直到现在您还没有得到正确答案的原因之一是示例代码中的某些内容不正确。如果没有数据库或通用样本,其他人可以使用的机会就会进一步减少(我知道很难提供这一点,但通常是值得的)。
就事实而言:
Run Code Online (Sandbox Code Playgroud)Expression<Func<TaskFile, double>> TimeSpent = (t => t.TimeEntries.Sum(te => (te.DateEnded - te.DateStarted).TotalHours));
然后,我们可以在 LINQ 查询中使用上面的内容,如下所示:
Run Code Online (Sandbox Code Playgroud)TaskFiles.Select(t => new { t.TaskId, TimeSpent = TimeSpent(t), })
(注意:也许您使用了Func<>
TimeSpent 的类型。这会产生与您的场景相同的情况,如下面段落中概述的那样。但请务必阅读并理解它)。
不,这不会编译。无法调用表达式(TimeSpent
是表达式)。首先需要将它们编译为委托。当您调用时,底层发生的事情Expression.Compile()
是表达式树被编译为 IL,然后注入到 中DynamicMethod
,然后您将获得一个委托。
以下内容将起作用:
var q = TaskFiles.Select(t => new {
t.TaskId,
TimeSpent = TimeSpent.Compile().DynamicInvoke()
});
Run Code Online (Sandbox Code Playgroud)
这会产生预期的输出,除了为插入的表达式生成每行一个查询之外。这在 LINQPad 中可见。不好。
为什么会发生这种情况?好吧,Linq To Sql 需要获取所有实例TaskFiles
,脱水TaskFile
实例,然后在内存中针对它运行选择器。每个 TaskFile 都会有一个查询,可能是因为它们包含一个或多个 1:m 映射。
虽然 LTS 允许在内存中投影 select,但它不能投影Wheres(需要引用,据我所知)。仔细想想,这是完全有道理的:通过过滤内存中的整个数据库,然后在内存中转换它的子集,您很可能会传输更多的数据。(尽管如您所见,它会产生查询性能问题,但在使用 ORM 时需要注意一些事情)。
CompiledQuery.Compile()
做一些不同的事情。它将查询编译为 SQL,并且它返回的委托只是 Linq to SQL 将在内部使用的占位符。您不能在 CLR 中“调用”此方法,它只能用作另一个表达式树中的节点。
那么为什么 LTS 会使用 'd 表达式生成高效的查询呢CompiledQuery.Compile
?因为它知道这个表达式节点是做什么的,因为它知道它背后的SQL。在这种Expression.Compile
情况下,它只是InvokeExpression
调用a ,DynamicMethod
正如我之前解释的那样。
为什么需要 DataContext 参数?是的,创建完整查询更方便,但这也是因为表达式树编译器需要知道用于生成 SQL 的映射。如果没有这个参数,找到这个映射就会很痛苦,所以这是一个非常明智的要求。
归档时间: |
|
查看次数: |
2554 次 |
最近记录: |