2011-07-06 11 views
8

Tengo la siguiente consulta compilada.Consultas LINQ a SQL * compiled * y cuando ejecutan

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

Esto ejecuta en la base de datos inmediatamente cuando lo haga

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

Como en antes haciendo

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

Pero si esta consulta no fue compilado, como en

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

entonces se ejecuta en la base de datos después de haciendo

var result = query.SingleOrDefault(); 

Es este el comportamiento esperado?

Nota: Este es un duplicado de When does a compiled query that returns an IQueryable execute?, pero todas las respuestas parecen estar en desacuerdo con mis conclusiones. He publicado mi respuesta allí, pero no sé cómo llamar la atención de la gente ya que tiene más de 2 años.

+0

Tienes razón. Las consultas compiladas no se pueden componer, devuelven resultados cuando se invoca el delegado, no la consulta en sí. He cambiado mi respuesta a la pregunta referenciada. – alex

Respuesta

7

Interesante pregunta. Si lo toma a las fuentes descompilados, cuando se compila una consulta, esto es lo que sucede:

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

El método UseExpressionCompile se define así:

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

Esto da como resultado false para la expresión que haya definido , entonces se usa el caso else

La invocación es así:

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 es como:

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

En este caso, nuestro proveedor es la clase SqlProvider, la SqlProvider.CompiledQuery es la clase que implementa ICompiledQuery.Ejecutar dicha clase se implementa:

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 llama SqlProvider.Execute, que es un método bastante grande, así que voy a publicar los aspectos más destacados:

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

En medio de adquisición y liberación del conexión excede los comandos sql. Entonces yo diría que tienes razón. Contrariamente a la creencia popular, las consultas compiladas no se comportan igual que las consultas no compiladas cuando se trata de ejecución diferida.

Estoy bastante seguro de que puedes descargar el código fuente real de MS, pero no lo tengo a mano y Resharper 6 tiene una increíble función de descompilación, así que acabo de usar eso.

+0

Gracias. Solo quería confirmar que no estaba haciendo nada mal. – lahsrah

-1

Sí, eso es correcto. No irá a buscar nada hasta que lo solicites.

Consulte MSDN en Deferred versus Immediate Loading. En particular, puede turn on/off lazy loading.

Mire la respuesta más votada sobre esa pregunta donde se crea la Lista final < T>. Hay una declaración selecta allí enviándola y pidiendo un resultado. LINQ esperará el mayor tiempo posible para enviar una solicitud a la base de datos.

Por cierto, se puede investigar esto fácilmente si se establece la propiedad DataContext.Log:

db.Log = Console.Out; 

A continuación se puede ver las sentencias SQL en la consola. Al recorrer su programa, puede ver exactamente cuándo la instrucción SQL llega a la base de datos.

+1

Um, no, eso no es cierto. Parece que para las consultas compiladas va a la base de datos tan pronto como se llama al método de consulta compilado. Esto es lo que estoy preguntando, entiendo la carga diferida, ¿leyeron toda mi pregunta? – lahsrah

+0

Sé cómo mirarlo, como ya dije, está sucediendo en la llamada al método de consulta compilado. Pero me resulta extraño que la otra pregunta tenga a todos diciendo que las consultas se ejecutan en .ToList() cuando se ejecutan claramente antes de ToList() al menos para mí. Entonces me pregunto si estoy haciendo algo mal o si es el comportamiento esperado. – lahsrah

1

no tengo nada que añadir a la respuesta de Andrew Barrett, excepto esto:

  • Esto es verdad (es decir consulta golpea la base de datos) cuando se invoca delegado devuelto por CompiledQuery.Compile() sólo para LINQ a SQL.
  • Si usa LINQ para entidades esto NO es verdad. La consulta no golpea la base de datos al invocar delegado, solo lo hace cuando comienza a recuperar datos. Comportamiento consistente con consultas no compiladas.
Cuestiones relacionadas