2009-03-30 11 views
7

Tengo un proveedor de Linq que funciona correctamente y obtiene datos de mi fuente de datos elegida, pero lo que me gustaría hacer ahora que tengo mi filtrado es permitir LINQ a objetos para procesar el resto del árbol de expresión (para cosas como las uniones, proyección, etc.)Realizando parte de una consulta IQueryable y difiriendo el resto a Linq for Objects

mi pensamiento era que podía simplemente reemplazar la constante de expresión que contiene mi IQueryProvider con los conjuntos de resultados IEnumerable a través de un ExpressionVisitor y luego devolver esa nueva expresión. También volver proveedor del IEnumerable de mi IQueryable ... pero esto no parece funcionar :-(

Edición de Cualquier idea:? Algunas buenas respuestas aquí, pero dada la forma ...

var qry = from c in MyProv.Table<Customer>() 
      Join o in MyProv.Table<Order>() on c.OrderID equals o.ID 
      select new 
      { 
      CustID = c.ID, 
      OrderID = o.ID 
      } 

En mi proveedor puedo recuperar fácilmente los 2 resultados de clientes y pedidos, si los datos provienen de una fuente SQL, solo construiría y transmitiría la sintaxis de SQL Join, pero en este caso los datos no son de un Fuente SQL, así que necesito hacer la combinación en el código ... pero como dije, tengo los 2 conjuntos de resultados, y Linq to Objects puede hacer un join ... (y luego el proyección) sería muy bueno simplemente sustituir las constantes de expresión MyProv.Table<Customer> y MyProv.Table<Order> con List<Customer> y List<Order> y dejar que un proveedor List<> procese la expresión ... ¿es eso posible? ¿cómo?

Respuesta

3

el tipo de cosas que yo después de que se estaba reemplazando el consultables <> constante en el árbol de expresión con un concreto IEnumerable (o IQueryable a través .AsQueryable()) conjunto de resultados ... este es un tema complejo que probablemente solo tenga algún sentido para los escritores de Linq Provider que están metidos hasta la rodilla en los visitantes del árbol de expresiones, etc.

Encontré un fragmento en el tutorial de msdn que hace algo al igual que lo que busco, esto me da un camino a seguir ...

using System; 
using System.Linq; 
using System.Linq.Expressions; 

namespace LinqToTerraServerProvider 
{ 
    internal class ExpressionTreeModifier : ExpressionVisitor 
    { 
     private IQueryable<Place> queryablePlaces; 

     internal ExpressionTreeModifier(IQueryable<Place> places) 
     { 
      this.queryablePlaces = places; 
     } 

     internal Expression CopyAndModify(Expression expression) 
     { 
      return this.Visit(expression); 
     } 

     protected override Expression VisitConstant(ConstantExpression c) 
     { 
      // Replace the constant QueryableTerraServerData arg with the queryable Place collection. 
      if (c.Type == typeof(QueryableTerraServerData<Place>)) 
       return Expression.Constant(this.queryablePlaces); 
      else 
       return c; 
     } 
    } 
} 
+0

te lo agradezco de rodillas - el fondo de visitantes árbol de expresión. :) Esto es exactamente lo que necesitaba – Rik

1

A menos que sea un malentendido, generalmente solo agrego .ToArray() en la cadena de métodos linq en el punto donde quiero que se ejecute el proveedor de linq.

Por ejemplo (piensa LINQ to SQL)

var result = datacontext.Table 
    .Where(x => x.Prop == val) 
    .OrderBy(x => x.Prop2) 
    .ToArray() 
    .Select(x => new {CoolProperty = x.Prop, OtherProperty = x.Prop2}); 

Así que a través OrdenarPor() se traduce en SQL, pero el Select() es LINQ a Objetos.

0

La respuesta de Rob es buena, pero fuerza la enumeración completa. Se podría emitir para mantener la sintaxis método de extensión y la evaluación perezosa:

var res = ((IEnumerable<Foo>)dc.Foos 
      .Where(x => x.Bla > 0)) // IQueryable 
      .Where(y => y.Snag > 0) // IEnumerable 
5

Tanto de las respuestas anteriores funciona, pero se lee mejor si se utiliza AsEnumerable() para emitir el IQueryable a IEnumerable:

// Using Bob's code... 
var result = datacontext.Table 
    .Where(x => x.Prop == val) 
    .OrderBy(x => x.Prop2) 
    .AsEnumerable() // <---- anything after this is done by LINQ to Objects 
    .Select(x => new { CoolProperty = x.Prop, OtherProperty = x.Prop2 }); 

EDIT:

// ... or MichaelGG's 
var res = dc.Foos 
      .Where(x => x.Bla > 0) // uses IQueryable provider 
      .AsEnumerable() 
      .Where(y => y.Snag > 0); // IEnumerable, uses LINQ to Objects 
1

Si implementa un patrón de repositorios podría salirse con sólo proporcionar IQueryable atrás y abstraer la tabla.

Ejemplo:

var qry = from c in MyProv.Repository<Customer>() 
      Join o in MyProv.Repository<Order>() on c.OrderID equals o.ID 
      select new 
      { 
      CustID = c.ID, 
      OrderID = o.ID 
      } 

y luego simplemente construir su proveedor para modelar el patrón IQueryable en su método de depósito al igual que this article ilustra.

De esta manera, puede escribir todo tipo de proveedores para usar para lo que necesite. Puede tener un proveedor de SQL LINQ 2 o escribir un proveedor de memoria para las pruebas de su unidad.

El método de repositorio para el proveedor de SQL LINQ 2 podría ser algo como esto:

public IQueryable<T> Repository<T>() where T : class 
{ 
    ITable table = _context.GetTable(typeof(T)); 
    return table.Cast<T>(); 
} 
+0

Muy interesante, voy a jugar con esto, este fin de semana. –

Cuestiones relacionadas