2011-07-06 36 views
8

我有以下編譯查詢。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歲以前讓人們注意它。

+0

你說得對。編譯後的查詢不可組合,它們在調用委託而不是查詢本身時返回結果。我改變了我對所引用問題的回答。 – alex

回答

7

有趣的問題。把它帶到反編譯源,當您編譯查詢,這是發生了什麼:

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

這個計算結果爲假的表達式您已經定義,所以使用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有一個很棒的去反編譯函數,所以我只是用它。

+0

謝謝。我只是想確認我沒有做錯任何事情。 – lahsrah

-1

是的,沒錯。除非你提出要求,否則它不會得到任何東西。

Deferred versus Immediate Loading上查看MSDN。特別是你可以turn on/off lazy loading

看看最後回答的問題,最終列表< T>被創建。在那裏有一個選擇語句發送它並要求結果。 LINQ會盡可能等待向數據庫發送請求。

db.Log = Console.Out; 

然後你可以看控制檯上的SQL語句:

順便說一句,你可以,如果你設置DataContext.Log屬性容易展開調查。通過瀏覽你的程序,你可以準確地看到SQL語句到達數據庫的時間。

+1

恩,這不是真的。對於已編譯的查詢,似乎只要編譯的查詢方法被調用就會進入數據庫。這就是我所要求的,我瞭解延期加載,你是否讀過我的整個問題? – lahsrah

+0

我知道如何看待它,因爲我已經說過它發生在編譯的查詢方法調用上。但是我發現奇怪的是,另一個問題是每個人都說查詢在.ToList()上執行時,當他們在ToList()之前清楚地執行時,至少對我來說是這樣。所以我想知道如果我做錯了什麼或者是否是預期的行爲。 – lahsrah

1

我有什麼要補充的安德魯·巴雷特的回答,除了這一點:當你調用由CompiledQuery.Compile返回委託()僅適用於LINQ to SQL的

  • 這是真實的(即查詢命中數據庫)。
  • 如果您使用LINQ to Entities,則不是這樣。查詢在調用委託時不會觸及數據庫,只有在您開始檢索數據時纔會執行。行爲與非編譯查詢一致。