Как уже упоминалось здесь, LINQ позволяет расширить любой запрос, просто добавив к нему дополнительные критерии.
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;
И так далее. Этот подход работает отлично. Но, скорее всего, вы знаете, что компиляция запросов LINQ довольно дорога: например. Entity Framework может скомпилировать около 500 относительно простых запросов в секунду (см., Например, ORMBattle.NET).
С другой стороны, многие инструментами ORM поддержки скомпилированных запросов:
- Вы пройти
IQueryable
экземпляр некоторых Compile
метода, и получить делегат, позволяющий выполнить ее намного быстрее позже, потому что перекомпиляция не будет происходить в Это дело.
Но если бы попытаться использовать этот подход здесь, мы сразу замечаем, что наш запрос на самом деле динамический: IQueryable
мы выполняем каждый раз, может отличаться от предыдущей. Наличие частей запроса определяется значениями внешних параметров.
Итак, мы можем выполнить такие запросы, как скомпилированные без eg.g. явное кэширование?
DataObjects.Net 4 поддерживает так называемую функцию булевского разветвления. Это означает, что любое константное булево выражение оценивается во время компиляции запроса, и его фактическое значение вводится в SQL-запрос как истинная булева константа (т. Е. Не как значение параметра или как выражение, использующее параметры).
Эта функция позволяет с легкостью генерировать различные планы запросов в зависимости от значений таких булевых выражений. Например.этот код:
int all = new Random().Next(2);
var query =
from c in Query<Customer>.All
where all!=0 || c.Id=="ALFKI"
select c;
будет выполняться с использованием двух различных запросов SQL, и таким образом - два различных плана запроса:
- Query план, основанный на индексе Seek (довольно быстро), если все == 0
- план запроса на основе сканирования индекса (довольно медленно), если все = 0
Случай, когда все == NULL, SQL-запрос:
SELECT
[a].[CustomerId],
111 AS [TypeId] ,
[a].[CompanyName]
FROM
[dbo].[Customers] [a]
WHERE((CAST(0 AS bit) <> 0) OR([a].[CustomerId] = 'ALFKI'));
Случай, когда все == NULL, план запроса:
|--Compute Scalar(DEFINE:([Expr1002]=(111)))
|--Clustered Index Seek(OBJECT:([DO40-Tests].[dbo].[Customers].[PK_Customer] AS [a]), SEEK:([a].[CustomerId]=N'ALFKI') ORDERED FORWARD)
Второй случай (когда все! = NULL), SQL-запрос: (! Когда все = нуль)
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!
Второй случай , план запроса:
|--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!
Обратите внимание, что почти любой другой ORM будет скомпилировать это запрос с использованием целочисленного параметра:
SELECT
[a].[CustomerId],
111 AS [TypeId] ,
[a].[CompanyName]
FROM
[dbo].[Customers] [a]
WHERE((@p <> 0) OR ([a].[CustomerId] = 'ALFKI'));
-- ^^ parameter is used here
Так как SQL-сервер (а также большинство баз данных) генерирует один вариант плана запроса для конкретного запроса, он имеет единственный вариант в данном случае - генерировать план с индексом сканирования:
|--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'))
Хорошо, это было «быстрое» объяснение полезности этой функции. Вернемся к вашему делу.
Логическое ветвление позволяет реализовать очень простым способом:
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;
пример отличается от вашего, но она иллюстрирует идею. В основном я использовал другую модель, чтобы проверить ее (мой пример основан на модели Northwind).
Этот запрос:
- Не динамический запрос, так что вы можете безопасно передать его
Query.Execute(...)
метод, чтобы получить его выполнен в виде скомпилированного запроса.
- Тем не менее каждое его выполнение приведет к такому же результату, как если бы это было сделано с «добавлением» к
IQueryable
.
LLBLGen поддерживает это, даже 20 из них. Поскольку предложения where добавляются один или несколько за раз. – PostMan
@PostMan у вас есть пример этого? – Luke101
Вы можете сделать это в Entity Framework, см. Ниже ответ tt83. Его ответ заключается в Linq to SQL, но концепция такая же для Entity Framework. Не нужно выбрасывать ребенка с водой для ванны. –