老外写的一个不错的扩展表达式的文章,原文地址:http://www.albahari.com/nutshell/predicatebuilder.html

Dynamically Composing Expression Predicates

Suppose you wanted to write a LINQ to SQL query that implemented a keyword-style. search. In other words, a query that returned rows whose description contained some or all of a given set of keywords.

We could 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 of the foreach loop.

So far, so good. But this only handles the case where you want to match all of the specified keywords. Suppose instead, we wanted products whose description contains any of the supplied keywords. Our previous approach of chaining Where operators is completely useless! We could instead chain Union operators, but this would be inefficient. The ideal approach is to dynamically construct a lambda expression tree that performs an or-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 our PredicateBuilder class.

Using PredicateBuilder

Here's how to solve the preceding example with PredicateBuilder:

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);
}


PredicateBuilder Source Code

Here's the complete source:



[转]Dynamically Composing Expression Predicates_cssusing System;

[转]Dynamically Composing Expression Predicates_cssusing System.Linq;

[转]Dynamically Composing Expression Predicates_cssusing System.Linq.Expressions;

[转]Dynamically Composing Expression Predicates_cssusing System.Collections.Generic;

[转]Dynamically Composing Expression Predicates_css 

[转]Dynamically Composing Expression Predicates_csspublic static class PredicateBuilder

[转]Dynamically Composing Expression Predicates_sql_07[转]Dynamically Composing Expression Predicates_sql_08...{

[转]Dynamically Composing Expression Predicates_html_09[转]Dynamically Composing Expression Predicates_html_10  public static Expression<Func<T, bool>> True<T> ()  ...{ return f => true;  }

[转]Dynamically Composing Expression Predicates_html_09[转]Dynamically Composing Expression Predicates_html_10  public static Expression<Func<T, bool>> False<T> () ...{ return f => false; }

[转]Dynamically Composing Expression Predicates_iphone_13 

[转]Dynamically Composing Expression Predicates_iphone_13  public static Expression<Func<T, bool>> Or<T> (this Expression<Func<T, bool>> expr1,

[转]Dynamically Composing Expression Predicates_iphone_13                                                      Expression<Func<T, bool>> expr2)

[转]Dynamically Composing Expression Predicates_html_09[转]Dynamically Composing Expression Predicates_html_10  ...{

[转]Dynamically Composing Expression Predicates_iphone_13    var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());

[转]Dynamically Composing Expression Predicates_iphone_13    return Expression.Lambda<Func<T, bool>>

[转]Dynamically Composing Expression Predicates_iphone_13          (Expression.Or (expr1.Body, invokedExpr), expr1.Parameters);

[转]Dynamically Composing Expression Predicates_html_21  }

[转]Dynamically Composing Expression Predicates_iphone_13 

[转]Dynamically Composing Expression Predicates_iphone_13  public static Expression<Func<T, bool>> And<T> (this Expression<Func<T, bool>> expr1,

[转]Dynamically Composing Expression Predicates_iphone_13                                                       Expression<Func<T, bool>> expr2)

[转]Dynamically Composing Expression Predicates_html_09[转]Dynamically Composing Expression Predicates_html_10  ...{

[转]Dynamically Composing Expression Predicates_iphone_13    var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());

[转]Dynamically Composing Expression Predicates_iphone_13    return Expression.Lambda<Func<T, bool>>

[转]Dynamically Composing Expression Predicates_iphone_13          (Expression.And (expr1.Body, invokedExpr), expr1.Parameters);

[转]Dynamically Composing Expression Predicates_html_21  }

[转]Dynamically Composing Expression Predicates_ide_31}


 

PredicateBuilder is also shipped as part of ​​LINQKit​​, a productivity kit for LINQ to SQL.

How it Works

The True and False methods do nothing special: they are simply convenient shortcuts for creating an Expression<Func<T,bool>> that initially evaluates to true or false. So the following:



var predicate = PredicateBuilder.True <Product> ();


is just a shortcut for this:

Expression<Func<Product, bool>> predicate = c => true;


When you’re building a predicate by repeatedly stacking and/or conditions, it’s useful to have a starting point of either true or false (respectively). Our SearchProducts method still works if no keywords are supplied.

The interesting work takes place inside the And and Or methods. We start by invoking the second expression with the first expression’s parameters. An Invoke expression calls another lambda expression using the given expressions as arguments. We can create the conditional expression from the body of the first expression and the invoked version of the second. The final step is to wrap this in a new lambda expression.

More Examples

A useful pattern in writing a data access layer is to create a reusable predicate library. Your queries, then, consist largely of select and orderby clauses, 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 uses PredicateBuilder:



[转]Dynamically Composing Expression Predicates_csspublic partial class Product

[转]Dynamically Composing Expression Predicates_sql_07[转]Dynamically Composing Expression Predicates_sql_08...{

[转]Dynamically Composing Expression Predicates_iphone_13  public static Expression<Func<Product, bool>> ContainsInDescription (

[转]Dynamically Composing Expression Predicates_iphone_13                                                params string[] keywords)

[转]Dynamically Composing Expression Predicates_html_09[转]Dynamically Composing Expression Predicates_html_10  ...{

[转]Dynamically Composing Expression Predicates_iphone_13    var predicate = PredicateBuilder.False<Product>();

[转]Dynamically Composing Expression Predicates_iphone_13    foreach (string keyword in keywords)

[转]Dynamically Composing Expression Predicates_html_09[转]Dynamically Composing Expression Predicates_html_10    ...{

[转]Dynamically Composing Expression Predicates_iphone_13      string temp = keyword;

[转]Dynamically Composing Expression Predicates_iphone_13      predicate = predicate.Or (p => p.Description.Contains (temp));

[转]Dynamically Composing Expression Predicates_html_21    }

[转]Dynamically Composing Expression Predicates_iphone_13    return predicate;

[转]Dynamically Composing Expression Predicates_html_21  }

[转]Dynamically Composing Expression Predicates_ide_31}


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 would 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;


The And and Or methods in boldface resolve to extension methods in PredicateBuilder.

An expression predicate can perform. the equivalent of an SQL subquery by referencing association properties. So, if Product had a child EntitySet called Purchases, we could refine our IsSelling method 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;
}

 

作者:​​菩提树下的杨过​