LINQ to SQL *编译*查询以及何时执行

我有以下编译查询。

private static Func<Db, int, IQueryable> func = CompiledQuery.Compile((Db db, int id) => from i in db.Items where i.ID == id select i ); 

当我这样做时,它立即在数据库上执行

 var db = new Db() var query = func(db, 5); // Query hits the database here 

之前一样

 var result = query.SingleOrDefault(); // Happens in memory 

但是如果没有编译这个查询,就像在

 var query = from i in db.Items where i.ID == id select i 

然后它在完成在数据库上执行

  var result = query.SingleOrDefault(); 

这是预期的行为吗?

注意:这与返回IQueryable执行的编译查询何时重复 ,但那里的所有答案似乎都不同意我的发现。 我已经在那里发布了我的答案,但我不知道如何让人们关注它,因为它已经超过2年了。

有趣的问题。 将它带到反编译源,编译查询时,会发生以下情况:

 public static Func Compile(Expression> query) where TArg0 : DataContext { if (query == null) System.Data.Linq.Error.ArgumentNull("query"); if (CompiledQuery.UseExpressionCompile((LambdaExpression) query)) return query.Compile(); else return new Func(new CompiledQuery((LambdaExpression) query).Invoke); } 

UseExpressionCompile方法定义如下:

 private static bool UseExpressionCompile(LambdaExpression query) { return typeof (ITable).IsAssignableFrom(query.Body.Type); } 

对于您定义的表达式,其计算结果为false,因此使用else案例。

Invoke是这样的:

 private TResult Invoke(TArg0 arg0, TArg1 arg1) where TArg0 : DataContext { return (TResult) this.ExecuteQuery((DataContext) arg0, new object[2] { (object) arg0, (object) arg1 }); } 

ExecuteQuery就像:

 private object ExecuteQuery(DataContext context, object[] args) { if (context == null) throw System.Data.Linq.Error.ArgumentNull("context"); if (this.compiled == null) { lock (this) { if (this.compiled == null) this.compiled = context.Provider.Compile((Expression) this.query); } } return this.compiled.Execute(context.Provider, args).ReturnValue; } 

在这种情况下,我们的提供程序是SqlProvider类,SqlProvider.CompiledQuery是实现ICompiledQuery的类。 执行该类的执行:

  public IExecuteResult Execute(IProvider provider, object[] arguments) { if (provider == null) throw System.Data.Linq.SqlClient.Error.ArgumentNull("provider"); SqlProvider sqlProvider = provider as SqlProvider; if (sqlProvider == null) throw System.Data.Linq.SqlClient.Error.ArgumentTypeMismatch((object) "provider"); if (!SqlProvider.CompiledQuery.AreEquivalentShapes(this.originalShape, sqlProvider.services.Context.LoadOptions)) throw System.Data.Linq.SqlClient.Error.CompiledQueryAgainstMultipleShapesNotSupported(); else return sqlProvider.ExecuteAll(this.query, this.queryInfos, this.factory, arguments, this.subQueries); } 

SqlProvider.ExecuteAll调用SqlProvider.Execute,这是一个非常大的方法,所以我将发布亮点:

 private IExecuteResult Execute(Expression query, SqlProvider.QueryInfo queryInfo, IObjectReaderFactory factory, object[] parentArgs, object[] userArgs, ICompiledSubQuery[] subQueries, object lastResult) { this.InitializeProviderMode(); DbConnection dbConnection = this.conManager.UseConnection((IConnectionUser) this); try { DbCommand command = dbConnection.CreateCommand(); command.CommandText = queryInfo.CommandText; command.Transaction = this.conManager.Transaction; command.CommandTimeout = this.commandTimeout; this.AssignParameters(command, queryInfo.Parameters, userArgs, lastResult); this.LogCommand(this.log, command); ++this.queryCount; switch (queryInfo.ResultShape) { case SqlProvider.ResultShape.Singleton: DbDataReader reader1 = command.ExecuteReader(); ... case SqlProvider.ResultShape.Sequence: DbDataReader reader2 = command.ExecuteReader(); ... default: return (IExecuteResult) new SqlProvider.ExecuteResult(command, queryInfo.Parameters, (IObjectReaderSession) null, (object) command.ExecuteNonQuery(), true); } } finally { this.conManager.ReleaseConnection((IConnectionUser) this); } } 

在获取和释放连接之间,它除了sql命令之外。 所以我说你是对的。 与流行的看法相反,当涉及延迟执行时,编译的查询与未编译的查询的行为不同。

我很确定你可以从MS下载实际的源代码,但我没有它的方便,Resharper 6有一个很棒的反编译function,所以我只是用它。

除了这个,我没有任何东西可以添加到Andrew Barrett的答案中:

  • 当您调用CompiledQuery.Compile()仅为LINQ to SQL返回的委托时,这是正确的(即查询命中数据库)。
  • 如果使用LINQ to Entities,则不然。 调用委托时查询不会命中数据库,只有在您开始检索数据时才会这样做。 行为与未编译的查询一致。

是的,这是对的。 在你问它之前,它不会去获取任何东西。

查看延迟与立即加载的 MSDN。 特别是,您可以打开/关闭延迟加载 。

查看关于该问题的最受欢迎的答案,其中创建了最终的List 。 在那里有一个select语句将其发送出去并要求结果。 LINQ将尽可能长时间地向数据库发送请求。

顺便说一下,如果设置DataContext.Log属性,则可以轻松地进行调查:

 db.Log = Console.Out; 

然后,您可以在控制台上查看SQL语句。 通过单步执行程序,您可以确切地看到SQL语句何时命中数据库。