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不支 ...
随机推荐
- 安卓开发笔记——Fragment+ViewPager组件(高仿微信界面)
什么是ViewPager? 关于ViewPager的介绍和使用,在之前我写过一篇相关的文章<安卓开发复习笔记——ViewPager组件(仿微信引导界面)>,不清楚的朋友可以看看,这里就不再 ...
- Til the Cows Come Home(最短路)
Til the Cows Come Home Time Limit:1000MS Memory Limit:65536KB 64bit IO Format:%I64d & %I ...
- 【NS2仿真】TCP与UDP混合
# # ftp # \ # tcp # \ # n0 sink # \ / # \ / # n1---5M 2ms---n3 # / \ # / \ # n2 null # / # udp # / # ...
- Office Online简介
一.什么是Office Online Office Online 将 Microsoft Office 体验扩展到了 Web 浏览器,这使您可以直接在存储文档的 SharePoint 网站上处理文档, ...
- Python 的字符串格式化和颜色控制
(部分内容源自武神博客和网络收集.) Python的字符串格式化有两种方式: 百分号方式.format方式 百分号的方式相对来说比较老,而format方式则是比较先进的方式,企图替换古老的方式,目前两 ...
- Java魔法堂:String.format详解
目录 一.前言 二.重载方法 三.占位符 四.对字符.字符串进行格式化 五.对整数进行格式化 六.对浮点数进行格式化 七.对日期时间进行格式化 ...
- 前端翻译:Activating Browser Modes with Doctype
一.前言 原本备份: http://www.cnblogs.com/fsjohnhuang/p/3830623.html 由于本人英语能力有限,译本内容难免有误,望各位指正! 本译文不含附录部分,请知 ...
- XML to Entity
public static T GetEntityByXml<T>(string xml, string rootNode=null) where T : new() { if (stri ...
- Copy和MutableCopy
实现拷贝的方法 -copy: 1.只会产生不可变的副本对象(比如:NSString) 2.[NSMutableString copy] 产品一个不可变的nsstring对象 -mutaleCopy: ...
- 《构建之法》第8、9、10章 读书笔记和Sprint总结
第八章:需求分析 这章主要解析了需求的多面方面,不同的项目需要不同的手段,真正的需求稍纵即逝,需要靠火眼金睛和敏捷的身手来发现并抓住它们.另外,很多时候用户并不知道自己确切的需求,或者不愿意表达完整的 ...