ASP.NET MVC & EF 构建智能查询 三、解析QueryModel

简介: ASP.NET MVC & EF 构建智能查询 一、智能查询的需求与设计 ASP.NET MVC & EF 构建智能查询 二、模型的设计与ModelBinder 上节说到我们已经将表单转化为了QueryModel 并且将查询条件按我们的设计存为了ConditionItem。

ASP.NET MVC & EF 构建智能查询 一、智能查询的需求与设计

ASP.NET MVC & EF 构建智能查询 二、模型的设计与ModelBinder

上节说到我们已经将表单转化为了QueryModel

并且将查询条件按我们的设计存为了ConditionItem。并且传递到了IQueryable.Where扩展方法中,对EF进行了查询:

image

当然,这里的Where是一个IQueryable的扩展方法,其中调用了将QueryModel转换为Expression表达式的类QueryableSearcher。

   1: public static class QueryableExtensions
   2: {
   3:     /// <summary>
   4:     /// zoujian add , 使IQueryable支持QueryModel
   5:     /// </summary>
   6:     /// <typeparam name="TEntity"></typeparam>
   7:     /// <param name="table">IQueryable的查询对象</param>
   8:     /// <param name="model">QueryModel对象</param>
   9:     /// <param name="prefix">使用前缀区分查询条件</param>
  10:     /// <returns></returns>
  11:     public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> table, QueryModel model, string prefix = "") where TEntity : class
  12:     {
  13:         Contract.Requires(table != null);
  14:         return Where<TEntity>(table, model.Items, prefix);
  15:     }
  16:  
  17:     private static IQueryable<TEntity> Where<TEntity>(IQueryable<TEntity> table, IEnumerable<ConditionItem> items, string prefix = "")
  18:     {
  19:         Contract.Requires(table != null);
  20:         IEnumerable<ConditionItem> filterItems =
  21:             string.IsNullOrWhiteSpace(prefix)
  22:                 ? items.Where(c => string.IsNullOrEmpty(c.Prefix))
  23:                 : items.Where(c => c.Prefix == prefix);
  24:         if (filterItems.Count() == 0) return table;
  25:         return new QueryableSearcher<TEntity>(table, filterItems).Search();
  26:     }
  27: }

这里面我们调用的QueryableSearcher其实就是我们将QueryModel转为Expression表达式并进行查询返回IQueryable的核心类。

db.Users.Where(c => c.Id < 10 && (c.Name == "chhlgy" || c.Name == "chsword")).ToList();

中的表达式

c => c.Id < 10 && (c.Name == "chhlgy" || c.Name == "chsword")为例

构造的过程为:

image

构建形如 c=>Body 的表达式

我们使用如下方法,也就是QueryableSearcher类的入口Search方法

   1: public IQueryable<T> Search()
   2: {
   3:     //构建 c=>Body中的c
   4:     ParameterExpression param = Expression.Parameter(typeof(T), "c");
   5:     //构建c=>Body中的Body
   6:     var body = GetExpressoinBody(param, Items);
   7:     //将二者拼为c=>Body
   8:     var expression = Expression.Lambda<Func<T, bool>>(body, param);
   9:     //传到Where中当做参数,类型为Expression<Func<T,bool>>
  10:     return Table.Where(expression);
  11: }

1.构建参数 c

在上文中使用

ParameterExpression param = Expression.Parameter(typeof(T), "c"); 来构建了参数c

2.构建 Body

就是我们前面的GetExpressionBody方法所生成的

   1: private Expression GetExpressoinBody(ParameterExpression param, IEnumerable<ConditionItem> items)
   2: {
   3:     var list = new List<Expression>();
   4:     //OrGroup为空的情况下,即为And组合
   5:     var andList = items.Where(c => string.IsNullOrEmpty(c.OrGroup));
   6:     //将And的子Expression以AndAlso拼接
   7:     if (andList.Count() != 0)
   8:     {
   9:         list.Add(GetGroupExpression(param, andList, Expression.AndAlso));
  10:     }
  11:     //其它的则为Or关系,不同Or组间以And分隔
  12:     var orGroupByList = items.Where(c => !string.IsNullOrEmpty(c.OrGroup)).GroupBy(c => c.OrGroup);
  13:     //拼接子Expression的Or关系
  14:     foreach (IGrouping<string, ConditionItem> group in orGroupByList)
  15:     {
  16:         if (group.Count() != 0)
  17:             list.Add(GetGroupExpression(param, group, Expression.OrElse));
  18:     }
  19:     //将这些Expression再以And相连
  20:     return list.Aggregate(Expression.AndAlso);
  21: }

