The LINQ of dynamically composing expression predicates

Source: Internet
Author: User

Suppose you want to write a LINQ to SQL or Entity Framework query that implements a keyword-style search. In other words, a query that returns rows whose descriptionContainsSome or all of a given set of keywords.

We can proceed as follows:

 
Iqueryable <product> searchproducts (Params string [] keywords)
{
Iqueryable <product> query = datacontext. Products;

Foreach (string keyword in keywords)
{
String temp = keyword;
Query = query. Where (P => P. description. Contains (temp ));
}
Return query;
}

The temporary variable in the loop is required to avoid the outer variable trap, where the same variable is captured for each iteration ofForeachLoop.

So far, so good. But this only handles the case where you want to matchAllOf the specified keywords. Suppose instead, we wanted products whose description containsAnyOf the supplied keywords. Our previous approach of chaining where operators is completely useless! We cocould instead chain Union operators, but this wocould be inefficient. The ideal approach is to dynamically construct a Lambda Expression Tree that performsOr-Based predicate.

Of all the things that will drive you to manually Constructing Expression trees, the need for dynamic predicates is the most common in a typical business application. fortunately, it's possible to write a set of simple and reusable extension methods that radically simplify this task. this is the role of ourPredicatebuilderClass.

Using predicatebuilder

Here's how to solve the preceding examplePredicatebuilder:

 
Iqueryable <product> searchproducts (Params string [] keywords)
{
VaR predicate = predicatebuilder. False <product> ();

Foreach (string keyword in keywords)
{
String temp = keyword;
Predicate = predicate. Or (P => P. description. Contains (temp ));
}
Return datacontext. Products. Where (predicate );
}

If querying with Entity Framework, change the last line to this:

 
Return objectcontext. Products. Asexpandable (). Where (predicate );

The asexpandable method is part of linqkit (see below ).

The easiest way to experiment with predicatebuilder is with linqpad. linqpad lets you instantly test LINQ queries against a database or local collection and has direct support for predicatebuilder (Press F4 and check 'include predicatebuilder ').

Predicatebuilder source code

Here's the complete source:

Using system;
Using system. LINQ;
Using system. LINQ. expressions;
Using system. Collections. Generic;

Public static class predicatebuilder
{
Public static expression <func <t, bool> true <t> () {return F => true ;}
Public static expression <func <t, bool> false <t> () {return F => false ;}

Public static expression <func <t, bool> or <t> (this expression <func <t, bool> expr1,
Expression <func <t, bool> expr2)
{
VaR invokedexpr = expression. Invoke (expr2, expr1.parameters. Cast <expression> ());
Return expression. Lambda <func <t, bool>
(Expression. orelse (expr1.body, invokedexpr), expr1.parameters );
}

Public static expression <func <t, bool> and <t> (this expression <func <t, bool> expr1,
Expression <func <t, bool> expr2)
{
VaR invokedexpr = expression. Invoke (expr2, expr1.parameters. Cast <expression> ());
Return expression. Lambda <func <t, bool>
(Expression. andalso (expr1.body, invokedexpr), expr1.parameters );
}
}

Predicatebuilder is also shipped as part of linqkit, a productivity kit for LINQ to SQL and Entity Framework.

If you're using LINQ to SQL, you can use the predicatebuilder source code on its own.

If you're using Entity Framework, you'll need the complete linqkit-forAsexpandableFunctionality. You can either reference linqkit. dll or copy linqkit's source code into your application.

How it works

TheTrueAndFalseMethods do nothing special: they are simply convenient shortcuts for creatingExpression <func <t, bool>That initially evaluates to true or false. So the following:

 
VaR predicate = predicatebuilder. True <product> ();

Is just a temporary cut for this:

 
Expression <func <product, bool> predicate = c => true;

When you're building a predicate by repeatedly stackingAnd/OrConditions, it's useful to have a starting point of either true or false (respectively). OurSearchproductsMethod still works if no keywords are supplied.

The interesting work takes place insideAndAndOrMethods. We startInvokingThe second expression with the first expression's parameters.InvokeExpression callanother Lambda expression using the given expressions as arguments. We can create the conditional expression from the body of the first expression andInvokedVersion of the second. The final step is to wrap this in a new Lambda expression.

Entity Framework's query processing pipeline cannot handleInvocation Expressions, Which is why you need to callAsexpandableOn the first object in the query. By calling asexpandable, you activate linqkit's expression visitor class which substitutes invocation expressions with simpler constructs that Entity Framework can understand.

More examples

A useful pattern in writing a data access layer is to create a reusable predicate library. Your queries, then, consist largelySelectAndOrderbyClses, the filtering logic farmed out to your library. Here's a simple example:

 
Public partial class product
{
Public static expression <func <product, bool> isselling ()
{
Return P =>! P. discontinued & P. lastsale> datetime. Now. adddays (-30 );
}
}

