首页 > 其他分享 >linq(lambd) 追加表达式 linqkit

linq(lambd) 追加表达式 linqkit

时间:2023-04-22 14:24:04浏览次数:46  
标签:return PredicateBuilder lambd linq linqkit var Expression public 表达式

动态组合表达式谓词

假设您要编写实现 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

 

转自:C# 简而言之 - PredicateBuilder (albahari.com)

标签:return,PredicateBuilder,lambd,linq,linqkit,var,Expression,public,表达式
From: https://www.cnblogs.com/SmallChen/p/17342996.html

相关文章

  • 为什么匿名内部类可以实例化并实现抽象方法?lambda表达式是简化了匿名内部类的实现过程
    为什么匿名内部类可以实例化并实现抽象方法?在Java中,接口是一种特殊的抽象类型,它只定义了一个或多个抽象方法。接口不能被实例化,但是我们可以使用匿名内部类来实现接口并创建一个具体的对象。匿名内部类是一种没有名字的局部内部类,它可以在定义的同时创建一个实例对象。因此,当我......
  • Maybatis-Plus lambdaQuery与lambdaUpdate
    lambdaQuery与lambdaUpdate1.等于//EQ就是EQUAL等于taskFlowService.lambdaQuery().eq(TaskFlow::getCreateTime,DateUtil.now())2.不等于//NE就是NOTEQUAL不等于taskFlowService.lambdaQuery().ne(TaskFlow::getCreateTime,DateUtil.now());3.大于//GT就是......
  • #yyds干货盘点#python之 Lambda 表达式
    lambda 关键字用于创建小巧的匿名函数。lambda a, b: a+b 函数返回两个参数的和。Lambda函数可用于任何需要函数对象的地方。在语法上,匿名函数只能是单个表达式。在语义上,它只是常规函数定义的语法糖。与嵌套函数定义一样,lambda函数可以引用包含作用域中的变量:>>>defmake_......
  • linq的妙用 分组 交换索引
    //////Splitsacollectionofobjectsintonpageswithan(forexample,ifIhavealistof45shoesandsay'shoes.Split(5)'Iwillnowhave4pagesof10shoesand1pageof5shoes.//////Thetypeofobjectthecollectionshouldcontain.......
  • Java之Lambda使用
    目录Java之Lambda使用流操作1.Java中filter和removeIf.2.Java中forEach使用.3.Java中Peek使用.4.Java中Map使用.5.Java中MapTo...使用.6.Java中Distinct使用.7.Java中Sorted使用.8.Java中skip使用.非流操作9.Java中groupingBy使用.10.Java中Collect使用.11.Java中FindFirst、findA......
  • What's PLinq? how to use it?
    What'sPLinq?howtouseit?PLinqstandsfor"ParallelLINQ",whichisaparallelimplementationofLINQ(Language-IntegratedQuery)in.NET.ItallowsdeveloperstoperformLINQqueriesinparallelbyautomaticallypartitioningtheinput......
  • Lambda表达式
    Lambda表达式Lambda表达式(LambdaExpression)是C++11引入的一个“语法糖”,可以方便快捷地创建一个“函数对象”。从C++11开始,C++有三种方式可以创建/传递一个可以被调用的对象:函数指针仿函数(Functor)Lambda表达式函数指针函数指针是从C语言老祖宗继承下来的东西,比......
  • Linq专题之提高编码效率—— 第一篇 Aggregate方法
    的几个方法,这个系列我会带领大家看遍linq,好的,废话不多说,先从Aggregate这个貂毛说起。 一:应用场景前不久在写一个项目的时候,我需要捞取营销活动,刚好营销活动有两个类型,一种是普通活动,一个是触发式活动,由于存放在两张表中,并且捞取之后需要做一些实体的转存,等等计算,所以就有了类......
  • Linq专题之提高编码效率—— 第二篇 神一样的匿名类型
    说起匿名类型,我们都知道这玩意都是为linq而生,而且匿名类型给我们带来的便利性大家在实战中应该都体会到了,特别适合于一次性使用,临时使用这些场景,虽然说是匿名类型,也就是说是有类型的,只是匿名了而已,对吧,这篇我们就来探索下匿名类型到底和linq有多大关系呢???......
  • lambda表达式以及异常
    lambda表达式简化匿名内部类*****前提:​ 函数式接口:只有一个被重写的抽象方法的接口​ @FunctionalInterface强制检测一个接口是否为函数式接口语法:​ ()->{}​ ():重写抽象方法的参数列表​ ->:箭头函数,lambda符号​ {}:重写抽象方法的方法体函数式编程:将函数的实现......