3.构建分组的逻辑关系 And/OR

也就是根据Or或And来拼接不同的Expression的GetGroupExpression方法

   1: private Expression GetGroupExpression(ParameterExpression param, IEnumerable<ConditionItem> items, Func<Expression, Expression, Expression> func)
   2: {
   3:     //获取最小的判断表达式
   4:     var list = items.Select(item => GetExpression(param, item));
   5:     //再以逻辑运算符相连
   6:     return list.Aggregate(func);
   7: }

4.构建分组的单元 单一的表达式 c.User<10

这里要获取三部分,分别是左侧的属性,这里的属性可能是多级

右侧的常量,这里的常量可能要有类型转换的问题,因为Expression要求类型

中间的判断符号或其它方法如Contains

   1: private Expression GetExpression(ParameterExpression param, ConditionItem item)
   2: {
   3:     //属性表达式
   4:     LambdaExpression exp = GetPropertyLambdaExpression(item, param);
   5:     //如果有特殊类型处理,则进行处理,暂时不关注
   6:     foreach (var provider in TransformProviders)
   7:     {
   8:         if (provider.Match(item, exp.Body.Type))
   9:         {
  10:             return GetGroupExpression(param, provider.Transform(item, exp.Body.Type), Expression.AndAlso);
  11:         }
  12:     }
  13:     //常量表达式
  14:     var constant = ChangeTypeToExpression(item, exp.Body.Type);
  15:     //以判断符或方法连接
  16:     return ExpressionDict[item.Method](exp.Body, constant);
  17: }

5.获取左侧的属性及类型

   1: private LambdaExpression GetPropertyLambdaExpression(ConditionItem item, ParameterExpression param)
   2: {
   3:     //获取每级属性如c.Users.Proiles.UserId
   4:     var props = item.Field.Split('.');
   5:     Expression propertyAccess = param;
   6:     var typeOfProp = typeof(T);
   7:     int i = 0;
   8:     do
   9:     {
  10:         PropertyInfo property = typeOfProp.GetProperty(props[i]);
  11:         if (property == null) return null;
  12:         typeOfProp = property.PropertyType;
  13:         propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
  14:         i++;
  15:     } while (i < props.Length);
  16:  
  17:     return Expression.Lambda(propertyAccess, param);
  18: }

6.获取操作符或方法

这里只列举了QueryMethod枚举的操作方法

   1: private static readonly Dictionary<QueryMethod, Func<Expression, Expression, Expression>> ExpressionDict =
   2:             new Dictionary<QueryMethod, Func<Expression, Expression, Expression>>
   3:                 {
   4:                     {
   5:                         QueryMethod.Equal,
   6:                         (left, right) => { return Expression.Equal(left, right); }
   7:                         },
   8:                     {
   9:                         QueryMethod.GreaterThan,
  10:                         (left, right) => { return Expression.GreaterThan(left, right); }
  11:                         },
  12:                     {
  13:                         QueryMethod.GreaterThanOrEqual,
  14:                         (left, right) => { return Expression.GreaterThanOrEqual(left, right); }
  15:                         },
  16:                     {
  17:                         QueryMethod.LessThan,
  18:                         (left, right) => { return Expression.LessThan(left, right); }
  19:                         },
  20:                     {
  21:                         QueryMethod.LessThanOrEqual,
  22:                         (left, right) => { return Expression.LessThanOrEqual(left, right); }
  23:                         },
  24:                     {
  25:                         QueryMethod.Contains,
  26:                         (left, right) =>
  27:                             {
  28:                                 if (left.Type != typeof (string)) return null;
  29:                                 return Expression.Call(left, typeof (string).GetMethod("Contains"), right);
  30:                             }
  31:                         },
  32:                     {
  33:                         QueryMethod.StdIn,
  34:                         (left, right) =>
  35:                             {
  36:                                 if (!right.Type.IsArray) return null;
  37:                                 //调用Enumerable.Contains扩展方法
  38:                                 MethodCallExpression resultExp =
  39:                                     Expression.Call(
  40:                                         typeof (Enumerable),
  41:                                         "Contains",
  42:                                         new[] {left.Type},
  43:                                         right,
  44:                                         left);
  45:  
  46:                                 return resultExp;
  47:                             }
  48:                         },
  49:                     {
  50:                         QueryMethod.NotEqual,
  51:                         (left, right) => { return Expression.NotEqual(left, right); }
  52:                         },
  53:                     {
  54:                         QueryMethod.StartsWith,
  55:                         (left, right) =>
  56:                             {
  57:                                 if (left.Type != typeof (string)) return null;
  58:                                 return Expression.Call(left, typeof (string).GetMethod("StartsWith", new[] {typeof (string)}), right);
  59:  
  60:                             }
  61:                         },
  62:                     {
  63:                         QueryMethod.EndsWith,
  64:                         (left, right) =>
  65:                             {
  66:                                 if (left.Type != typeof (string)) return null;
  67:                                 return Expression.Call(left, typeof (string).GetMethod("EndsWith", new[] {typeof (string)}), right);
  68:                             }
  69:                         },
  70:                     {
  71:                         QueryMethod.DateTimeLessThanOrEqual,
  72:                         (left, right) => { return Expression.LessThanOrEqual(left, right); }
  73:                         }
  74:                 };

