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

时间:2011-07-06 06:30:21

标签: c# linq linq-to-sql

我有以下编译查询。

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();

这是预期的行为吗?

注意:这是 When does a compiled query that returns an IQueryable execute? 的副本,但那里的所有答案似乎都不同意我的发现。我已经在那里发布了我的答案,但我不知道如何让人们关注它,因为它已经超过2年了。

3 个答案:

答案 0 :(得分:8)

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

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案例。

Invoke就像这样:

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有一个很棒的反编译功能,所以我只是用它。

答案 1 :(得分:1)

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

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

答案 2 :(得分:-1)

是的,这是对的。在你问它之前,它不会去做任何事情。

Deferred versus Immediate Loading上查看MSDN。特别是,您可以turn on/off lazy loading

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

顺便说一句,如果设置DataContext.Log属性:

,则可以轻松调查此问题
db.Log = Console.Out;

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