C#用表达式树构建动态查询的方法

前文介绍了C#中表达式树的基本知识,在实际中,表达式树有很多用法,这里举几个例子,说明如何使用表达式树构建动态查询,从而扩展LINQ的查询方法。

在LINQ中,只要数据源实现了IQuerable<T>接口,表达式树就可以用来表示结构化查询。比如,LINQ提供了用来查询关系数据源的IQueryable<T>接口的实现,C#编译器在执行这类数据源查询时,会在运行时生成表达式树,然后,查询会遍历表达式树的数据结构,然后将其转换成针对特定数据源的合适的查询语言。

下面的几个例子演示了如何使用表达式树动态生成查询。

Example 1:动态生成Where和OrderBy

这个例子是MSDN上的例子,平常在C#中我们一般直接写LINQ代码,比如下面这个:

companies.Where(company => (company.ToLower() == "coho winery" || company.Length > 16))
     .OrderBy(company => company)

对集合进行查询,然后排序等。这个时固定化了数据源,在有些时候,我们需要把这个查询“泛型化”,这就需要能够动态构造查询的能力,恰好我们可以使用表达式树,动态构建查询。

在System.Linq.Expression命名空间下有一些工厂方法能够用来表示各种查询,从而来组合出上述的查询条件。首先构造出一个表达式树,然后将表达式树传给要查询数据的CreateQuery<IElement>(Expression)方法。

//数据源
string[] companies = { "Consolidated Messenger", "Alpine Ski House", "Southridge Video", "City Power & Light",
          "Coho Winery", "Wide World Importers", "Graphic Design Institute", "Adventure Works",
          "Humongous Insurance", "Woodgrove Bank", "Margie's Travel", "Northwind Traders",
          "Blue Yonder Airlines", "Trey Research", "The Phone Company",
          "Wingtip Toys", "Lucerne Publishing", "Fourth Coffee" };

IQueryable<string> queryableData = companies.AsQueryable();

//company 参数
ParameterExpression pe = Expression.Parameter(typeof(string), "company");

// ***** 开始构造 Where(company => (company.ToLower() == "coho winery" || company.Length > 16)) ***** 

