2011-07-06 12 views
8

Mam następujące skompilowane zapytanie.LINQ do SQL * skompilowane * zapytania i kiedy wykonują

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

ten wykonuje się na bazie danych natychmiast, gdy robię

var db = new Db() 
var query = func(db, 5); // Query hits the database here 

jak w przed robi

var result = query.SingleOrDefault(); // Happens in memory 

Ale jeśli ta kwerenda nie został skompilowany, jak w

var query = from i in db.Items 
      where i.ID == id 
      select i 

to wykonuje się na bazie po robi

var result = query.SingleOrDefault(); 

Czy to jest normalne zachowanie?

Uwaga: jest to duplikat When does a compiled query that returns an IQueryable execute?, ale wszystkie odpowiedzi wydają się nie zgadzać z moimi ustaleniami. Odpowiedziałem tam na nie, ale nie wiem, jak zwrócić na to uwagę ludzi, ponieważ ma ponad 2 lata.

+0

Masz rację. Skompilowane zapytania nie są kompozycyjne, zwracają wyniki, gdy delegate jest wywoływane, a nie samo zapytanie. Zmieniłem swoją odpowiedź na zadane pytanie. – alex

Odpowiedz

7

Interesujące pytanie. Biorąc go do dekompilowana źródeł, podczas kompilowania kwerendy, to co się dzieje:

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

Sposób UseExpressionCompile jest zdefiniowany następująco:

private static bool UseExpressionCompile(LambdaExpression query) 
{ 
    return typeof (ITable).IsAssignableFrom(query.Body.Type); 
} 

Ta wartość false dla wyrażenia zdefiniowaniu , więc używany jest inny przypadek.

Invoke jest tak:

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 jest jak:

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

W tym przypadku nasz dostawca klasa SqlProvider The SqlProvider.CompiledQuery jest klasa, która implementuje ICompiledQuery.Wykonać na tej klasy jest realizowany:

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 wzywa SqlProvider.Execute, który jest dość duży sposób, więc będę zamieszczać Highlights:

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

W między pozyskiwania i zwalniania połączenie to wygasza polecenia sql. Więc powiedziałbym, że masz rację. Wbrew powszechnemu przekonaniu, skompilowane zapytania nie zachowują się tak samo jak nieskompilowane zapytania, jeśli chodzi o odroczone wykonanie.

Jestem prawie pewny, że możesz pobrać aktualny kod źródłowy ze stwardnienia rozsianego, ale nie mam tego pod ręką, a Resharper 6 ma niesamowite przejście do funkcji dekompilowanej, więc właśnie to wykorzystałem.

+0

Dzięki. Chciałem tylko potwierdzić, że nie zrobiłem nic złego. – lahsrah

-1

Tak, to prawda. Nie dostanie niczego, dopóki tego nie poprosisz.

Sprawdź MSDN pod numerem Deferred versus Immediate Loading. W szczególności możesz turn on/off lazy loading.

Zobacz najbardziej przeczącą odpowiedź na to pytanie, w którym tworzona jest ostateczna lista < T>. Jest tam instrukcja wyboru wysyłająca ją i prosząca o wynik. LINQ będzie czekać tak długo, jak to możliwe, aby wysłać żądanie do bazy danych.

Nawiasem mówiąc, można to łatwo zbadać, czy ustawić właściwość DataContext.Log:

db.Log = Console.Out; 

Wtedy można oglądać SQL na konsoli. Przechodząc przez program można dokładnie zobaczyć, kiedy instrukcja SQL trafi do bazy danych.

+1

Um, nie to nie jest prawda. Wydaje się, że dla skompilowanych zapytań trafia do bazy danych zaraz po wywołaniu skompilowanej metody zapytania. Oto, o co proszę, rozumiem odroczone ładowanie, czy przeczytałeś moje całe pytanie? – lahsrah

+0

Wiem, jak na to patrzeć, tak jak już powiedziałem, że dzieje się to na wywołaniu metody kompilacji zapytań. Ale wydawało mi się dziwne, że w drugim pytaniu wszyscy mówią, że kwerendy wykonywane są na .ToList(), gdy wyraźnie wykonują to przynajmniej przed ToList(). Zastanawiam się więc, czy robię coś złego, czy też jest to oczekiwane zachowanie. – lahsrah

1

mam nic do dodania do odpowiedzi Andrew Barretta z wyjątkiem tego:

  • To jest prawdziwe (tzn kwerendy natrafi bazy danych) Po wywołaniu delegata zwrócony przez CompiledQuery.Compile() tylko dla LINQ to SQL.
  • Jeśli użyjesz LINQ do Entities, nie jest to prawda. Zapytanie nie trafia w bazę danych po wywołaniu delegata, robi to tylko po rozpoczęciu pobierania danych. Zachowanie zgodne z niezkładanymi zapytaniami.