Come già menzionato qui, LINQ consente di estendere qualsiasi query aggiungendo semplicemente più criteri ad esso.
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;
E così via. Questo approccio funziona perfettamente. Ma probabilmente, sai che la compilazione di query LINQ è piuttosto costosa: ad es. Entity Framework può compilare solo circa 500 query relativamente semplici al secondo (vedere ad esempio ORMBattle.NET).
D'altra parte, molti strumenti ORM supporta le query compilate:
- si passa un'istanza
IQueryable
a qualche metodo Compile
, e ottenere un delegato che consente di eseguire molto più veloce più tardi, perché non ricompilazione avverrebbe in questo caso.
Ma se vorremmo provare a utilizzare questo approccio qui, notiamo subito che la nostra query è in realtà dinamica: IQueryable
eseguiamo ogni volta che potrebbe differire da quella precedente. La presenza di parti di query è determinata dai valori dei parametri esterni.
Così possiamo eseguire tali query come compilate senza ad es. memorizzazione nella cache esplicita?
DataObjects.Net 4 supporta la cosiddetta funzione "boolean branching". Esso implica che qualsiasi espressione booleana costante viene valutata durante la compilazione della query e il suo valore effettivo viene iniettato nella query SQL come costante booleana vera (cioè non come valore del parametro o come espressione che utilizza parametri).
Questa funzione consente di generare diversi piani di query in base ai valori di tali espressioni booleane con facilità. Per esempio.questo codice:
int all = new Random().Next(2);
var query =
from c in Query<Customer>.All
where all!=0 || c.Id=="ALFKI"
select c;
sarà eseguito utilizzando due diversi query SQL, e quindi - due diversi piani di query:
- Query piano basato su Index Seek (abbastanza veloce), se tutto == 0
- piano di query sulla base di indice di scansione (piuttosto lento), se tutto = 0
caso quando tutto == null, query SQL:
SELECT
[a].[CustomerId],
111 AS [TypeId] ,
[a].[CompanyName]
FROM
[dbo].[Customers] [a]
WHERE((CAST(0 AS bit) <> 0) OR([a].[CustomerId] = 'ALFKI'));
caso quando tutto == null, piano di query:
|--Compute Scalar(DEFINE:([Expr1002]=(111)))
|--Clustered Index Seek(OBJECT:([DO40-Tests].[dbo].[Customers].[PK_Customer] AS [a]), SEEK:([a].[CustomerId]=N'ALFKI') ORDERED FORWARD)
secondo caso (quando tutto! = Null), query SQL: (! Quando tutti = null)
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!
Secondo caso , piano di query:
|--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!
Nota che quasi ogni altro ORM sarebbe compilare questo a una query utilizzando parametro intero:
SELECT
[a].[CustomerId],
111 AS [TypeId] ,
[a].[CompanyName]
FROM
[dbo].[Customers] [a]
WHERE((@p <> 0) OR ([a].[CustomerId] = 'ALFKI'));
-- ^^ parameter is used here
Dal momento che SQL Server (così come la maggior parte delle basi di dati) genera una singola versione di piano di query per una determinata query, è l'unica opzione in questo caso - generare un piano con indice di scansione:
|--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, questa era una spiegazione "rapida" dell'utilità di questa funzione. Torniamo al tuo caso ora.
ramificazione booleano consente di implementare in modo molto semplice:
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;
L'esempio differisce dalla vostra, ma illustra l'idea. Ho usato un modello diverso principalmente per essere in grado di testarlo (il mio esempio si basa sul modello om Northwind).
Questa domanda è:
Non
- una query dinamica, in modo da poter tranquillamente passarlo a
Query.Execute(...)
metodo per ottenerlo eseguito come query compilata.
- Ciononostante, ciascuna esecuzione porterà allo stesso risultato come se ciò avvenisse con "accodamento" a
IQueryable
.
LLBLGen supporta questo, anche 20 di loro. Come le clausole where vengono aggiunte una o più alla volta. – PostMan
@PostMan vuoi fare un esempio? – Luke101
È possibile farlo in Entity Framework, vedere la risposta di tt83 di seguito. La sua risposta è per Linq to SQL ma il concetto è lo stesso per Entity Framework. Non c'è bisogno di buttare fuori il bambino con l'acqua del bagno. –