//构造表达式 company.ToLower() == "coho winery"
Expression left = Expression.Call(pe, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
Expression right = Expression.Constant("coho winery");
Expression e1 = Expression.Equal(left, right);

//构造表达式 company.Length > 16
left = Expression.Property(pe, typeof(string).GetProperty("Length"));
right = Expression.Constant(16, typeof(int));
Expression e2 = Expression.GreaterThan(left, right);

//构造上述两个表达式的或
// '(company.ToLower() == "coho winery" || company.Length > 16)'.
Expression predictBody = Expression.OrElse(e1, e2);

//构造where表达式 'queryableData.Where(company => (company.ToLower() == "coho winery" || company.Length > 16))'
MethodCallExpression whereCallExpression = Expression.Call(
  typeof(Queryable),
  "Where",
  new Type[] { queryableData.ElementType },
  queryableData.Expression,
  Expression.Lambda<Func<string, bool>>(predictBody, new ParameterExpression[] { pe }));
// ***** Where 部分构造完成 ***** 

// *****开始构造 OrderBy(company => company) *****
// 构造表达式树,表示 'whereCallExpression.OrderBy(company => company)'
MethodCallExpression orderByCallExpression = Expression.Call(
  typeof(Queryable),
  "OrderBy",
  new Type[] { queryableData.ElementType, queryableData.ElementType },
  whereCallExpression,
  Expression.Lambda<Func<string, string>>(pe, new ParameterExpression[] { pe }));
//***** OrderBy 构造完成 *****

//创建查询
IQueryable<string> results = queryableData.Provider.CreateQuery<string>(orderByCallExpression);

foreach (string company in results)
{
  Console.WriteLine(company);
}

上面代码中,在传递到Queryable.Where方法中,使用了固定数量的表达式,在实际中,可以动态根据用户输入,来合并各种查询操作。

Example 2: 扩展in查询

LINQ中,并没有提供In的操作,比如要查询xx 是否在["xx1","xx2"...]中时,需要手动编写SQL使用in,并将array拼成分割的字符串。如果使用LINQ,则可以变成xx==xx1,或者xx==xx2,实现如下:

public static Expression<Func<TElement, bool>> BuildWhereInExpression<TElement, TValue>(
 Expression<Func<TElement, TValue>> propertySelector, IEnumerable<TValue> values)
{
  ParameterExpression p = propertySelector.Parameters.Single();
  if (!values.Any())
    return e => false;

  var equals = values.Select(value => (Expression)Expression.Equal(propertySelector.Body,
    Expression.Constant(value, typeof(TValue))));
  var body = equals.Aggregate<Expression>((accumulate, equal) => Expression.Or(accumulate, equal));

  return Expression.Lambda<Func<TElement, bool>>(body, p);
}

public static IQueryable<TElement> WhereIn<TElement, TValue>(this IQueryable<TElement> source,
  Expression<Func<TElement, TValue>> propertySelector, params TValue[] values)
{
  return source.Where(BuildWhereInExpression(propertySelector, values));
}

比如上例中,我们要判断在集合中是否存在以下数据,则可以直接使用where in

string[] companies = { "Consolidated Messenger", "Alpine Ski House", "Southridge Video", "City Power & Light",
          "Coho Winery", "Wide World Importers", "Graphic Design Institute", "Adventure Works",
          "Humongous Insurance", "Woodgrove Bank", "Margie's Travel", "Northwind Traders",
          "Blue Yonder Airlines", "Trey Research", "The Phone Company",
          "Wingtip Toys", "Lucerne Publishing", "Fourth Coffee" };

string[] t = new string[] { "Consolidated Messenger", "Alpine Ski House 1" };

IQueryable<string> results = companies.AsQueryable().WhereIn(x => x, t);
foreach (string company in results)
{
  Console.WriteLine(company);
}

Example 3:封装分页逻辑

在很多地方都会用到分页,所以我们可以把查询排序跟分页封装到一起,这样用的时候就很方便,这里针对查询和排序,新建一个实体对象。

public class QueryOptions
{
  public int CurrentPage { get; set; } = 1;
  public int PageSize { get; set; } = 10;
  public string OrderPropertyName { get; set; }
  public bool DescendingOrder { get; set; }

  public string SearchPropertyName { get; set; }
  public string SearchTerm { get; set; }
}

上面这个对象,包含了分页相关设置,以及查询和排序字段及属性。

将排序逻辑封装在了PageList对象中,该对象继承自List,每次分页就返回分页后的数据放在PageList中。

public class PagedList<T> : List<T>
{
  public int CurrentPage { get; set; }
  public int PageSize { get; set; }
  public int TotalPages { get; set; }
  public QueryOptions Options { get; set; }
  public bool HasPreviousPage => CurrentPage > 1;
  public bool HasNextPage => CurrentPage < TotalPages;

  public PagedList(IQueryable<T> query, QueryOptions o)
  {
    CurrentPage = o.CurrentPage;
    PageSize = o.PageSize;
    Options = o;
    if (o != null)
    {
      if (!string.IsNullOrEmpty(o.OrderPropertyName))
      {
        query = Order(query, o.OrderPropertyName, o.DescendingOrder);
      }

      if (!string.IsNullOrEmpty(o.SearchPropertyName) && !string.IsNullOrEmpty(o.SearchTerm))
      {
        query = Search(query, o.SearchPropertyName, o.SearchTerm);
      }
    }
    TotalPages = query.Count() / PageSize;
    AddRange(query.Skip((CurrentPage - 1) * PageSize).Take(PageSize));
  }

  private IQueryable<T> Search(IQueryable<T> query, string searchPropertyName, string searchItem)
  {
    var parameter = Expression.Parameter(typeof(T), "x");
    var source = searchPropertyName.Split(".").Aggregate((Expression)parameter, Expression.Property);
    var body = Expression.Call(source, "Contains", Type.EmptyTypes, Expression.Constant(searchItem, typeof(string)));
    var lambda = Expression.Lambda<Func<T, bool>>(body, parameter);
    return query.Where(lambda);
  }

  private IQueryable<T> Order(IQueryable<T> query, string orderPropertyName, bool descendingOrder)
  {
    var parameter = Expression.Parameter(typeof(T), "x");
    var source = orderPropertyName.Split(".").Aggregate((Expression)parameter, Expression.Property);
    var lambda = Expression.Lambda(typeof(Func<,>).MakeGenericType(typeof(T), source.Type), source, parameter);
    return typeof(Queryable).GetMethods().Single(method =>
        method.Name == (descendingOrder ? "OrderByDescending" : "OrderBy")
        && method.IsGenericMethodDefinition
        && method.GetGenericArguments().Length == 2
        && method.GetParameters().Length == 2)
      .MakeGenericMethod(typeof(T), source.Type)
      .Invoke(null, new object[] { query, lambda }) as IQueryable<T>;
  }
}

可以看到,在where和order部分,我们通过查询表达式,动态构造了这两部分的逻辑。使用的时候,非常方便:

public PagedList<Category> GetCategories(QueryOptions options)
{
  return new PagedList<Category>(context.Categories, options);
}

这里context.Categories时DbSet集合,QueryOptions是用户传入的分页选项。每次输入查询排序及分页选项,就把分好的放在PageList里面返回。

Example 4 简化数据绑定

在Windows Form中,所有的控件都继承自Control基类,它有一个DataBindings属性,可以用来绑定对象,这样就能自动更新。通常,在需要给一些对象赋值,或者保存空间里设置的值到配置文件时,我们可以使用数据绑定,而不是手动的去一个一个的赋值。但DataBindings的原型方法Add为如下,比如我要将某个空间的值绑定到配置对象里,代码如下:

textBoxCustomerName.DataBindings.Add("Text", bindingSource, "CustomerName");

上述代码将TextBox控件的Text属性跟bingSource对象的CustomerName属性进行了绑定,可以看到以上代码有很多地方需要手写字符串:TextBox控件的属性“Text”,bingSource对象的“CustomerName”属性,非常不友好,而且容易出错,一看这种需要手打字符串的地方,就是"Bad Smell",需要优化。。

可以感觉到,如果我们直接能像在Visual Studio里面那样,直接使用对象.属性的方式来赋值,比如这里直接用textBoxCustomerName.Text,和bindingSource.CustomerName,来替代两个手动输入的地方,整个体验就要好很多,比如,如果将上述代码写为下面的形式:

dataSource.CreateBinding(textBoxCustomerName, ctl => ctl.Text, data => data.Name);

就要好很多。其实现方式为,我们先定义一个绑定的类,如下:

public class FlexBindingSource<TDataSource>
{
  private BindingSource _winFormsBindingSource;

  /// <summary>
  ///
  /// </summary>
  /// <param name="source">object entity binding to the control</param>
  public FlexBindingSource(TDataSource source)
  {
    _winFormsBindingSource = new BindingSource();
    _winFormsBindingSource.DataSource = source;
  }

  /// <summary>
  /// Creates a DataBinding between a control property and a datasource property
  /// </summary>
  /// <typeparam name="TControl">The control type, must derive from System.Winforms.Control</typeparam>
  /// <param name="controlInstance">The control instance on wich the databinding will be added</param>
  /// <param name="controlPropertyAccessor">A lambda expression which specifies the control property to be databounded (something like textboxCtl => textboxCtl.Text)</param>
  /// <param name="datasourceMemberAccesor">A lambda expression which specifies the datasource property (something like datasource => datasource.Property) </param>
  /// <param name="dataSourceUpdateMode">See control.DataBindings.Add method </param>
  public void CreateBinding<TControl>(TControl controlInstance, Expression<Func<TControl, object>> controlPropertyAccessor, Expression<Func<TDataSource, object>> datasourceMemberAccesor, DataSourceUpdateMode dataSourceUpdateMode = DataSourceUpdateMode.OnValidation)
    where TControl : Control
  {
    string controlPropertyName = FlexReflector.GetPropertyName(controlPropertyAccessor);
    string sourcePropertyName = FlexReflector.GetPropertyName(datasourceMemberAccesor);
    controlInstance.DataBindings.Add(controlPropertyName, _winFormsBindingSource, sourcePropertyName, true, dataSourceUpdateMode);
  }
}

这里面,构造函数里面的source,就是需要绑定的实体对象;在CreateBinding方法中,我们通过在FlexReflector在表达式中获取待绑定的字符串,然后调用传进去的Control对象的绑定方法来实现绑定。FlexReflector方法内容如下:

private static MemberExpression GetMemberExpression(Expression selector)
{
  LambdaExpression lambdaExpression = selector as LambdaExpression;
  if (lambdaExpression == null)
  {
    throw new ArgumentNullException("The selector is not a valid lambda expression.");
  }
  MemberExpression memberExpression = null;
  if (lambdaExpression.Body.NodeType == ExpressionType.Convert)
  {
    memberExpression = ((UnaryExpression)lambdaExpression.Body).Operand as MemberExpression;
  }
  else if (lambdaExpression.Body.NodeType == ExpressionType.MemberAccess)
  {
    memberExpression = lambdaExpression.Body as MemberExpression;
  }
  if (memberExpression == null)
  {
    throw new ArgumentException("The property is not a valid property to be extracted by a lambda expression.");
  }
  return memberExpression;
}

使用方法很简单,首先定义一个绑定源的实例。

private FlexBindingSource<CustomerInfo> dataSource;
dataSource = new FlexBindingSource<CustomerInfo>(customerInstance);

然后就可以使用CreateBinding方法直接绑定了。

dataSource.CreateBinding(textBoxCustomerName, ctl => ctl.Text, data => data.Name);

以上就是C#用表达式树构建动态查询的方法的详细内容,更多关于c# 构建动态查询的资料请关注我们其它相关文章!

(0)

相关推荐

  • C#之Expression表达式树实例

    本文实例讲述了C#之Expression表达式树,分享给大家供大家参考.具体实现方法如下: 表达式树表示树状数据结构的代码,树状结构中的每个节点都是一个表达式,例如一个方法调用或类似 x < y 的二元运算 1.利用 Lambda 表达式创建表达式树 复制代码 代码如下: Expression<Func<int, int, int, int>> expr = (x, y, z) => (x + y) / z; 2.编译表达式树,该方法将表达式树表示的代码编译成一个可执行

  • C# 表达式目录树的应用详解

    使用表达式目录树实现两个不同类型的属性赋值: public class People { public int Age { get; set; } public string Name { get; set; } public int Id; } public class PeopleCopy { public int Age { get; set; } public string Name { get; set; } public int Id; } public class Class1 {

  • C#表达式目录树示例详解

    1.表达式目录树 表达式目录树,在C#中是Expression来定义的,它是一种语法树,或者说是一种数据结构.其主要用于存储需要计算.运算的一种结构,它只提供存储功能,不进行运算.通常Expression是配合Lambda一起使用,lambda可以是匿名方法.Expression可以动态创建. 声明一个lambda表达式,其中可以指明类型,也可以是匿名方法: //Func<int, int, int> func = new Func<int, int, int>((m, n) =&

  • C#简单实现表达式目录树(Expression)

    1.什么是表达式目录树 :简单的说是一种语法树,或者说是一种数据结构(Expression) 2.用Lambda声明表达式目录树: Expression<Func<int, int, int>> exp = (n, m) => n * m + 2; //表达试目录树的方法体只能是一行,不能有大括号.比如: //Expression<Func<int, int, int>> exp1 = (m, n) => // { // return m * n

  • C# 快速高效率复制对象(表达式树)

    1.需求 在代码中经常会遇到需要把对象复制一遍,或者把属性名相同的值复制一遍. 比如: public class Student { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } } public class StudentSecond { public int Id { get; set; } public string Name { get; set; } p

  • c#反射表达式树模糊搜索示例

    复制代码 代码如下: public static Expression<Func<T, bool>> GetSearchExpression<T>(string SearchString)        {            Expression<Func<T, bool>> filter = null; if (string.IsNullOrEmpty(SearchString)) return null;            var l

  • C#表达式树的基本用法讲解

    表达式树使用一种类似树的结构来表示代码,它的每个节点都是一个表达式,比如方法调用和x<y这样的二元运算等.我们可以对表达式树的内容进行编辑和运算,这样能够动态修改可执行代码,以及动态创建查询等.我们可以使用匿名lambda表达式或者C# API来创建表达式树. 这一系列文章,主要是对C#表达式树的一种总结,基本知识参考MSDN的内容 这部分内容可以直接到MSDN上查看,后面的几篇文章主要分享一下,在工作中碰到的应用到表达式树的部分,谨做为记录和分享. 生成表达式树 通过lambda表达式创建表达

  • 浅谈c#表达式树Expression简单类型比较demo

    实例如下: using System; using System.Linq.Expressions; class DynamicPredicate { public static Expression<Func<T, T, bool>> Generate<T>(string op) { ParameterExpression x = Expression.Parameter(typeof(T), "x"); ParameterExpression y

  • C# 表达式树Expression Trees的知识梳理

    目录 简介 Lambda 表达式创建表达式树 API 创建表达式树 解析表达式树 表达式树的永久性 编译表达式树 执行表达式树 修改表达式树 调试 简介 表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x < y 这样的二元运算等. 你可以对表达式树中的代码进行编辑和运算.这样能够动态修改可执行代码.在不同数据库中执行 LINQ 查询以及创建动态查询. 表达式树还能用于动态语言运行时 (DLR) 以提供动态语言和 .NET Framework 之间的互操作性. 一.

  • C#用表达式树构建动态查询的方法

    前文介绍了C#中表达式树的基本知识,在实际中,表达式树有很多用法,这里举几个例子,说明如何使用表达式树构建动态查询,从而扩展LINQ的查询方法. 在LINQ中,只要数据源实现了IQuerable<T>接口,表达式树就可以用来表示结构化查询.比如,LINQ提供了用来查询关系数据源的IQueryable<T>接口的实现,C#编译器在执行这类数据源查询时,会在运行时生成表达式树,然后,查询会遍历表达式树的数据结构,然后将其转换成针对特定数据源的合适的查询语言. 下面的几个例子演示了如何使

  • C#表达式中的动态查询详解【译】

    前言 当您使用LINQ来处理数据库时,这种体验是一种神奇的体验,对吗?你把数据库实体像一个普通的收集,使用Linq中像Where,Select或者 Take,这些简单的使用就能让代码可用了. 但是,让我们考虑一下这里是如何通过动态查询和表达式树实现此功能的:幕后发生的事情.您编写的LINQ查询将转换为SQL(或其他方式),并将该SQL查询发送到数据库.然后将数据库的响应映射到C#对象.但是,如何完全转换为SQL? 在本文中,您将看到诸如Entity Framework和MongoDB C#驱动程

  • C#使用表达式树(LambdaExpression)动态更新类的属性值(示例代码)

    有看过我之前发表过的C#相关文章分享和阅读过我代码的朋友们可能会在我的代码里面经常看到各种各样的λ表达式动态拼接,C#的λ表达式树是一个好东西,也是别的语言学不来的,熟悉掌握λ表达式就能够实现各种场景的个性化操作,如动态拼接查询条件.排序方式等,也能够实现替代反射的高性能操作,比如我们常用到的IQueryable和IEnumerable,每个扩展方法就全是λ表达式树. 本文给大家分享C#使用表达式树(LambdaExpression)动态更新类的属性值的相关知识,在某些业务中会遇到需要同步两个类

  • C#表达式树Expression动态创建表达式

    上一篇中说到了 Expression 的一些概念性东西,其实也是为了这一篇做知识准备.为了实现 EFCore 的多条件.连表查询,简化查询代码编写,也就有了这篇文章. 在一些管理后台中,对数据进行多条件查询是一件很普遍的事情,比如在用户列表需要实现可以对 "用户名"."手机号"."账户是否冻结" 等等一系列的条件查询,常见的处理方式就是通过一系列 if...else... 来对条件进行拼接.这会导致查询接口实现起来堆叠了一堆看起来有用但实际很繁琐

  • ASP.NET Core中如何使用表达式树创建URL详解

    表达式树(Expression Tree) 表达式树是不可执行的代码,它只是用于表示一种树状的数据结构,树上的每一个节点都表示为某种表达式类型,大概有25种表达式类型,它们都派生自Expression类.创建表达式树具体有两个优势: 1.对表达式树的代码进行编辑修改,使表达式树中的代码变成动态代码,根据不同的数据库修改树上的代码逻辑从而达到动态切换数据库查询语句的目的,用表达式树可以动态构建针对不同数据库的查询语句. 2.完成类似反射访问未知对象的属性,通过动态构造表达式树,生成委托. 当我们在

  • C#表达式树基础教程

    什么是表达式树 来自微软官方文档的定义: 表达式树以树形数据结构表示代码. 它能干什么呢? 你可以对表达式树中的代码进行编辑和运算. 这样能够动态修改可执行代码.在不同数据库中执行 LINQ 查询以及创建动态查询. 好不好玩? 表达式树还能用于动态语言运行时 (DLR) 以提供动态语言和 .NET Framework 之间的互操作性,同时保证编译器编写员能够发射表达式树而非 Microsoft 中间语言 (MSIL). 哪里有应用? ORM框架.工作流框架等,使用到 Lambda 的代码...动

  • C#五类运算符使用表达式树进行操作

    在 C# 中,算术运算符,有以下类型 算术运算符 关系运算符 逻辑运算符 位运算符 赋值运算符 其他运算符 这些运算符根据参数的多少,可以分作一元运算符.二元运算符.三元运算符.本文将围绕这些运算符,演示如何使用表达式树进行操作. 对于一元运算符和二元运算符的 Expression 的子类型如下: UnaryExpression; //一元运算表达式 BinaryExpression; //二元运算表达式 一,算术运算符 运算符 描述 + 把两个操作数相加 - 从第一个操作数中减去第二个操作数

  • C语言构建动态数组完整实例

    本文以一个完整的实例代码简述了C语言构建动态数组的方法,供大家参考,完整实例如下: #include <stdio.h> #include <malloc.h> int main(void) { int len; int * arr; printf("请输入数组长度:"); scanf("%d", &len); arr = (int *)malloc(sizeof(int)*len); printf("请输入数组的值:&qu

随机推荐