2012-05-16 14 views
6

tengo la siguiente consulta en LINQ a Entidades:LINQ a Entidades no admite invocación

var query = from p in db.Products 
      where p.Price > 10M 
      select p; 

En este punto, la consulta no se ha ejecutado, y quiero escribir una consulta que devolverá verdadero/falso Based en algunas condiciones:

return query.Any(p => p.IsInStock && 
       (p.Category == "Beverage" || 
       p.Category == "Other")); 

Esto funciona bien; Sin embargo, me gustaría obtener un poco de reutilización de mi código. Tengo muchos métodos que hay que filtrar en función de si la categoría es una bebida u otro, así que he intentado crear un delegado:

Func<Product, bool> eligibleForDiscount = (product) => product.Category == "Beverage" || product.Category == "Other"; 

quería sustituir el cheque en línea con el delegado:

return query.Any(p => p.IsInStock && eligibleForDiscount(p)); 

Esto me da un error al decir que LINQ to Entities no admite Invoke. ¿Por qué no puedo sustituir el código en línea por un delegado como este, y hay alguna forma en que pueda lograr mi reutilización de alguna otra manera?

+1

posible duplicado de [El LINQ tipo de nodo expresión 'Invoke' no se admite en LINQ to Entities en marco de la entidad] (http: // stackoverflow .com/questions/8741667/the-linq-expression-node-type-invoke-is-not-supported-in-linq-to-entities-in-e) –

+2

Teniendo en cuenta que la pregunta no tiene una respuesta aceptada y no tengo respuesta idea de lo que es PredicateBuilder, no considero que esta pregunta sea un duplicado. – Dismissile

Respuesta

6

Recuerda que debajo del capó Linq-to-{DATABASE} está transformando el IQueryable que has producido en Sql.

Puede código no en línea así porque una Invoke (el método que en realidad estás llamando al llamar a una Func o Action) no tiene forma coherente de transformarla en una instrucción SQL (que podría estar haciendo nada ahí).

Dicho esto se puede reutilizar partes mediante su división por:

var query = from p in db.Products 
      where p.Price > 10M 
      select p; 

query = query.Where(p => p.IsInStock); 
query = query.Where(p => p.Category == "Beverage" || p.Category == "Other"); 
return query.Any(); 

Tanto los que se puede poner en métodos que toman un IQueryable<Product> y regresar el mismo (pero filtrada). ¡Entonces puedes reutilizar al contenido de tu corazón!

+0

Su sugerencia sería tener un método que tome IQueryable y devuelva IQueryable en lugar de un delegado. Supongo que ambos hacen lo mismo :) – Dismissile

+0

Hacen exactamente lo mismo, y es mucho más reutilizable ... Los métodos y funciones que toman IQueryable y return IQueryable son realmente poderosos de esa manera. – Crisfole

+1

Como una ventaja adicional, esto también se puede convertir en un método de extensión que proporciona una consulta personalizada y elegante. WHEREIsInAnyCategory ("Beverage", "Other") (o query.WhereBeverageOrOther()) sintaxis en una interfaz IQueryable . – MerickOWA

2

El problema es que IQueryable necesita generar una expresión SQL para pasar a RDBMS, y no puede hacerlo cuando todo lo que tiene es un predicado opaco.

La manera obvia pero ineficiente es volver a escribir la consulta de la siguiente manera:

return query.Where(p => p.IsInStock).AsEnumerable().Any(eligibleForDiscount); 

Una forma menos trivial sería la siguiente:

bool GotEligible(Expression<Func<Product,bool>> pred) { 
    return query.Where(p => p.IsInStock).Any(pred); 
} 

Nota cómo en lugar de un predicado este método tiene una expresión de predicado. Ahora es transparente para el EF y se puede convertir a una consulta SQL sin problemas.

+0

por cierto, quiere deshacerse de '(p)' en la cláusula 'Any' – Crisfole

+0

@ChristopherPfohl Sí, ¡tiene razón! ¡Gracias! – dasblinkenlight

+0

Y * probablemente * se refería a 'GotEligible';) – Crisfole

0

Mientras siga con IQueryable, puede mantener las consultas reutilizables en las funciones.

public IQueryable<Product> EligibleForDiscount(IQueryable<Product> products) 
    { 
     return products.Where(p => product.Category == "Beverage" || 
            product.Category == "Other"); 
    } 

Ahora llaman como cualquier otra función:

IQueryable<Product> query = (from p in db.Products 
           where p.Price > 10M 
           select p); 

    query = EligibleForDiscount(query);