7.将Value中的值转为目标类型

   1: /// <summary>
   2: /// 类型转换,支持非空类型与可空类型之间的转换
   3: /// </summary>
   4: /// <param name="value"></param>
   5: /// <param name="conversionType"></param>
   6: /// <returns></returns>
   7: public static object ChangeType(object value, Type conversionType)
   8: {
   9:     if (value == null) return null;
  10:     return Convert.ChangeType(value, TypeUtil.GetUnNullableType(conversionType));
  11: }
  12:  
  13: /// <summary>
  14: /// 转换SearchItem中的Value的类型,为表达式树
  15: /// </summary>
  16: /// <param name="item"></param>
  17: /// <param name="conversionType">目标类型</param>
  18: public static Expression ChangeTypeToExpression(ConditionItem item, Type conversionType)
  19: {
  20:     if (item.Value == null) return Expression.Constant(item.Value, conversionType);
  21:     #region 数组
  22:     if (item.Method == QueryMethod.StdIn)
  23:     {
  24:         var arr = (item.Value as Array);
  25:         var expList = new List<Expression>();
  26:         //确保可用
  27:         if (arr != null)
  28:             for (var i = 0; i < arr.Length; i++)
  29:             {
  30:                 //构造数组的单元Constant
  31:                 var newValue = ChangeType(arr.GetValue(i), conversionType);
  32:                 expList.Add(Expression.Constant(newValue, conversionType));
  33:             }
  34:         //构造inType类型的数组表达式树,并为数组赋初值
  35:         return Expression.NewArrayInit(conversionType, expList);
  36:     }
  37:  
  38:     #endregion
  39:  
  40:     var elementType = TypeUtil.GetUnNullableType(conversionType);
  41:     var value = Convert.ChangeType(item.Value, elementType);
  42:     return Expression.Constant(value, conversionType);
  43: }

源代码下载:

http://efsearchmodel.codeplex.com/releases/view/63921

