Como ya se ha mencionado aquí, LINQ permite ampliar cualquier consulta simplemente añadiendo más criterios.
var query =
from x in xs
where x==1
select x;
if (mustAddCriteria1)
query =
from x in query
where ... // criteria 1
select x;
if (mustAddCriteria2)
query =
from x in query
where ... // criteria 2
select x;
Y así sucesivamente. Este enfoque funciona perfectamente. Pero es probable que sepa que la compilación de consultas LINQ es bastante costosa: p. Entity Framework puede compilar aproximadamente 500 consultas relativamente simples por segundo (ver, por ejemplo, ORMBattle.NET).
Por otro lado, muchas herramientas ORM admite consultas compiladas:
- Se pasa una instancia
IQueryable
hasta cierto Compile
método, y obtener un delegado que permite ejecutar mucho más rápido después, porque ninguna recompilación ocurriría en este caso.
Pero si nos gustaría probar a utilizar este enfoque aquí, inmediatamente notamos que nuestra consulta es en realidad dinámica: IQueryable
que se ejecute cada vez puede ser diferente a la anterior. La presencia de partes de consulta está determinada por valores de parámetros externos.
Entonces, ¿podemos ejecutar esas consultas compiladas sin, p. Ej., caché explícito?
DataObjects.Net 4 es compatible con la función llamada "ramificación booleana". Implica que cualquier expresión booleana constante se evalúa durante la compilación de consultas y su valor real se inyecta en la consulta SQL como una constante booleana verdadera (es decir, no como un valor de parámetro o como una expresión que utiliza parámetros).
Esta característica permite generar diferentes planes de consulta dependiendo de los valores de tales expresiones booleanas con facilidad. P.ej.este código:
int all = new Random().Next(2);
var query =
from c in Query<Customer>.All
where all!=0 || c.Id=="ALFKI"
select c;
se ejecutará mediante dos consultas SQL diferentes, y por lo tanto - dos diferentes planes de consulta:
- de consultas plan basado en búsqueda de índice (bastante rápido), si todo == 0
- plan de consulta basada en la exploración de índice (bastante lento), si todo = 0
caso cuando todos == null, consulta SQL:
SELECT
[a].[CustomerId],
111 AS [TypeId] ,
[a].[CompanyName]
FROM
[dbo].[Customers] [a]
WHERE((CAST(0 AS bit) <> 0) OR([a].[CustomerId] = 'ALFKI'));
caso cuando todos == null, plan de consulta:
|--Compute Scalar(DEFINE:([Expr1002]=(111)))
|--Clustered Index Seek(OBJECT:([DO40-Tests].[dbo].[Customers].[PK_Customer] AS [a]), SEEK:([a].[CustomerId]=N'ALFKI') ORDERED FORWARD)
segundo caso (cuando todos! = Null), consulta SQL: (! = Null cuando todo)
SELECT
[a].[CustomerId],
111 AS [TypeId] ,
[a].[CompanyName]
FROM
[dbo].[Customers] [a]
WHERE((CAST(1 AS bit) <> 0) OR([a].[CustomerId] = 'ALFKI'));
-- Notice the^value is changed!
Segundo caso , plan de consulta:
|--Compute Scalar(DEFINE:([Expr1002]=(111)))
|--Clustered Index Scan(OBJECT:([DO40-Tests].[dbo].[Customers].[PK_Customer] AS [a]))
-- There is index scan instead of index seek!
Tenga en cuenta que casi cualquier otro ORM sería compilar este a una consulta que utiliza parámetro entero:
SELECT
[a].[CustomerId],
111 AS [TypeId] ,
[a].[CompanyName]
FROM
[dbo].[Customers] [a]
WHERE((@p <> 0) OR ([a].[CustomerId] = 'ALFKI'));
-- ^^ parameter is used here
Desde SQL Server (así como la mayoría de las bases de datos) genera una única versión del plan de consulta para una consulta en particular, tiene la única opción en este caso - generar un plan con el recorrido de índice:
|--Compute Scalar(DEFINE:([Expr1002]=(111)))
|--Clustered Index Scan(OBJECT:([DO40-Tests].[dbo].[Customers].[PK_Customer] AS [a]), WHERE:(CONVERT(bit,[@p],0)<>(0) OR [DO40-Tests].[dbo].[Customers].[CustomerId] as [a].[CustomerId]=N'ALFKI'))
Ok, esa fue una explicación "rápida" de la utilidad de esta característica. Regresemos a su caso ahora.
ramificación booleana permite ponerlo en práctica de manera muy simple:
var categoryId = 1;
var userId = 1;
var query =
from product in Query<Product>.All
let skipCategoryCriteria = !(categoryId > 0)
let skipUserCriteria = !(userId > 0)
where skipCategoryCriteria ? true : product.Category.Id==categoryId
where skipUserCriteria ? true :
(
from order in Query<Order>.All
from detail in order.OrderDetails
where detail.Product==product
select true
).Any()
select product;
El ejemplo diferente del suyo, pero ilustra la idea. Usé un modelo diferente principalmente para poder probar esto (mi ejemplo se basa en el modelo de Northwind).
Esta consulta es:
No
- una consulta dinámica, por lo que puede pasar con seguridad a
Query.Execute(...)
método para conseguirlo ejecutado como consulta compilada.
- Sin embargo, cada una de sus ejecuciones dará lugar al mismo resultado como si esto se hiciera con "anexar" al
IQueryable
.
LLBLGen es compatible con esto, incluso 20 de ellos. Como las cláusulas where se agregan una o muchas a la vez. – PostMan
@PostMan ¿Tendría un ejemplo de esto? – Luke101
Puede hacer esto en Entity Framework vea la respuesta de tt83 a continuación. Su respuesta es para Linq a SQL, pero el concepto es el mismo para Entity Framework. No es necesario tirar al bebé con el agua del baño. –