正如这里已经提到的,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 查询的编译非常昂贵:例如实体框架每秒只能编译大约 500 个相对简单的查询(参见例如ORMBattle.NET http://ormbattle.net/).
另一方面,许多 ORM 工具支持编译查询:
- 你通过一个
IQueryable
以某些为例Compile
方法,并获取一个委托,允许稍后更快地执行它,因为在这种情况下不会发生重新编译。
但如果我们尝试在这里使用这种方法,我们会立即注意到我们的查询实际上是动态的:IQueryable
我们每次执行的操作都可能与前一次不同。查询部分的存在由外部参数的值确定。
那么我们可以执行这样的查询吗?显式缓存?
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 查询执行,因此 - 两个不同的查询计划:
- 基于索引查找的查询计划(相当快),if all==0
- 基于索引扫描的查询计划(相当慢),if all!=0
当all==null时的情况,SQL查询:
SELECT
[a].[CustomerId],
111 AS [TypeId] ,
[a].[CompanyName]
FROM
[dbo].[Customers] [a]
WHERE(( CAST( 0 AS bit ) <> 0 ) OR( [a].[CustomerId] = 'ALFKI' ) );
当all==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)
第二种情况(当all!=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!
第二种情况(当all!=null时),查询计划:
|--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 Server(以及大多数数据库)为特定查询生成单一版本的查询计划,因此在这种情况下它有唯一的选择 - 生成带有索引扫描的计划:
|--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;
这个例子与你的不同,但它说明了这个想法。我使用不同的模型主要是为了能够测试这个(我的示例基于 om Northwind 模型)。
这个查询是:
- 不是动态查询,因此您可以安全地将其传递给
Query.Execute(...)
方法让它作为编译查询执行。
- 然而,每次执行都会产生相同的结果,就好像通过“附加”来完成一样
IQueryable
.