簡體   English   中英

LINQ 到 SQL *已編譯*查詢以及它們何時執行

[英]LINQ to SQL *compiled* queries and when they execute

我有以下編譯的查詢。

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 class,SqlProvider.CompiledQuery 是實現 ICompiledQuery 的 class。 在 class 上執行:

  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 有一個很棒的 go 可以反編譯 function,所以我只是使用它。

除了這個,我沒有什么可以添加到 Andrew Barrett 的回答中:

  • 當您調用 CompiledQuery.Compile() 返回的委托時,這是真的(即查詢命中數據庫),僅針對 LINQ 到 SQL。
  • 如果您將 LINQ 用於實體,這是不正確的。 調用委托時查詢不會命中數據庫,它僅在您開始檢索數據時才會這樣做。 與非編譯查詢一致的行為。

是的,沒錯。 它不會 go 並得到任何東西,直到你要求它。

查看有關延遲加載與立即加載的 MSDN。 特別是,您可以打開/關閉延遲加載

在創建最終 List<T> 的那個問題上查看最受好評的答案。 那里有一條 select 語句發送它並詢問結果。 LINQ 將盡可能長時間地等待向數據庫發送請求。

順便說一句,如果您設置DataContext.Log屬性,您可以輕松地對此進行調查:

db.Log = Console.Out;

然后您可以在控制台上觀看 SQL 語句。 通過單步執行您的程序,您可以准確地看到 SQL 語句何時命中數據庫。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM