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
顺便说一下,如果设置DataContext.Log
属性,则可以轻松地进行调查:
db.Log = Console.Out;
然后,您可以在控制台上查看SQL语句。 通过单步执行程序,您可以确切地看到SQL语句何时命中数据库。