欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

c# 动态构建LINQ查询表达式

程序员文章站 2022-04-12 19:30:15
作者:精致码农出处:联系:最近工作中遇到一个这样的需求:在某个列表查询功能中,可以选择某个数字列(如商品单价、当天销售额、当月销售额等),再选择 小于或等于 和 大于或等于 ,再填写一个待比较的数值,...

作者:精致码农

出处:

联系:

最近工作中遇到一个这样的需求:在某个列表查询功能中,可以选择某个数字列(如商品单价、当天销售额、当月销售额等),再选择 小于或等于大于或等于 ,再填写一个待比较的数值,对数据进行查询过滤。

如果只有一两个这样的数字列,那么使用 entity framework core 可以这么写 linq 查询:

public task<list<product>> getproductsasync(string propertytofilter, mathoperator mathoperator, decimal value)
{
  var query = _context.products.asnotracking();

  query = propertytofilter switch
  {
    "amount1" when mathoperator == mathoperator.lessthanorequal => query.where(x => x.amount1 <= value),
    "amount1" when mathoperator == mathoperator.greaterthanorequal => query.where(x => x.amount1 >= value),

    "amount2" when mathoperator == mathoperator.lessthanorequal => query.where(x => x.amount2 <= value),
    "amount2" when mathoperator == mathoperator.greaterthanorequal => query.where(x => x.amount2 >= value),

    _ => throw new argumentexception($"不支持 {propertytofilter} 列作为数字列查询", nameof(propertytofilter))
  };

  return query.tolistasync();
}

如果固定只有一两个数字列且将来也不会再扩展,这样写简单粗暴,也没什么问题。

但如果有几十个数字列,这样使用 swith 模式匹配的写法就太恐怖了,代码大量重复。很自然地,我们得想办法根据属性名动态创建 where 方法的参数。它的参数类型是:expression<func<t, bool>>,是一个表达式参数。

要知道如何动态创建一个类似 expression<func<t, bool>> 类型的表达式实例,就要知道如何拆解表达式树。

对于本示例,以 x => x.amount1 <= value 表达式实例为例,它的表达式树是这样的:

c# 动态构建LINQ查询表达式

然后我们可以按照此表达式树结构来构建我们的 linq 表达式:

public task<list<product>> getproductsasyncv2(string propertytofilter, mathoperator mathoperator, decimal value)
{
  var query = _context.products.asnotracking();

  var paramexp = expression.parameter(typeof(product));
  var memberexp = expression.propertyorfield(paramexp, propertytofilter);
  var valueexp = expression.constant(value);
  var compareexp = mathoperator == mathoperator.lessthanorequal ?
    expression.lessthanorequal(memberexp, valueexp) :
    expression.greaterthanorequal(memberexp, valueexp);
  var lambda = expression.lambda<func<product, bool>>(compareexp, paramexp);

  return query.where(lambda).tolistasync();
}

每个 expression.xxx 静态方法返回的都是一个以 expression 为基类的实例,代表一个表达式。不同的表达式又可以组成一个新的表达式,直到得到我们需要的 lambda 表达式。这样就形成了一种树形结构,我们称为表达式树。知道如何把一个最终的查询表达式拆解成表达式树,我们就容易动态构建此查询表达式。

得到一个表达式后,我们还可以动态编译并调用该表达式,比如上面示例得到的 lambda 变量,是一个expression<func<product, bool>> 类型,调用其 compile 方法,可以得到 func<product, bool> 类型的委托。

...

var totestproduct = new product { amount1 = 100, amount2 = 200 };

func<product, bool> func = lambda.compile();
var result = func(totestproduct);

console.writeline($"the product's {propertytofilter} is to {mathoperator} {value}.");

// output: the product's amount1 is lessthanorequal to 150.

你可以通过研究 expression 类来了解更多动态构建表达式的方法。

动态构建 linq 表达式对于不能在编译时建立查询,只能在运行时建立查询的场景很有用。但它的缺点也很明显,不易维护、不易阅读、不易调试。如果最终的表达式执行出错,很难通过调试来发现具体是构建中的那一步写错了,只能凭自己的理解和经验查找错误。所以,如非必须,一般不推荐动态构建 linq 查询表达式。

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