2009-08-20 21 views
8

Supongamos que tengo una expresión IQueryable<T> que me gustaría encapsular la definición de, almacenarla y reutilizarla o incrustarla en una consulta más grande más adelante. Por ejemplo:¿Cómo mantener la ejecución diferida de LINQ?

IQueryable<Foo> myQuery = 
    from foo in blah.Foos 
    where foo.Bar == bar 
    select foo; 

ahora creo que solo puedo mantener ese objeto myQuery alrededor y utilizarlo como he descrito. Sin embargo, algunas cosas que no estoy seguro acerca de:

  1. La mejor forma de parametrizar ella? Inicialmente definí esto en un método y luego devolví el IQueryable<T> como resultado del método. De esta forma puedo definir blah y bar como argumentos de método y supongo que solo crea un nuevo IQueryable<T> cada vez. ¿Es esta la mejor manera de encapsular la lógica de un IQueryable<T>? ¿Hay otras formas?

  2. ¿Qué sucede si mi consulta se resuelve en un escalar en lugar de IQueryable? Por ejemplo, ¿qué ocurre si quiero que esta consulta sea exactamente como se muestra pero anexe .Any() para que me informe si hubo resultados que coinciden? Si agrego el (...).Any(), el resultado es bool y se ejecuta inmediatamente, ¿no? ¿Hay alguna manera de utilizar estos operadores Queryable (Any, SindleOrDefault, etc.) sin ejecutar de inmediato? ¿Cómo maneja esto LINQ-to-SQL?

Editar: Parte 2 es realmente más de tratar de entender cuáles son las diferencias entre IQueryable<T>.Where(Expression<Func<T, bool>>) limitación vs IQueryable<T>.Any(Expression<Func<T, bool>>). Parece que este último no es tan flexible cuando se crean consultas más grandes en las que se debe retrasar la ejecución. Se puede agregar el Where() y luego otras construcciones se pueden anexar y luego finalmente ejecutar. Como el Any() devuelve un valor escalar, parece que se ejecutará inmediatamente antes de que se pueda generar el resto de la consulta.

Respuesta

5
  1. usted tiene que ser muy cuidadoso acerca de pasar alrededor de IQueryables cuando se está utilizando un DataContext, porque una vez que el contexto de obtener dispuestos usted no será capaz de ejecutar dicha IQueryable más. Si no está utilizando un contexto, entonces puede estar bien, pero tenga en cuenta eso.

  2. . Cualquier() y .FirstOrDefault() son no diferido. Cuando los llame, harán para que se ejecute. Sin embargo, esto puede no hacer lo que crees que hace. Por ejemplo, en LINQ to SQL si realiza un .Any() en un IQueryable, actúa como IF EXISTS (SQL AQUÍ) básicamente.

Usted puede IQueryable de la cadena a lo largo de esta manera, si usted quiere:

var firstQuery = from f in context.Foos 
        where f.Bar == bar 
        select f; 

var secondQuery = from f in firstQuery 
        where f.Bar == anotherBar 
        orderby f.SomeDate 
        select f; 

if (secondQuery.Any()) //immediately executes IF EXISTS(second query in SQL) 
{ 
    //causes execution on second query 
    //and allows you to enumerate through the results 
    foreach (var foo in secondQuery) 
    { 
     //do something 
    } 

    //or 

    //immediately executes second query in SQL with a TOP 1 
    //or something like that 
    var foo = secondQuery.FirstOrDefault(); 
} 
+0

Suena como en el # 1, que presenta un método que esencialmente construye un nuevo 'IQueryable' cada vez que es una buena cosa * * ya que de esta manera no voy a tener problemas con su eliminación. En el n. ° 2, estoy confundido acerca de cómo LINQ-to-SQL puede traducir el operador 'Any', pero no puedo diferir. Si tuviera que usar un operador 'Any' dentro de una consulta más grande ¿se ejecuta inmediatamente allí también, o es parte de la ejecución de la consulta más grande? – mckamey

+0

Bien, creo que ya casi estoy allí. Si tuviera que insertar un '.Any()' en una cláusula 'where', entonces no ejecutaría eso en un bucle, ¿correcto? Compilaría la expresión SQL apropiada y la enviaría hacia abajo. Entonces, en efecto, no es '.Any()' lo que impide la ejecución diferida, ya que es la forma en que se está utilizando. Básicamente, si el resultado de una consulta * completa * es un escalar, entonces el compilador cree que necesita el resultado ahora en lugar de continuar con la compilación de un 'IQueryable '. – mckamey

