LINQ到SQL *编译的*查询以及它们何时执行
本文关键字:何时 执行 查询 SQL 编译 LINQ | 更新日期: 2023-09-27 18:01:23
我有以下编译好的查询:
private static Func<Db, int, IQueryable<Item>> 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<TArg0, TArg1, TResult> Compile<TArg0, TArg1, TResult>(Expression<Func<TArg0, TArg1, TResult>> 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<TArg0, TArg1, TResult>(new CompiledQuery((LambdaExpression) query).Invoke<TArg0, TArg1, TResult>);
}
UseExpressionCompile方法的定义如下:
private static bool UseExpressionCompile(LambdaExpression query)
{
return typeof (ITable).IsAssignableFrom(query.Body.Type);
}
对于您定义的表达式,它的计算结果为false,因此使用else情况。
调用如下:
private TResult Invoke<TArg0, TArg1, TResult>(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有一个很棒的去反编译功能,所以我就用了。
对于Andrew Barrett的回答,我没有什么可补充的了:
- 当你调用由CompiledQuery.Compile()返回的委托时,这是正确的(即查询命中数据库),仅用于LINQ to SQL。
- 如果你使用LINQ到实体,这是不正确的。当你调用委托时,查询不会碰到数据库,它只在你开始检索数据时才会。与未编译查询一致的行为。
没错。除非你让它去取东西,否则它是不会去取的。
查看MSDN延迟加载与立即加载。特别是,你可以打开/关闭延迟加载。
看看在创建最终列表
DataContext.Log
属性,您可以轻松地研究这个问题:
db.Log = Console.Out;
然后您可以在控制台上查看SQL语句。通过分步执行程序,您可以准确地看到SQL语句到达数据库的时间。