As already mentioned here, LINQ allows you to expand any query by simply adding more criteria to it.
var query = from x in xs where x==1 select x; if (mustAddCriteria1) query = from x in query where ...
And so on. This approach works just fine. But, most likely, you know that compiling LINQ queries is quite expensive: for example. Entity Framework can compile about 500 relatively simple queries per second (see, for example, ORMBattle.NET ).
On the other hand, many ORM tools support compiled queries:
- You pass an
IQueryable instance for some Compile method and get a delegate that allows you to execute it much faster later, because in this case there will be no recompilation.
But if we try to use this approach here, we immediately notice that our query is actually dynamic: the IQueryable that we execute each time may differ from the previous one. The presence of query parts is determined by the values ββof external parameters.
So, we can execute such requests as compiled without eg. explicit caching?
DataObjects.Net 4 supports the so-called Boolean branching function. This means that any constant Boolean expression is evaluated at the time the query is compiled, and its actual value is entered into the SQL query as a true Boolean constant (i.e. Not as a parameter value or as an expression using parameters).
This function makes it easy to generate various query plans depending on the values ββof such Boolean expressions. For example. this code:
int all = new Random().Next(2); var query = from c in Query<Customer>.All where all!=0 || c.Id=="ALFKI" select c;
will be executed using two different SQL queries and, therefore, two different query plans:
- Query plan based on index search (pretty fast), if all == 0
- Query plan based on index scan (rather slow) if all! = 0
Case when all == null, SQL query:
SELECT [a].[CustomerId], 111 AS [TypeId] , [a].[CompanyName] FROM [dbo].[Customers] [a] WHERE(( CAST( 0 AS bit ) <> 0 ) OR( [a].[CustomerId] = 'ALFKI' ) );
Case when everything == null, query plan:
|
Second case (when all! = Null), SQL query:
SELECT [a].[CustomerId], 111 AS [TypeId] , [a].[CompanyName] FROM [dbo].[Customers] [a] WHERE(( CAST( 1 AS bit ) <> 0 ) OR( [a].[CustomerId] = 'ALFKI' ) );
The second case (when everything! = Null), the query plan:
|--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!
Note that almost any other ORM will compile this for a query using an integer parameter:
SELECT [a].[CustomerId], 111 AS [TypeId] , [a].[CompanyName] FROM [dbo].[Customers] [a] WHERE(( @p <> 0 ) OR ( [a].[CustomerId] = 'ALFKI' ) ); -- ^^ parameter is used here
Since SQL Server (like most databases) generates a single version of a query plan for a specific query, it has the only option in this case - create a plan with indexing:
|--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'))
Well, that was a βquickβ explanation of the usefulness of this feature. Now get back to your business.
Logical branching makes it very easy to implement:
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;
The example is different from yours, but it illustrates the idea. I used another model mainly to test it (my example is based on the Northwind model).
This request:
- This is not a dynamic query, so you can safely pass it to the
Query.Execute(...) method Query.Execute(...) that it Query.Execute(...) as a compiled query. - However, each execution will produce the same result as if it were done with an βaddβ to
IQueryable .