+1

@McKAMEY correcto, tan pronto como use .Any() en un contexto que no es diferible, se ejecutará. En el caso de .Where() está buscando una expresión, que es diferible, entonces estás bien. En el caso de var o el bucle foreach, esos causan la ejecución porque no son diferibles. – Joseph

2

Una opción mucho mejor que el almacenamiento en caché de objetos IQueryable es para almacenar en caché los árboles de expresión. Todos los objetos IQueryable tienen una propiedad llamada Expression (I believe), que representa el árbol de expresiones actual para esa consulta.

En un momento posterior, puede volver a crear la consulta llamando a queryable.Provider.CreateQuery (expresión), o directamente a cualquier proveedor (en su caso, un contexto de datos Linq2Sql).

Sin embargo, la parametrización de estos árboles de expresiones es un poco más difícil, ya que utilizan ConstantExpressions para generar un valor. Para parametrizar estas consultas, tendrá que volver a generar la consulta cada vez que desee parámetros diferentes.

+1

Diría que la parametrización (o, lo que es más importante, el encapsulamiento de una sola unidad de lógica) es el verdadero objetivo aquí en lugar del almacenamiento en caché. Teniendo en cuenta que el compilador de C# ya lo ha convertido, no creo que un equivalente en tiempo de ejecución sea de mucho beneficio de uso/rendimiento (como lo que implica el almacenamiento en caché). – mckamey

+0

¿Puede explicar por qué esta opción es mejor? Por lo que yo entiendo, simplemente desenvuelve la 'Expresión' para volver a envolverla más tarde: ¿por qué no mantener la envoltura como está? – PPC

1

Any() utilizado de esta manera se difiere.

var q = dc.Customers.Where(c => c.Orders.Any()); 

Any() utilizado de esta manera no se difiere, pero aún así se traduce a SQL (toda la tabla clientes no se carga en memoria).

bool result = dc.Customers.Any(); 

Si quieres un dilatarán(), hacerlo de esta manera:

public static class QueryableExtensions 
{ 
    public static Func<bool> DeferredAny<T>(this IQueryable<T> source) 
    { 
    return() => source.Any(); 
    } 
} 

que se llama así:

Func<bool> f = dc.Customers.DeferredAny(); 
bool result = f(); 

La desventaja es que esta técnica no lo hará permitir sub-consultas.

+0

Cuando dices diferido, parece que quieres decir que puedo definir una expresión lambda (o delegada) pero no ejecutarla inmediatamente. Creo que hay una sutileza en el concepto de "ejecución diferida" de LINQ que permite que la operación se convierta en parte de un árbol de expresiones más grande, que el proveedor puede interpretar como quiera. Mi pregunta es más tratando de llegar a cuáles son las diferencias de limitación entre 'IQueryable .Where (Expression >)' vs.'IQueryable .Any (Expression >)', ya que parece que este último no es tan flexible. – mckamey

+1

Esa inflexibilidad proviene de la diferencia en los tipos de devolución. El tipo llamado 'bool' no puede tener un comportamiento diferido. –

+0

esta sugerencia parece insegura para mí (considere el DataContext) –

0

Crear una aplicación parcial de la consulta dentro de una expresión

Func[Bar,IQueryable[Blah],IQueryable[Foo]] queryMaker = 
(criteria, queryable) => from foo in queryable.Foos 
     where foo.Bar == criteria 
     select foo; 

y luego se puede usar por ...

IQueryable[Blah] blah = context.Blah; 
Bar someCriteria = new Bar(); 
IQueryable[Foo] someFoosQuery = queryMaker(blah, someCriteria); 

La consulta podría ser encapsulado dentro de una clase si quieres hacerlo más portátil/reutilizable.

public class FooBarQuery 
{ 
    public Bar Criteria { get; set; } 

    public IQueryable[Foo] GetQuery(IQueryable[Blah] queryable) 
    { 
    return from foo in queryable.Foos 
     where foo.Bar == Criteria 
     select foo; 
    } 
} 
Cuestiones relacionadas