首页
视频
资源
登录
DJ王重阳
慢慢来
博主信息
排名
6
文章
6
粉丝
16
评论
8
文章类别
ORM_EF
3篇
.NET Core
2篇
最新文章
最新评价
{{item.articleTitle}}
{{item.blogName}}
:
{{item.content}}
关于我们
ICP备案 :
渝ICP备18016597号-1
网站信息:
2018-2024
TNBLOG.NET
技术交流:
群号656732739
联系我们:
contact@tnblog.net
欢迎加群
欢迎加群交流技术
原
简化ORM-EF的Lambda查询后排序字段多判断繁琐,并利用表达式树自定义排序拓展方法
4150
人阅读
2021/3/30 22:06
总访问:
24072
评论:
3
收藏:
0
手机
分类:
ORM_EF
tn3#问题详述: 1. 假设我们实例化一个学生成绩对象,其对象及成员如下: ```csharp public class Model(){ public int Id { get; set; }//Id public string UserName { get; set; }//学生名称 public string Father { get; set; }//学生父亲名称 public int? Count { get; set; }//参加考试次数 public int? Max { get; set; }//最高分 public int? Min { get; set; }//最低分 public int? Sum { get; set; }//总分 } ``` 2.在控制器中使用Lambda查询数据库,但不进行.ToList()转换,使用var泛型接收: ```csharp var User_Parent_Score = (...省略查询...).Select(a => new Model { UserName=a.Key.UserName, Father=a.Key.Father, Count = a.Count(), Max = a.Max(b=>b.Score), Min = a.Min(b => b.Score), Sum = a.Sum(b => b.Score), }); ``` 3.通过.ToList()转换并传到前台解析效果如下,点击表头就排序该字段(本章论后四个字段),第一次点击进行该字段升序排序,第二次则为该字段降序排序,第三次升,第四次降......: ![前台解析效果图](https://img.tnblog.net/arcimg/17726394069/56aa0b6170ff436386d4aee6f9b74f1b.PNG "解析效果图") 4.从前台传入两个string参数,一个命名为RowName代表代表以该字段进行排序,另一个命名为OrderState代表进行排序的状态(“asc”升序/“des”降序);通过前台设置排除为空情况RowName的值应为Count/Max/Min/Sum其中一种,OrderState的值为asc/des其中一种 tn3#解法一: if()判断(常规解法,字段少时宜用,多字段比较繁琐): - 思路:我们通过判断传入的RowName的值判断其哪个字段进行排序,再在其判断里判断传入的OrderState是进行升序还是降序,如下: ```csharp if (RowName == "Count") { if (OrderState == "asc") User_Parent_Score = User_Parent_Score.OrderBy(a => a.Count); else User_Parent_Score = User_Parent_Score.OrderByDescending(a => a.Count); } if (RowName == "Max") { if (OrderState == "asc") User_Parent_Score = User_Parent_Score.OrderBy(a => a.Max); else User_Parent_Score = User_Parent_Score.OrderByDescending(a => a.Max); } if...... ``` tn3#解法二:表达式树动态生成Lambda表达式传入(EF核心) - 思路:从解法一中可以看出排除RowName和OrderState的值不同,RowName决定Lambda表达式不同,OrderState决定调用的方法不同,执行的分组代码其实大致相同,那么我们可以利用表达式树动态生成Lambda表达式进行传入,这样我们只需要简单判断是进行升序或降序即可,这样无论多少字段都只需判断两次即可: >首先引入表达式树命名空间: ```csharp using System.Linq.Expressions; ``` >再通过.Parameter(传入类型,自定义名称)生成Lambda左部: ```csharp var Left = Expression.Parameter(typeof(Model), "a"); ``` >再通过.Property(左部,字段名)生成Lambda左部: ```csharp var Right = Expression.Property(Left, RowName); ``` >再通过Expression.Lambda<Lambda的委托>(右部,左部)将左右两部分组成labmda表达式: ```csharp Expression<Func<Model, int?>> CreateLambda = Expression.Lambda<Func<Model, int?>>(Body, Left); ``` >最后再通过判断OrderState进行不同方法的传值,此时只需传入上面生成的Lambda表达式CreateLambda即可! - 完整代码如下: ```csharp //判断RowName是否为空 if (!String.IsNullOrEmpty(RowName)) { //表达式树,EF核心原理,可以借组表达式树来动态生成Lambda //生成Lambda左边部分 var Left = Expression.Parameter(typeof(Model), "a"); //生成Lambda右边部分 var Right = Expression.Property(Left, RowName); //将左右两部分组成Lambda表达式 Expression<Func<Model, int?>> CreateLambda = Expression.Lambda<Func<Model, int?>>(Right, Left); //判断OrderState后传入生成的Lambda表达式 if (OrderState == "asc") User_Parent_Score = User_Parent_Score.OrderBy(CreateLambda); else User_Parent_Score = User_Parent_Score.OrderByDescending(CreateLambda); } ``` tn>将上述法二写成拓展方法,如下: >在静态类写一个静态方法,传入string RowName,string OrderState两个参数: ```csharp public static IQueryable<TSource> MyOrderBy<TSource>(this IQueryable<TSource> source,string RowName,string OrderState){} ``` >将上述例子中的对象Model换成泛型TSource,并返回其本身即可! - 完整代码如下: ```csharp public static IQueryable<TSource> MyOrderBy<TSource>(this IQueryable<TSource> source,string RowName,string OrderState) { //判断RowName是否为空 if (!String.IsNullOrEmpty(RowName)) { //表达式树,EF核心原理,可以借组表达式树来动态生成Lambda //生成Lambda左边部分 var Left = Expression.Parameter(typeof(TSource), "a"); //生成Lambda右边部分 var Body = Expression.Property(Left, RowName); //将左右两部分组成Lambda表达式 Expression<Func<TSource, int?>> CreateLambda = Expression.Lambda<Func<TSource, int?>>(Body, Left); //判断后传入生成的Lambda表达式 if (OrderState == "asc") source = source.OrderBy(CreateLambda); else source = source.OrderByDescending(CreateLambda); } //返回其本身 return source; } ``` >在查询时调用该拓展方法即可实现相应排序,如下: ```csharp var User_Parent_Score = (...省略查询...).Select(a => new Model { UserName=a.Key.UserName, Father=a.Key.Father, Count = a.Count(), Max = a.Max(b=>b.Score), Min = a.Min(b => b.Score), Sum = a.Sum(b => b.Score), }).MyOrderBy(RowName,OrderState); ``` ------------ tn> 上述方法二和由方法二写的拓展方法都是在字段为int?的前提下进行书写和运用,若再添加一平均分字段(double?类型),再以该字段排序则会报错,为使代码耦合度低,我们需要将上述拓展方法中的int?也使用泛型表示..... tn3#使用泛型代替单一数据类型,降低上述拓展方法耦合度: - 问题详述: > 如果我们单纯地声明一个泛型,在调用该方法时需要说明泛型类型,否则就会: ![为说明泛型类型](https://img.tnblog.net/arcimg/17726394069/c80a761c1f4445ecb839375cce6927c6.PNG "未说明泛型类型") 这样在调用方法时,的确麻烦,所以直接传入泛型代替int?类型不可取!我们需要在调用方法时只需要简单地传入排序字段和排序方式即可,就像上述的`.MyOrder(RowName,OrderState)`即可 - 解决方案(我们利用一些基础的反射,通过反射来实现类型传入和方法调用): >我们将上述的通过传入字段生成Lambda表达式的代码写成一个普通的静态方法(声明两个泛型,一个为传入的排序对象泛型,一个为排序字段泛型,传入排序对象和排序字段),名为CreateLambda(): ```csharp public static Expression<Func<TSource, Type>> CreateLambda<TSource, Type>(IQueryable<TSource> source, string RowName) { var Left = Expression.Parameter(typeof(TSource),"a"); var Right = Expression.Property(Left, RowName); Expression<Func<TSource, Type>> Lambda = Expression.Lambda<Func<TSource, Type>>(Right, Left); return Lambda; } ``` >然后我们在写两个排序普通的静态方法(声明两个泛型,一个为传入的排序对象泛型,一个为排序字段泛型,传入排序对象和排序字段),分别名为OrderAsc()和OrderDes(),代表正序(小=>大)和反序: ```csharp public static IQueryable<TSource> OrderAsc<TSource, Type>(IQueryable<TSource> source, string RowName) { return source.OrderBy(CreateLambda<TSource, Type>(source, RowName)); } public static IQueryable<TSource> OrderDes<TSource, Type>(IQueryable<TSource> source, string RowName) { return source.OrderByDescending(CreateLambda<TSource, Type>(source, RowName)); } ``` >万事俱备,只欠东风,接下来到了实现上述代码功能的关键操作,利用反射通过方法名调用方法并通过字段名获取类型传入 ###### 1. 利用反射通过方法名获取方法(SortExtand包含上述所有方法的静态类名) ```csharp //利用反射通过方法名获取方法,typeof()获取类型的System.Type对象,GetMethod()通过方法名获得类中方法 var Method = typeof(SortExtand).GetMethod((OrderState == "asc") ? "OrderAsc" : "OrderDes"); ``` ###### 2.将泛型类型提供给方法,typeof(TSource).GetProperty(RowName).PropertyType通过传入字段名获取该字段类型 ```csharp //将泛型类型传入方法,并用Method接收返回值 Method = Method.MakeGenericMethod(typeof(TSource), typeof(TSource).GetProperty(RowName).PropertyType); ``` ###### 3.使用反射调用方法 ```csharp //执行方法,使用Invoke(类的实例化对象,方法参数)调用方法,这里类是静态的所以传入null代替 source = (IQueryable<TSource>)Method.Invoke(null, new object[] { source, RowName }); ``` >这样就可以实现使用该拓展方法时只需简单传入排序字段和排序方法即可,即.MyOrder(RowName,OrderState),看一哈最终完整代码...... - 完整代码: ```csharp public static IQueryable<TSource> MyOrderBy<TSource>(this IQueryable<TSource> source, string RowName, string OrderState) { //判断传入RowName是否为空 if (!String.IsNullOrEmpty(RowName)) { // 利用反射通过方法名获取方法(SortExtand包含上述所有方法的静态类名) var Method = typeof(SortExtand).GetMethod((OrderState == "asc") ? "OrderAsc" : "OrderDes"); //将泛型类型传入方法,并用Method接收返回值 Method = Method.MakeGenericMethod(typeof(TSource), typeof(TSource).GetProperty(RowName).PropertyType); //执行方法,使用Invoke(类的实例化对象,方法参数)调用方法,这里类是静态的所以传入null代替 source = (IQueryable<TSource>)Method.Invoke(null, new object[] { source, RowName }); } return source; } public static IQueryable<TSource> OrderAsc<TSource, Type>(IQueryable<TSource> source, string RowName) { return source.OrderBy(CreateLambda<TSource, Type>(source, RowName)); } public static IQueryable<TSource> OrderDes<TSource, Type>(IQueryable<TSource> source, string RowName) { return source.OrderByDescending(CreateLambda<TSource, Type>(source, RowName)); } public static Expression<Func<TSource, Type>> CreateLambda<TSource, Type>(IQueryable<TSource> source, string RowName) { var Left = Expression.Parameter(typeof(TSource),"a"); var Right = Expression.Property(Left, RowName); Expression<Func<TSource, Type>> Lambda = Expression.Lambda<Func<TSource, Type>>(Right, Left); return Lambda; } ```
👈{{preArticle.title}}
👉{{nextArticle.title}}
评价
{{titleitem}}
{{titleitem}}
{{item.content}}
{{titleitem}}
{{titleitem}}
{{item.content}}