We can extend this by adding a method that usesPredicatebuilder:

 
Public partial class product
{
Public static expression <func <product, bool> containsindescription (
Params string [] keywords)
{
VaR predicate = predicatebuilder. False <product> ();
Foreach (string keyword in keywords)
{
String temp = keyword;
Predicate = predicate. Or (P => P. description. Contains (temp ));
}
Return predicate;
}
}

This offers an excellent balance of simplicity and reusability, as well as separating business logic from expression plumbing logic. to retrieve all products whose description contains "BlackBerry" or "iPhone", along with the nokias and ericssons that are selling, you wocould do this:

VaR newkids = product. containsindescription ("BlackBerry", "iPhone ");

VaR classics = product. containsindescription ("Nokia", "Ericsson ")
. And(Product. isselling ());
VaR query =
From P in data. Products. Where (newkids. Or(Classics ))
Select P;

TheAndAndOrMethods in boldface resolve to extension methods inPredicatebuilder.
An expression predicate can perform the equivalent of an SQL subquery by referencing Association properties. So, ifProductHad a child entityset calledPurchases, We cocould refine ourIssellingMethod to return only those products that have sold a minimum number of units as follows:

Public static expression <func <product, bool> isselling (INT minpurchases)
{
Return prod =>
! Prod. discontinued &&
Prod.Purchases. Where (purch => purch. Date> datetime. Now. adddays (-30 ))
. Count ()> = minpurchases;
}
Nesting predicates

Consider the following predicate:

 
P => P. Price> 100 &&
P. Price <1000 &&
(P. description. Contains ("foo") | P. description. Contains ("far "))

Let's say we wanted to build this dynamically. The question is, how do we deal with the parenthesis around the two expressions in the last line?

The answer is to build the parenthesised expression first, and then consume it in the outer expression as follows:

VaR inner = predicatebuilder. False <product> ();
Inner = Inner. Or (P => P. description. Contains ("foo "));
Inner = Inner. Or (P => P. description. Contains ("far "));

VaR outer = predicatebuilder. True <product> ();
Outer = outer. And (P = & gt; p. Price & gt; 100 );
Outer = outer. And (P => P. Price <1000 );
Outer = outer. And (Inner);

Notice that with the inner expression, we start with predicatebuilder.False(Because we're usingOrOperator). With the outer expression, however, we start with predicatebuilder.True(Because we're usingAndOperator ).

Generic predicates

Suppose every table in your database hasValidfromAndValidtoColumns as follows:

Create Table pricelist
(
Id int not null primary key,
Name nvarchar (50) not null,
Validfrom datetime,
Validto datetime
)

To retrieve rows valid asDatetime. Now(The most common case), you 'd do this:

 
From P in pricelists
Where(P. validfrom = NULL | P. validfrom <= datetime. Now )&&
(P. validto = NULL | P. validto> = datetime. Now)
Select P. Name

Of course, that logic in bold is likely to be duplicated into SS multiple queries! No problem: Let's define a method inPricelistClass that returns a reusable expression:

Public static expression <func <pricelist, bool> iscurrent ()
{
Return P => (P. validfrom = NULL | P. validfrom <= datetime. Now )&&
(P. validto = NULL | P. validto> = datetime. Now );
}

OK: Our query is now much simpler:

 
VaR currentpricelists = dB. pricelists. Where (pricelist. iscurrent ());

And with predicatebuilder'sAndAndOrMethods, we can easily introduce other conditions:

 
VaR currentpricelists = dB. pricelists. Where (
Pricelist. iscurrent (). And (P => P. Name. startswith ("")));

But what about all the other tables that also haveValidfromAndValidtoColumns? We don't want to repeat ourIscurrentMethod for every table! Fortunately, we can generalize ourIscurrentMethod with generics.

The first step is to define an interface:

 
Public interface ivalidfromto
{
Datetime? Validfrom {Get ;}
Datetime? Validto {Get ;}
}

Now we can define a single genericIscurrentMethod Using that interface as a constraint:

Public static expression <func <Tentity, Bool> iscurrent <Tentity> ()
WhereTentity: ivalidfromto{Return e => (E. validfrom = NULL | E. validfrom <= datetime. now) & (E. validto = NULL | E. validto> = datetime. now );}

The final step is to implement this interface in each class that supportsValidfromAndValidto. If you're using Visual Studio or a tool like sqlmetal to generate your entity classes, do this in the non-generated half of the partial classes:

 
Public partial class pricelist:Ivalidfromto{}
Public partial class product:Ivalidfromto{}
Using predicatebuilder within linqpad

With linqpad, you can write and test queries much faster than with Visual Studio's build/run/debug cycle. To use predicatebuilder in linqpad with LINQ to SQL:

    • Press F4 and check 'include predicatebuilder'

To use predicatebuilder in linqpad with Entity Framework:

    • Press F4 and add a reference to linqkit. dll

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.