linq 动态组合条件
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:
- Press F4 and add a reference to LinqKit.dll
linq 动态组合条件的更多相关文章
- Entity Framework Linq 动态组合where条件
public static class PredicateExtensions { public static Expression<Func<T, bool>> True&l ...
- Linq 动态组合排序(Lambda)
最近有个项目需要做一个排班的功能,需要对排班的数据按不同的规则进行排序:因为排序规则是动态变化的,所以不太适合放到数据库中(临时表)中处理: 所以考虑使用Linq的排序方式(按不同的条件判断条件组合排 ...
- spring mvc 4.3.2 + mybatis 3.4.1 + mysql 5.7.14 +shiro 幼儿园收费系统 之 动态组合条件查询
实际应用中,系统设计无法预料到用户最终的查询条件是怎样的.通常的做法是给出一些限制死的查询条件让用户查询.业务稍有改动,就要重新设计界面,增加查询字段等,费时费力. 比较好的做法是,除了常用的查询外, ...
- Linq动态查询与模糊查询 ---转
Linq动态查询与模糊查询(带源码示例) 继LINQ动态组合查询PredicateExtensions讲解 ----- 在用上面的方法时遇到了些问题 解决 LINQ to Entities 不支持 L ...
- EntityFramework动态多条件查询与Lambda表达式树
在常规的信息系统中, 我们有需要动态多条件查询的情况, 例如UI上有多个选择项可供用户选择多条件查询数据. 那么在.net平台Entity Framework下, 我们用Lambd ...
- EntityFramework动态组合多排序字段
前言:在使用EF当中,肯定会遇到动态查询的需求,建立一个公共调用的动态组合表达式查询也是必不可少的,以下是建立动态组合多排序字段做个记录,供以后调用 1.建立一个结构,用于多个排序字段组合,这个结构体 ...
- Mysql动态多条件查询
动态多条件查询是一类经常遇到的问题. 在Mysql里面可以用语句简单的解决. SELECT * FROM product WHERE price = IF('{0}' = '', price, '{0 ...
- 浅析Entity Framework Core2.0的日志记录与动态查询条件
前言 Entity Framework Core 2.0更新也已经有一段时间了,园子里也有不少的文章.. 本文主要是浅析一下Entity Framework Core2.0的日志记录与动态查询条件 去 ...
- .netLinq动态Where条件
文章介绍全网最佳方式使用EF+Linq实现多条件组合查询:代码中没有使用到网上主流自定义Expression表达式而是采用linq原生态功能编写示例直观.易懂,且有效解决自定义Expression不支 ...
随机推荐
- PHP实现简单的监控nginx日志文件功能
此功能是为了实现,定时监控nginx生成的日志数据,并将新增的数据提交到一个接口(比如大数据的接口,让大数据来进行分析) define("MAX_SHOW", 8192*5); / ...
- window.location 对象所包含的属性
window.location 对象所包含的属性 属性 描述 hash 从井号 (#) 开始的 URL(锚) host 主机名和当前 URL 的端口号 hostname 当前 URL 的主机名 hre ...
- [Git] Git 常用技巧
版本对比 1. 对比两个 COMMIT git diff <commit> <commit> 2. 对比 COMMIT 和父 COMMIT git diff <commi ...
- 一款 .NET 下的轻量级 REST 和 HTTP API 客户端 - RestSharp
项目地址:https://github.com/restsharp/RestSharp Features Supports .NET 3.5+, Silverlight 4, Windows Phon ...
- IT痴汉的工作现状24-Just for fun
早在大学一开始我进行Linux的学习了,那时大家都跟Windows Xp玩的火热,而我从来就不走寻常路,在XP上安装了VMware虚拟机搞起了Linux的探索.这简直让我眼界大开,每天都和那么多的国外 ...
- 写出优美代码的两个方式:一步到位VS迭代优化
最近把手头这个安卓APP的所有事务性方法都写完了,有了以下体会,新手体会,老鸟轻拍 想写成优美代码的人一般都会有这样的想法: 一定要在写每一句代码,写每一个方法,构造每一个类的时候,都要记得优化: ...
- spring日记------部署环境、写demo
一.安装jdk1.7 祥见http://zhinan.sogou.com/guide/detail/?id=1610006590 二.创建web项目 略 三.配置ssm环境 3.1添加spring.m ...
- weblogic 12C 数据源配置出错的解决办法
驱动程序类名称: 11G 10.3.6与12G数据源配置有很大区别,整个一天才搞明白. 如有疑问可留言:http://www.cnblogs.com/endv/p/4110798.html 配 ...
- enum to IEnumerable<T>
想foreach一个枚举,发觉它特别复杂,因此先把枚举转换为IEnumerable接口之后,简单多了.下面是枚举(enum)转换为IEnumerable接口方法: 使用:修改这篇<程序一旦发觉写 ...
- 利用代码生成工具Database2Sharp设计数据编辑界面
在Winform程序开发中,界面部分的开发工作量一般是比较大的,特别是表的字段数据比较多的情况下,数据编辑界面所需要的繁琐设计和后台逻辑处理工作量更是直线上升,而且稍不注意,可能很多处理有重复或者错误 ...