ASP.NET MVC 引入了 ModelBinder 技术,让我们可以在 Action 中以强类型参数的形式接收 Request 中的数据,极大的方便了我们的编程,提高了生产力。在查询 Action 中,我们可以将 Expression Trees 用作参数,通过自定义的 ModelBinder 动态自动构建查询表达式树,进一步发挥 MVC 的威力,简化编码工作。

MVC 查询和存在的不足

下面是一个查询 Employee 的 Action,在 MVC 项目中经常可以见到:

public ActionResult Index(string firstName, string lastName, DateTime? birthday, bool? sex) {
var employees = repository.Query();
if (firstName.IsNotNullAndEmpty())
employees = employees.Where(e => e.FirstName.Contains(firstName));
if (firstName.IsNotNullAndEmpty())
employees = employees.Where(e => e.LastName.Contains(lastName));
if (birthday.HasValue)
employees = employees.Where(e => e.Birthday.Value.Date == birthday.Value.Date);
if (sex.HasValue)
employees = employees.Where(e => e.Sex == sex);
return View(employees);
}

得益于 MVC 的绑定技术,我们可以简单通过 Action 的参数来获取请求的值,很少再使用 Request["XXXX"] 的方式。

仔细观察,会发现上面这个 Action 中充斥着大量 if 判断,以致代码行数比较多,不是特别清晰。

public ActionResult Index2(string firstName, string lastName, DateTime? birthday, bool? sex) {
var employees = repository.Query()
.WhereIf(e => e.FirstName.Contains(firstName), firstName.IsNotNullAndEmpty())
.WhereIf(e => e.LastName.Contains(lastName), lastName.IsNotNullAndEmpty())
.WhereIf(e => e.Birthday.Value.Date == birthday.Value.Date, birthday.HasValue)
.WhereIf(e => e.Sex == sex, sex.HasValue);
return View("Index", employees);
}

代码相清晰了许多,我之前的几个 MVC 项目中也是这样处理的。

但时间一长,我逐步也发现了这种方式一些不足之处:

  1. 首先,网站中有很多类似的查询,如Customer、Order、Product 等等。而且大致也有点规律:字符串的一般模糊查询,时间日期类的一般按日期查询(忽略时间),其它类型则相等查询。不同 Model 查询的 Action 编码总有八、九分相似,但又不是简单的重复,却又难以重构
  2. 需求变动,如增加一个查询条件,修改 View 是必须的,但也要修改 Action,增加一个参数,还要加一行 Where 或 WhereIf。简单变动却多处修改,烦人啊,而且这种需求变动又是比较频繁的,尤其是在项目初期。若能只修改 View 而不修改 Action 就爽了。

思考后,我决定使用 Expression Trees 作为查询 Action的参数来弥补这些不足。

使用 Expression<Func<T, bool>> 作为 Action 的参数

public ActionResult Index3(Expression<Func<Employee, bool>> predicate) {
var employees = repository.Query().Where(predicate);
return View("Index", employees);
}

将 Expression Trees 作为 Action 的唯一的参数(暂不考虑分页、排序等),将所有的查询条件都统一汇集至  predicate 参数。

所有的查询(不管是 Employee 还是 Customer)都使用如上代码。其它实体查询只需修改参数的类型,如 Customer 查询改为 Expression<Func<Customer, bool>> 。

如上修改代码后,直接运行会报错,因为 MVC 中默认的数据绑定器 DefaultModelBinder 不能正确绑定 Expression<Func<T, bool>> 类型的参数。

我们要新创一个新的 ModelBinder。

创建 QueryConditionExpressionModelBinder

需要一个新的 ModelBinder 来为 Expression<Func<T, bool>> 类型的参数赋值,且命名为 QueryConditionExpressionModelBinder。

QueryConditionExpressionModelBinder 要根据上下文来自动生成查询的 Expression Trees。主要关注的上下文有两点:首先是当前 Model 的类型,即 typeof(T);其次是 Request 提供的值,可通过 ValueProvider 获取。

下面给出一个粗略实现,仅用来说明这个思路是可行的:

public class QueryConditionExpressionModelBinder : IModelBinder {
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
var modelType = GetModelTypeFromExpressionType(bindingContext.ModelType);
if (modelType == null) return null; var body = default(Expression);
var parameter = Expression.Parameter(modelType, modelType.Name); foreach (var property in modelType.GetProperties()){
var queryValue = GetValueAndHandleModelState(property, bindingContext.ValueProvider, controllerContext.Controller);
if (queryValue == null) continue; Expression proeprtyCondition = null;
if (property.PropertyType == typeof (string)){
if (!string.IsNullOrEmpty(queryValue as string)){
proeprtyCondition = parameter
.Property(property.Name)
.Call("Contains", Expression.Constant(queryValue));
}
}
else if (property.PropertyType == typeof (DateTime?)){
proeprtyCondition = parameter
.Property(property.Name)
.Property("Value")
.Property("Date")
.Equal(Expression.Constant(queryValue));
}
else{
proeprtyCondition = parameter
.Property(property.Name)
.Equal(Expression.Constant(queryValue));
}
if (proeprtyCondition != null)
body = body != null ? body.AndAlso(proeprtyCondition) : proeprtyCondition;
}
if (body == null) body = Expression.Constant(true);
return body.ToLambda(parameter);
}
/// <summary>
/// 获取 Expression<Func<TXXX, bool>> 中 TXXX 的类型
/// </summary>
private Type GetModelTypeFromExpressionType(Type lambdaExpressionType) { if (lambdaExpressionType.GetGenericTypeDefinition() != typeof (Expression<>)) return null; var funcType = lambdaExpressionType.GetGenericArguments()[];
if (funcType.GetGenericTypeDefinition() != typeof (Func<,>)) return null; var funcTypeArgs = funcType.GetGenericArguments();
if (funcTypeArgs[] != typeof (bool)) return null;
return funcTypeArgs[];
}
/// <summary>
/// 获取属性的查询值并处理 Controller.ModelState
/// </summary>
private object GetValueAndHandleModelState(PropertyInfo property, IValueProvider valueProvider, ControllerBase controller) {
var result = valueProvider.GetValue(property.Name);
if (result == null) return null; var modelState = new ModelState {Value = result};
controller.ViewData.ModelState.Add(property.Name, modelState); object value = null;
try{
value = result.ConvertTo(property.PropertyType);
}
catch (Exception ex){
modelState.Errors.Add(ex);
}
return value;
}
}

如果不想在 Global.asax 文件中设置 Expression<Func<T, bool>> 的 ModelBinder, 可以借助用下面这个 Attribute 类:

public class QueryConditionBinderAttribute : CustomModelBinderAttribute {
public override IModelBinder GetBinder() {
return new QueryConditionExpressionModelBinder();
}
}

Index3 简单修改如下:

public ActionResult Index3([QueryConditionBinder]Expression<Func<Employee, bool>> predicate) { //... }

Expression Trees 参数简化查询的更多相关文章

  1. ASP.NET MVC:Expression Trees 作为参数简化查询

    ASP.NET MVC 引入了 ModelBinder 技术,让我们可以在 Action 中以强类型参数的形式接收 Request 中的数据,极大的方便了我们的编程,提高了生产力.在查询 Action ...

  2. [C#] C# 知识回顾 - 表达式树 Expression Trees

    C# 知识回顾 - 表达式树 Expression Trees 目录 简介 Lambda 表达式创建表达式树 API 创建表达式树 解析表达式树 表达式树的永久性 编译表达式树 执行表达式树 修改表达 ...

  3. Expression Trees

    Expression Trees 只是想简单说下表达式树 - Expression Trees 目录 简介 Lambda 表达式创建表达式树 API 创建表达式树 解析表达式树 表达式树的永久性 编译 ...

  4. C# 知识回顾 - 表达式树 Expression Trees

    C# 知识回顾 - 表达式树 Expression Trees 目录 简介 Lambda 表达式创建表达式树 API 创建表达式树 解析表达式树 表达式树的永久性 编译表达式树 执行表达式树 修改表达 ...

  5. 表达式树(Expression Trees)

    [翻译]表达式树(Expression Trees) 原文地址:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/con ...

  6. Expression表达式树动态查询

    在进行数据列表的查询中,我们通常会使用两种方式进行查询: linq查询 数据库sql语句查询 这样固然可以实现查询,本人之前也都是这么做的,因为查询的条件很少.使用linq,可以将所有的查询条件的属性 ...

  7. ADO.NET笔记——带参数的查询防止SQL注入攻击

    相关知识: 把单引号替换成两个单引号,虽然能起到一定的防止SQL注入攻击的作用,但是更为有效的办法是把要拼接的内容做成“参数” SQLCommand支持带参数的查询,也就是说,可以在查询语句中指定参数 ...

  8. 带参数的查询防止SQL注入攻击

    把单引号替换成两个单引号,虽然能起到一定的防止SQL注入攻击的作用,但是更为有效的办法是把要拼接的内容做成“参数” SQLCommand支持带参数的查询,也就是说,可以在查询语句中指定参数: 参数的设 ...

  9. odoo 11 实现多个字段对应一个查询参数的查询

    在整理英语单词开发模块的过程中,有这样一个需求,就是我在查询界面里输入一个查询的值A,这个A可能是下面的任何一个值 1.一个英语单词  2.汉语文字  3.一个英语单词的部分 这里有两张表:engli ...

随机推荐

  1. mshcMigrate制作的mshc文件中有链接打不开

    网上下载的c3ddotnetapiref.chm文件, 使用mshcMigrate工具(2.0.0.75)转换成mshc文件, 添加到help viewer 2.2中, 有时会遇到这样的错误: 选择是 ...

  2. Activiti工作流框架——快速上手

        一.前言 最近在做公司的OA,里面有用到工作流,公司用的是 jbpm4,感觉比较老,资料有点少,就先学学 新一点的 activiti  ㄟ(▔▽▔)ㄏ 首先工作流的概念是:工作流(Workfl ...

  3. Luogu5290 十二省联考2019春节十二响(贪心+启发式合并)

    考虑链的做法,显然将两部分各自从大到小排序后逐位取max即可,最后将根计入.猜想树上做法相同,即按上述方式逐个合并子树,最后加入根.用multiset启发式合并即可维护.因为每次合并后较小集合会消失, ...

  4. P1033 自由落体

    原题链接 https://www.luogu.org/problemnew/show/P1033 不得不说,这个题太坑了!!!主要是题目说得不明确...... 先来看图: 看一下用红圈圈起来的部分,就 ...

  5. 「洛谷1884」「USACO12FEB」过度种植【离散化扫描线】

    题目链接 [洛谷传送门] 题解 矩阵面积的并模板.(请求洛谷加为模板题) 很明显是要离散化的. 我们将矩阵与\(x\)轴平行的两个线段取出来.并且将这两个端点的\(x1\)和\(x2\)进行离散化. ...

  6. 省市区JSON

    行政编码 ViewBag.CssLinks = ""; 行政编码 中国和韩国行政编码选择.数据来自json文件,但在前端通过json对象调用. 注意本地方式,是将json文件作为对 ...

  7. P1313 计算系数

    题目描述 给定一个多项式(by+ax)k(by+ax)^k(by+ax)k,请求出多项式展开后xn×ymx^n \times y^mxn×ym项的系数. 输入输出格式 输入格式: 共一行,包含555个 ...

  8. bzoj5028小Z的加油店(线段树+差分)

    题意:维护支持以下两种操作的序列:1 l r询问a[l...r]的gcd,2 l r x把a[l...r]全部+x 题解:一道经典题.根据gcd(a,b)=gcd(a-b,b)以及区间加可知,这题可以 ...

  9. Docker: 基础介绍 [一]

    一.Docker介绍 Docker是Docker.lnc公司开源的一个基于LXC技术之上构建的Container容器引擎,源代码托管在Github上,基于Go语言并遵从Apache2.0协议开源 Do ...

  10. 基于alpine制作php镜像

    alpine包搜索https://pkgs.alpinelinux.org/ 安装依赖库 apk add --no-cache xxx 可以基于php apline镜像自行增加或删除扩展. https ...