目录
相关文章
|
10月前
|
存储 Shell Linux
快速上手基于 BaGet 的脚本自动化构建 .net 应用打包
本文介绍了如何使用脚本自动化构建 `.net` 应用的 `nuget` 包并推送到指定服务仓库。首先概述了 `BaGet`——一个开源、轻量级且高性能的 `NuGet` 服务器,支持多种存储后端及配置选项。接着详细描述了 `BaGet` 的安装、配置及使用方法,并提供了 `PowerShell` 和 `Bash` 脚本实例,用于自动化推送 `.nupkg` 文件。最后总结了 `BaGet` 的优势及其在实际部署中的便捷性。
398 10
|
18天前
|
JSON 编解码 API
Go语言网络编程:使用 net/http 构建 RESTful API
本章介绍如何使用 Go 语言的 `net/http` 标准库构建 RESTful API。内容涵盖 RESTful API 的基本概念及规范,包括 GET、POST、PUT 和 DELETE 方法的实现。通过定义用户数据结构和模拟数据库,逐步实现获取用户列表、创建用户、更新用户、删除用户的 HTTP 路由处理函数。同时提供辅助函数用于路径参数解析,并展示如何设置路由器启动服务。最后通过 curl 或 Postman 测试接口功能。章节总结了路由分发、JSON 编解码、方法区分、并发安全管理和路径参数解析等关键点,为更复杂需求推荐第三方框架如 Gin、Echo 和 Chi。
|
7月前
|
前端开发 C# 开发者
.NET使用Umbraco CMS快速构建一个属于自己的内容管理系统
.NET使用Umbraco CMS快速构建一个属于自己的内容管理系统
90 12
|
7月前
|
弹性计算 开发框架 安全
基于云效 Windows 构建环境和 Nuget 制品仓库进行 .Net 应用开发
本文将基于云效 Flow 流水线 Windows 构建环境和云效 Packages Nuget 制品仓库手把手教你如何开发并部署一个 .NET 应用,从环境搭建到实战应用发布的详细教程,帮助你掌握 .NET 开发的核心技能。
|
8月前
|
Kubernetes Cloud Native Ubuntu
庆祝 .NET 9 正式版发布与 Dapr 从 CNCF 毕业:构建高效云原生应用的最佳实践
2024年11月13日,.NET 9 正式版发布,Dapr 从 CNCF 毕业,标志着云原生技术的成熟。本文介绍如何使用 .NET 9 Aspire、Dapr 1.14.4、Kubernetes 1.31.0/Containerd 1.7.14、Ubuntu Server 24.04 LTS 和 Podman 5.3.0-rc3 构建高效、可靠的云原生应用。涵盖环境准备、应用开发、Dapr 集成、容器化和 Kubernetes 部署等内容。
407 5
|
9月前
|
SQL 开发框架 .NET
ASP.NET连接SQL数据库:实现过程与关键细节解析an3.021-6232.com
随着互联网技术的快速发展,ASP.NET作为一种广泛使用的服务器端开发技术,其与数据库的交互操作成为了应用开发中的重要环节。本文将详细介绍在ASP.NET中如何连接SQL数据库,包括连接的基本概念、实现步骤、关键代码示例以及常见问题的解决方案。由于篇幅限制,本文不能保证达到完整的2000字,但会确保
|
11月前
|
C# Windows 开发者
超越选择焦虑:深入解析WinForms、WPF与UWP——谁才是打造顶级.NET桌面应用的终极利器?从开发效率到视觉享受,全面解读三大框架优劣,助你精准匹配项目需求,构建完美桌面应用生态系统
【8月更文挑战第31天】.NET框架为开发者提供了多种桌面应用开发选项,包括WinForms、WPF和UWP。WinForms简单易用,适合快速开发基本应用;WPF提供强大的UI设计工具和丰富的视觉体验,支持XAML,易于实现复杂布局;UWP专为Windows 10设计,支持多设备,充分利用现代硬件特性。本文通过示例代码详细介绍这三种框架的特点,帮助读者根据项目需求做出明智选择。以下是各框架的简单示例代码,便于理解其基本用法。
604 0
|
11月前
|
Java Spring 自然语言处理
Spring 框架里竟藏着神秘魔法?国际化与本地化的奇妙之旅等你来揭开谜底!
【8月更文挑战第31天】在软件开发中,国际化(I18N)与本地化(L10N)对于满足不同地区用户需求至关重要。Spring框架提供了强大支持,利用资源文件和`MessageSource`实现多语言文本管理。通过配置日期格式和货币符号,进一步完善本地化功能。合理应用这些特性,可显著提升应用的多地区适应性和用户体验。
95 0
|
11月前
|
微服务 API Java
微服务架构大揭秘!Play Framework如何助力构建松耦合系统?一场技术革命即将上演!
【8月更文挑战第31天】互联网技术飞速发展,微服务架构成为企业级应用主流。微服务将单一应用拆分成多个小服务,通过轻量级通信机制交互。高性能Java Web框架Play Framework具备轻量级、易扩展特性,适合构建微服务。本文探讨使用Play Framework构建松耦合微服务系统的方法。Play采用响应式编程模型,支持模块化开发,提供丰富生态系统,便于快速构建功能完善的微服务。
137 0
|
11月前
|
SQL 开发框架 .NET
代码更简洁,开发更高效:从零开始使用Entity Framework Core与传统ADO.NET构建数据持久化层的比较
【8月更文挑战第31天】在.NET平台上开发数据驱动应用时,选择合适的ORM框架至关重要。本文通过对比传统的ADO.NET和现代的Entity Framework Core (EF Core),展示了如何从零开始构建数据持久化层。ADO.NET虽强大灵活,但需要大量手写代码;EF Core则简化了数据访问,支持LINQ查询,自动生成SQL命令,提升开发效率。从创建.NET Core项目、定义数据模型、配置`DbContext`到执行数据库操作,EF Core提供了一套流畅的API,使数据持久化层的构建变得简单直接。
182 0

推荐镜像

更多
  • DNS