当我在我的编译查询中包含一个函数时幕后会发生什么,就像我在这里使用DataConvert.ToThema()将表对象转换为我的自定义业务对象一样:
public static class Queries
{
public static Func<MyDataContext, string, Thema> GetThemaByTitle
{
get
{
var func = CompiledQuery.Compile(
(MyDataContext db, string title) =>
(from th in elan.tbl_Thema
where th.Titel == title
select DataConvert.ToThema(th)).Single()
);
return func;
}
}
}
public static class DataConvert
{
public static Thema ToThema(tbl_Thema tblThema)
{
Thema thema = new Thema();
thema.ID = tblThema.ThemaID;
thema.Titel = tblThema.Titel;
// and some other stuff
return thema;
}
}
并像这样称呼它
Thema th = Queries.GetThemaByTitle.Invoke(db, "someTitle");
显然该函数没有转换为SQL或其他东西(怎么可能),但是当我在VS2010中设置断点时它也不成立。
它没有问题,但我不明白为什么或为什么。到底发生了什么?
答案 0 :(得分:2)
你的DataConvert.ToThema()
静态方法只是创建一个具有默认构造函数的类型的实例,并设置各种属性,这是正确的吗?如果是这样,它与以下内容并无太大区别:
(from th in elan.tbl_Thema
where th.Titel == title
select new Thema{ID=th.ThemaID, Titel=th.Titel, etc...}
).Single());
当您致电Queries.GetThemaByTitle
时,正在编译查询。 (顺便说一句,你这样称呼它的方式可能会或可能不会给你带来任何预编译的好处)。 'Query'实际上是一个代码表达式树,其中只有一部分用于生成发送到数据库的SQL代码。
它的其他部分将生成IL代码,该代码抓取从数据库返回的内容并将其放入某种形式供您使用。 LINQ(EF或L2S)足够聪明,能够接受静态方法调用并从中生成IL来执行您想要的操作 - 也许它正在使用内部delegate
或其他类似的操作。但最终,它不需要与我上面替换的内容(很多)不同。
但请注意,无论您回来的类型是什么,都会发生这种情况;某处,正在生成IL代码,将DB值放入CLR对象。那是表达树的另一部分。
如果你想更详细地了解那些表达树及其涉及的内容,我必须深入挖掘,但我不确定你的问题是否是你想要的。
答案 1 :(得分:1)
首先我要指出,无论您是否编译查询都无关紧要。即使您没有预编译,您也会观察到相同的结果。
从技术上讲,正如安德鲁指出的那样,做这项工作并不复杂。在评估LINQ表达式时,将在内部构造表达式树。您的函数在此表达式树中显示为节点。这里没有魔力。你将能够在L2S和L2E中编写这个表达式,它将编译并运行正常。直到您尝试对数据库实际执行实际的SQL查询。这就是差异的开始。 L2S似乎很乐意执行此任务,而L2E因NotSupportedException失败,并报告它不知道如何将ToThema转换为商店查询。
那么里面发生了什么?在L2S中,正如Andrew所解释的那样,查询编译器理解您的函数可以与已经执行的商店查询分开运行。因此,它会将对函数的调用发送到对象读取管道中(从SQL读取的数据将转换为您调用后返回的对象)。
一旦Andrew不太正确,那么静态方法中的内容是否重要。我不这么认为。
如果在调试器中为您的函数添加了一个断点,您将看到每个返回的行调用一次。在堆栈跟踪中,您将看到“轻量级函数”,实际上,这意味着该方法是在运行时发出的。所以这就是Linq to Sql的工作原理。
Linq to Entity团队似乎采取了不同的路线。我不知道,是什么原因,为什么他们决定禁止L2E查询中的所有InvocationExpressions。也许这些是性能原因,或者可能是因为它们需要支持所有类型的提供程序,而不仅仅支持SQL Server,因此数据读取器的行为可能会有所不同。或者他们只是认为大多数人都不会意识到其中一些是按照返回的行执行的,并且希望关闭此选项。
只是我的想法。如果有人有任何见解,请加入!