http://www.albahari.com/nutshell/predicatebuilder.aspx

Dynamically Composing Expression Predicates

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 description contains some 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 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);
}

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 -
for the AsExpandable functionality.
You can either reference LINQKit.dll or copy LINQKit's source code into your application.

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.

Entity Framework's query processing pipeline cannot handle invocation expressions, which is why you need to call AsExpandable on 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 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:

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

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 => p.Price > 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 using the Or operator). With the outer expression, however, we start with PredicateBuilder.True (because we're using the And operator).

Generic Predicates

Suppose every table in your database has ValidFrom and ValidTo columns as follows:

create table PriceList
(
ID int not null primary key,
Name nvarchar(50) not null,
ValidFrom datetime,
ValidTo datetime

)

To retrieve rows valid as of DateTime.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 across multiple queries! No problem: let's define a method in the PriceList class 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's And and Or methods, we can easily introduce other conditions:

var currentPriceLists = db.PriceLists.Where (
PriceList.IsCurrent().And (p => p.Name.StartsWith ("A")));

But what about all the other tables that also have ValidFrom and ValidTo columns? We don't want to repeat our IsCurrent method for every table! Fortunately, we can generalize our IsCurrent method 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 generic IsCurrent method using that interface as a constraint:

public static Expression<Func<TEntity, bool>> IsCurrent<TEntity>()
where TEntity : 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 supports ValidFrom and ValidTo. 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:

linq 动态组合条件的更多相关文章

  1. Entity Framework Linq 动态组合where条件

    public static class PredicateExtensions { public static Expression<Func<T, bool>> True&l ...

  2. Linq 动态组合排序(Lambda)

    最近有个项目需要做一个排班的功能,需要对排班的数据按不同的规则进行排序:因为排序规则是动态变化的,所以不太适合放到数据库中(临时表)中处理: 所以考虑使用Linq的排序方式(按不同的条件判断条件组合排 ...

  3. spring mvc 4.3.2 + mybatis 3.4.1 + mysql 5.7.14 +shiro 幼儿园收费系统 之 动态组合条件查询

    实际应用中,系统设计无法预料到用户最终的查询条件是怎样的.通常的做法是给出一些限制死的查询条件让用户查询.业务稍有改动,就要重新设计界面,增加查询字段等,费时费力. 比较好的做法是,除了常用的查询外, ...

  4. Linq动态查询与模糊查询 ---转

    Linq动态查询与模糊查询(带源码示例) 继LINQ动态组合查询PredicateExtensions讲解 ----- 在用上面的方法时遇到了些问题 解决 LINQ to Entities 不支持 L ...

  5. EntityFramework动态多条件查询与Lambda表达式树

              在常规的信息系统中, 我们有需要动态多条件查询的情况, 例如UI上有多个选择项可供用户选择多条件查询数据. 那么在.net平台Entity Framework下, 我们用Lambd ...

  6. EntityFramework动态组合多排序字段

    前言:在使用EF当中,肯定会遇到动态查询的需求,建立一个公共调用的动态组合表达式查询也是必不可少的,以下是建立动态组合多排序字段做个记录,供以后调用 1.建立一个结构,用于多个排序字段组合,这个结构体 ...

  7. Mysql动态多条件查询

    动态多条件查询是一类经常遇到的问题. 在Mysql里面可以用语句简单的解决. SELECT * FROM product WHERE price = IF('{0}' = '', price, '{0 ...

  8. 浅析Entity Framework Core2.0的日志记录与动态查询条件

    前言 Entity Framework Core 2.0更新也已经有一段时间了,园子里也有不少的文章.. 本文主要是浅析一下Entity Framework Core2.0的日志记录与动态查询条件 去 ...

  9. .netLinq动态Where条件

    文章介绍全网最佳方式使用EF+Linq实现多条件组合查询:代码中没有使用到网上主流自定义Expression表达式而是采用linq原生态功能编写示例直观.易懂,且有效解决自定义Expression不支 ...

随机推荐

  1. `cocos2dx非完整` 开始自己的FW模块

    上一篇的文章中说到了一些个人习惯的东西以及一些简单的项目配置,这一篇文章我们来进一步完善一些东西.首先,打开编译以后的客户端执行,会看到一大堆的fileutils加载luac文件的提示,在终端显示一大 ...

  2. java简单统计.java文件中的有效代码行,空行,注释行

    package regxdemo; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundExc ...

  3. 爬虫技术 -- 进阶学习(七)简单爬虫抓取示例(附c#代码)

    这是我的第一个爬虫代码...算是一份测试版的代码.大牛大神别喷... 通过给定一个初始的地址startPiont然后对网页进行捕捉,然后通过正则表达式对网址进行匹配. List<string&g ...

  4. SQL Server里等待统计(Wait Statistics)介绍

    在今天的文章里我想详细谈下SQL Server里的统计等待(Wait Statistics),还有她们如何帮助你立即为什么你的SQL Server当前很慢.一提到性能调优,对我来说统计等待是SQL S ...

  5. ASP.NET HTTP模拟提交通用类 GET POST

     用法: WebRequestSugar ws = new WebRequestSugar(); //可选参数 //ws.SetAccept //ws.SetContentType //ws.SetC ...

  6. UWP开发入门(十九)——10分钟学会在VS2015中使用Git

    写程序必然需要版本控制,哪怕是个人项目也是必须的.我们在开发UWP APP的时候,VS2015默认提供了对微软TFS和Git的支持.考虑到现在Git很火,作为微软系的程序员也不得不学一点防身,以免被开 ...

  7. WebApi 集成 Swagger

    1. Swagger(俗称:丝袜哥)是什么东西? Swagger 是一个规范和完整的框架,用于生成.描述.调用和可视化 RESTful 风格的 Web 服务.总体目标是使客户端和文件系统作为服务器以同 ...

  8. c# XML序列化与反序列化

    c# XML序列化与反序列化 原先一直用BinaryFormatter来序列化挺好,可是最近发现在WinCE下是没有办法进行BinaryFormatter操作,很不爽,只能改成了BinaryWrite ...

  9. 0422 发现( 数学口袋精灵)bug

    团队博客地址: 甘佳萍:http://www.cnblogs.com/gjpg/ 李鹏飞:http://www.cnblogs.com/l549023320/ 赵创佳:http://www.cnblo ...

  10. 环信SDK与Apple Watch的结合(1)

    该系列是记录在apple watch上开发IM,用到了最近挺流行的环信IM SDK. 一.先来一段网上随处可查到的信息: 1.两种分辨率 1.65寸 312*390 1.5寸 272*340 2.开发 ...