Jak już wspomniano, LINQ pozwala rozszerzyć dowolne zapytanie, po prostu dodając do niego więcej kryteriów.
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;
I tak dalej. To podejście działa idealnie. Ale prawdopodobnie wiesz, że kompilacja zapytań LINQ jest dość kosztowna: Entity Framework może skompilować około 500 stosunkowo prostych zapytań na sekundę (patrz np. ORMBattle.NET).
Z drugiej strony, wiele narzędzi ORM obsługują skompilowane zapytania:
- przekazać instancję
IQueryable
pewnym Compile
metody i uzyskać delegata pozwalając, aby go wykonać znacznie szybciej później, bo nie ponownej kompilacji będzie występować w ta sprawa.
Ale jeśli chcielibyśmy spróbować użyć tej metody tutaj, od razu zauważy, że nasze zapytanie jest faktycznie dynamiczna: IQueryable
wykonujemy za każdym razem może się różnić od poprzedniej. Obecność części zapytań jest określana przez wartości parametrów zewnętrznych.
Czy możemy więc wykonać takie zapytania, jak skompilowane bez np. jawne buforowanie?
DataObjects.Net 4 obsługuje tak zwaną funkcję "boolean branching". Oznacza to, że każde ciągłe wyrażenie boolowskie jest oceniane podczas kompilacji zapytań, a jego rzeczywista wartość jest wprowadzana do zapytania SQL jako prawdziwa stała boolowska (tj. Nie jako wartość parametru lub jako wyrażenie wykorzystujące parametry).
Ta funkcja umożliwia łatwe generowanie różnych planów zapytań na podstawie wartości takich wyrażeń logicznych. Na przykład.ten kod:
int all = new Random().Next(2);
var query =
from c in Query<Customer>.All
where all!=0 || c.Id=="ALFKI"
select c;
będą wykonywane przy użyciu dwóch różnych zapytań SQL, a co za tym idzie - dwa różne plany zapytanie:
- Query Plan oparty na indeksie seek (dość szybko), jeśli wszystko == 0
- planu kwerend na podstawie skanowania indeksu (dość wolno), jeśli wszystko = 0
przypadku, gdy wszyscy == null, zapytań SQL:
SELECT
[a].[CustomerId],
111 AS [TypeId] ,
[a].[CompanyName]
FROM
[dbo].[Customers] [a]
WHERE((CAST(0 AS bit) <> 0) OR([a].[CustomerId] = 'ALFKI'));
przypadku, gdy wszyscy == null, plan zapytania:
|--Compute Scalar(DEFINE:([Expr1002]=(111)))
|--Clustered Index Seek(OBJECT:([DO40-Tests].[dbo].[Customers].[PK_Customer] AS [a]), SEEK:([a].[CustomerId]=N'ALFKI') ORDERED FORWARD)
drugim przypadku (gdy wszystko! = Null), zapytań SQL: (! = Null, gdy wszystko)
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!
Drugi przypadek , plan zapytania:
|--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!
Zauważ, że prawie każdy inny ORM byłoby skompilować to do zapytania wykorzystując parametr całkowita:
SELECT
[a].[CustomerId],
111 AS [TypeId] ,
[a].[CompanyName]
FROM
[dbo].[Customers] [a]
WHERE((@p <> 0) OR ([a].[CustomerId] = 'ALFKI'));
-- ^^ parameter is used here
Ponieważ SQL Server (jak większość baz danych) generuje jedną wersję planu kwerend dla danego zapytania, ma jedyną opcją w tym przypadku - generowania planu ze skanowania indeksu:
|--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, to było "szybkie" wyjaśnienie przydatności tej funkcji. Wróćmy teraz do twojej sprawy.
Boolean rozgałęzienie pozwala wykorzystać je w bardzo prosty sposób:
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;
Przykład różni się od Ciebie, ale ilustruje ten pomysł. Użyłem innego modelu głównie, aby móc to przetestować (mój przykład oparty jest na modelu om Northwind).
To zapytanie jest:
- Nie dynamiczny zapytań, dzięki czemu można bezpiecznie przekazać go do
Query.Execute(...)
metody, aby to realizowane jako skompilowanego zapytania.
- Niemniej jednak każde jego wykonanie doprowadzi do tego samego wyniku, co w przypadku "dołączania" do
IQueryable
.
LLBLGen obsługuje to, nawet 20 z nich. Jako klauzule where są dodawane jeden lub wiele naraz. – PostMan
@PostMan miałbyś tego przykład? – Luke101
Możesz to zrobić w Entity Framework, patrz odpowiedź tt83 poniżej. Jego odpowiedź jest dla Linq na SQL, ale koncepcja jest taka sama dla Entity Framework. Nie trzeba wylewać dziecka z kąpielą. –