Expression Trees 参数简化查询

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()[0];if (funcType.GetGenericTypeDefinition() != typeof (Func<,>)) return null;var funcTypeArgs = funcType.GetGenericArguments();if (funcTypeArgs[1] != typeof (bool)) return null;return funcTypeArgs[0];}/// <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) { //... }

 

 

 

转载于:https://www.cnblogs.com/wd0730/p/3213457.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/376510.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为你的程序添加监听器

平时在写程序时经常会遇到监听器&#xff0c;比如按钮的click监听器&#xff0c;按键监听器等等。而android中的监听器和java中的回调函数是同一个概念&#xff0c;都是在底层代码中定义一个接口来调用高层的代码。那么什么是回调函数呢&#xff1f;网上说的是“在WINDOWS中&am…

图像处理

android图像处理系列之四&#xff0d;&#xff0d;给图片添加边框&#xff08;上&#xff09; http://www.oschina.net/question/157182_40586 android图像处理系列之六&#xff0d;&#xff0d;给图片添加边框&#xff08;下&#xff09;&#xff0d;图片叠加 http://www.osc…

Git push 时每次都需要密码的疑惑

2015.1.13更新&#xff1a; 在本地搭建Git服务器时&#xff0c;也是有每次操作需要密码的情况。 是因为每次做推送动作时&#xff0c;Git需要认证你是好人。所以需要密码。 可以在 /home/username/.ssh/authorized_keys 文件里添加你的 ssh 公钥。一行一个。这样就可以在你push…

ruby字符串处理

1. str"abc123"puts str[0].chr > a puts str[0] >a的ascii码 2.中文字符串的正则表达式 文本编码:utf-8 文件第一行&#xff1a;#encoding:urf-8 require "iconv" str"八万"reg/(.)万/datareg.match(str)result Iconv.i…

PHP+七牛云存储上传图片代码片段

2014年11月14日 16:37:51 第一段代码是上传单个图片的,第二个是上传多个图片的 1 //上传到七牛2 //单个文件3 //formname: 表单名字; pre: 图片Url中显示的图片名字(也就是七牛中的key)4 public function upImage($formname, $pre)5 {6 if (empty($_FI…

【PS】Gold words tutorials 赤金字教程

material_01material_021. White background and black words.The font of "Laker" is Teenick, and "Huang" is 中國龍粗魏碑2.Open material_01 and select a part of it.Copy and paste the part part into our workspace.You can drag and move to pa…

iOS 键盘的关闭

iOS 键盘的关闭 //通过委托来放弃 “第一响应者” #pragma mark - UITextField Delegate Method -(BOOL)textFieldShouldReturn:(UITextField*)textField {[textField resignFirstResponder];return YES; } //通过委托来放弃 “第一响应者” #pragma mark - UITextView Delegat…

递归与分治

今天总算把第三章递归与分治看完了&#xff0c;呵呵&#xff0c;没想到开头就给我来了点打击&#xff0c;看以后不认真学还真不行了&#xff01; 为了祝贺初战告捷&#xff0c;把几个简单的题目贴上来吧&#xff0c;纪念一下&#xff01; 《整数因子分解》 大于1的正整数n可以分…

Android中的Handler机制

直接在UI线程中开启子线程来更新TextView显示的内容&#xff0c;运行程序我们会发现&#xff0c;如下错 误&#xff1a;android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.翻译过来就是&…

初来乍到

从今天开始&#xff0c;我也加入博客园这个大家庭了&#xff0c;希望能和大家一起学习IT技术&#xff0c;共同进步。小弟初来乍到&#xff0c;望大家能多多关照&#xff01;转载于:https://www.cnblogs.com/markwave/p/3227777.html

JQuery学习四(过滤选择器)

&#xff1a;first选择第一个元素。$&#xff08;“div:first”&#xff09;进行选择第一个<div> :last 选择最后一个最后一个元素 $&#xff08;"div:last"&#xff09;选取最后一个<div> [:not(选择器&#xff09;] 选择不满足“选择器”条件的元素 $…

160 - 1 Acid burn

环境&#xff1a;Windows XP sp3 先打开&#xff0c;看看长什么样&#xff1a; OD载入&#xff0c;右键->查找->所有参考文本字串 找到Sorry,The serial is incorect 找到后就在反汇编窗口跟随&#xff0c;往上翻&#xff1a; 0042F998 /. 55 push ebp 0…

跟树有关的数据结构学习系列之概览

1.Binary Search Tree&#xff08;BST&#xff09; 二叉搜索树 2.B-Tree 3.BTree 4.B*Tree转载于:https://www.cnblogs.com/devindong/p/3233041.html

在社会实践中长本领

暑假回到家&#xff0c;家里要我在自家店里帮忙&#xff0c;做员工。因为我家跟舅舅家合资开了一家家禽冻品批发部&#xff0c;生意兴旺&#xff0c;越做越大&#xff0c;忙得不可开交。在自家店里做员工&#xff0c;当然&#xff0c;家里人都很高兴&#xff0c;我也乐意。在员…

Animating Layout Changes(展开收起)

原文地址&#xff1a;https://developer.android.com/training/animation/layout.html#add &#xff08;1&#xff09;设置布局文件&#xff1a; <LinearLayout android:id"id/container"android:animateLayoutChanges"true"... /> &#xff08;2&am…

160 - 2 Afkayas.1

环境&#xff1a; Windows Xp sp3 OD载入&#xff1a; 运行&#xff0c;然后输入&#xff1a; 然后回到OD&#xff0c;按F12来暂停&#xff0c; 然后ALTF9回到程序领空&#xff0c;把弹出的那个错误消息框点掉&#xff0c;这时OD来到这里&#xff1a; 004025F9 . 68 E81…

POJ 2125 Destroying The Graph (二分图最小点权覆盖集+输出最小割方案)

题意 有一个图&#xff0c; 两种操作&#xff0c;一种是删除某点的所有出边&#xff0c;一种是删除某点的所有入边&#xff0c;各个点的不同操作分别有一个花费&#xff0c;现在我们想把这个图的边都删除掉&#xff0c;需要的最小花费是多少。 思路 很明显的二分图最小点权覆盖…

160 - 3 Afkayas.2

环境&#xff1a; Windows xp sp3 这次的目标有两个&#xff1a; 1.去除Nag窗口 2.找出Serial的算法 1.这次去除Nag窗口用了另外两个程序&#xff1a; &#xff08;1&#xff09;VBLocalize v1.1.0.0 &#xff08;2&#xff09;UltraEdit &#xff08;3&#xff09;VBEx…

class threading.Thread()说明:

class threading.Thread()说明&#xff1a; class threading.Thread(groupNone, targetNone, nameNone, args(), kwargs{}) This constructor should always be called with keyword arguments. Arguments are: group should be None; reserved for future extension when a Th…

并行编程——内存模型之顺序一致性

1 定义 Sequential consistency , 简称 SC&#xff0c;定义如下 … the result of any execution is the same as if the operations of all the processors were executed in some sequential order, and the operations of each individual processor appear in this sequen…