linq(lambd) 追加表达式 linqkit
动态组合表达式谓词
假设您要编写实现 SQL 的 LINQ to SQL 或实体框架查询 关键字样式搜索。换句话说,返回其行的查询 描述包含给定集合的部分或全部 的关键字。
我们可以按以下步骤进行:
IQueryable<Product> SearchProducts (params string[] keywords) { IQueryable<Product> query = dataContext.Products; foreach (string keyword in keywords) query = query.Where (p => p.Description.Contains (keyword)); return query; }
目前为止,一切都好。但这只处理您想要的情况 以匹配所有指定的关键字。假设 相反,我们想要描述包含任何提供的关键字的产品。我们以前的链接方法 其中 运算符完全没用!我们可以改为连锁联盟运营商,但是 这将是低效的。理想的方法是动态构造一个 执行基于 OR 的 lambda 表达式树 谓语。
在所有会驱使您手动构建的事情中 表达式树,对动态谓词的需求是最常见的 典型的业务应用程序。幸运的是,可以编写一组 简单且可重用的扩展方法从根本上简化了此任务。这是 我们的谓词生成器类的角色。
使用 PredicateBuilder
下面介绍如何使用 PredicateBuilder 解决前面的示例:
IQueryable<Product> SearchProducts (params string[] keywords) { var predicate = PredicateBuilder.False<Product>(); foreach (string keyword in keywords) predicate = predicate.Or (p => p.Description.Contains (keyword)); return dataContext.Products.Where (predicate); }
如果使用实体框架进行查询,请将最后一行更改为:
return objectContext.Products.AsExpandable().Where (predicate);
AsExpandable 方法是 LINQKit 的一部分(见下文)。
试验 PredicateBuilder 的最简单方法是使用 LINQPad。 LINQPad 允许您针对数据库或本地集合即时测试 LINQ 查询,并直接支持 PredicateBuilder(按 F4 并选中“包含谓词生成器”)。
PredicateBuilder 源代码
这是完整的来源:
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 也作为 LINQKit 的一部分提供,LINQKit 是 LINQ to SQL 和实体框架的生产力工具包。
如果使用 LINQ to SQL,则可以单独使用 PredicateBuilder 源代码。
如果你是 使用实体框架,您将需要完整的 LINQKit - 用于扩展功能。 您可以引用 LINQKit.dll也可以将 LINQKit 的源代码复制到应用程序中。
工作原理
True 和 False 方法没有什么特别之处:它们只是创建表达式<Func<T,bool>>的方便快捷方式,该表达式最初计算结果为 对或错。所以以下内容:
var predicate = PredicateBuilder.True <Product> ();
只是一个快捷方式:
Expression<Func<Product, bool>> predicate = c => true;
当您通过重复堆叠和/或条件来构建谓词时, 将起点设置为 true 或 false (分别)很有用。如果没有关键字,我们的搜索产品方法仍然有效 提供。
有趣的工作发生在 And 和 Or 方法中。我们首先用第一个表达式调用第二个表达式 表达式的参数。调用表达式调用 另一个使用给定表达式作为参数的 lambda 表达式。我们可以 从第一个表达式的主体和第二个表达式的调用版本创建条件表达式。最后一步是 将其包装在新的 lambda 表达式中。
实体框架的查询处理管道无法 处理调用表达式,这就是需要对查询中的第一个对象调用 AsExpandable 的原因。通过调用 AsExpandable,您可以 激活 LINQKit 的表达式访问者类,用于替换调用 具有实体框架可以理解的更简单构造的表达式。
更多示例
编写数据访问层的一个有用模式是创建一个 可重用的谓词库。然后,您的查询主要由选择和排序子句组成, 筛选逻辑已扩展到您的库。下面是一个简单的示例:
public partial class Product { public static Expression<Func<Product, bool>> IsSelling() { return p => !p.Discontinued && p.LastSale > DateTime.Now.AddDays (-30); } }
我们可以通过添加一个使用 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) predicate = predicate.Or (p => p.Description.Contains (keyword)); return predicate; } }
这提供了简单性和可重用性的完美平衡, 以及将业务逻辑与表达式管道逻辑分离。检索 描述中包含“黑莓”或“iPhone”的所有产品,以及 正在销售的诺基亚和爱立信,你会这样做:
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;
粗体中的 And 和 Or 方法解析为 PredicateBuilder 中的扩展方法。
表达式谓词可以通过以下方式执行 SQL 子查询的等效项: 引用关联属性。所以,如果产品有 一个名为“购买”的子实体集,我们可以优化我们的 IsSelling 方法,以仅返回那些 已售出最低单位数量,如下所示:
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; }
嵌套谓词
请考虑以下谓词:
p => p.Price > 100 && p.Price < 1000 && (p.Description.Contains ("foo") || p.Description.Contains ("far"))
假设我们想动态构建它。问题是, 我们如何处理最后一行中两个表达式周围的括号?
答案是先构建括号表达式,然后 然后在外部表达式中使用它,如下所示:
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);
请注意,对于内部表达式,我们从 谓词生成器。False(因为我们使用的是 Or 运算符)。跟 然而,外部表达式,我们从 PredicateBuilder 开始。True(因为我们使用的是 And 运算符)。
泛型谓词
假设数据库中的每个表都有 ValidFrom 和 ValidTo 列,如下所示:
create table PriceList ( ID int not null primary key, Name nvarchar(50) not null, ValidFrom datetime, ValidTo datetime )
检索自 DateTime.Now 起有效的行(最 常见情况),您将这样做:
from p in PriceLists where (p.ValidFrom == null || p.ValidFrom <= DateTime.Now) && (p.ValidTo == null || p.ValidTo >= DateTime.Now) select p.Name
当然,粗体字的逻辑很可能会在 多个查询!没问题:让我们在 PriceList 类中定义一个返回可重用表达式的方法:
public static Expression<Func<PriceList, bool>> IsCurrent() { return p => (p.ValidFrom == null || p.ValidFrom <= DateTime.Now) && (p.ValidTo == null || p.ValidTo >= DateTime.Now); }
好的:我们的查询现在简单多了:
var currentPriceLists = db.PriceLists.Where (PriceList.IsCurrent());
使用PredicateBuilder的And and Or 方法,我们 可以轻松引入其他条件:
var currentPriceLists = db.PriceLists.Where ( PriceList.IsCurrent().And (p => p.Name.StartsWith ("A")));
但是,所有其他同时具有 ValidFrom 和 ValidTo 列的表呢?我们不想重复我们的 IsCurrent 方法 每张桌子!幸运的是,我们可以推广我们的 IsCurrent 方法 泛 型。
第一步是定义接口:
public interface IValidFromTo { DateTime? ValidFrom { get; } DateTime? ValidTo { get; } }
现在我们可以定义一个通用的 IsCurrent 方法,使用 该接口作为约束:
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); }
最后一步是在每个类中实现此接口 支持 ValidFrom 和 ValidTo。如果您使用的是 Visual Studio 或 像 SqlMetal 这样的工具来生成你的实体类,在未生成的一半中执行此操作 分部类:
public partial class PriceList : IValidFromTo { } public partial class Product : IValidFromTo { }
在 LINQPad 中使用 PredicateBuilder
使用 LINQPad,您可以编写和测试查询 比Visual Studio的构建/运行/调试周期快得多。要使用 PredicateBuilder in LINQPad with LINQ to SQL:
- 按 F4 并选中“包含谓词生成器”
若要在 LINQPad 中使用 PredicateBuilder 和实体框架,请执行以下操作:
- 按 F4 并添加对 LinqKit 的引用.dll