tnblog
首页
视频
资源
登录
慢慢来
排名
40
文章
11
粉丝
3
评论
0
ICP备案 :渝ICP备18016597号-1
网站信息:2018-2025TNBLOG.NET
技术交流:群号656732739
联系我们:contact@tnblog.net
公网安备:50010702506256
欢迎加群交流技术

简化ORM-EF的Lambda查询后排序字段多判断繁琐,并利用表达式树自定义排序拓展方法

5821人阅读 2021/3/30 22:06 总访问:30763 评论:3 收藏:0 手机
分类: ORM_EF

问题详述:

  1. 假设我们实例化一个学生成绩对象,其对象及成员如下:
    1. public class Model(){
    2. public int Id { get; set; }//Id
    3. public string UserName { get; set; }//学生名称
    4. public string Father { get; set; }//学生父亲名称
    5. public int? Count { get; set; }//参加考试次数
    6. public int? Max { get; set; }//最高分
    7. public int? Min { get; set; }//最低分
    8. public int? Sum { get; set; }//总分
    9. }
    2.在控制器中使用Lambda查询数据库,但不进行.ToList()转换,使用var泛型接收:
    1. var User_Parent_Score = (...省略查询...).Select(a => new Model {
    2. UserName=a.Key.UserName,
    3. Father=a.Key.Father,
    4. Count = a.Count(),
    5. Max = a.Max(b=>b.Score),
    6. Min = a.Min(b => b.Score),
    7. Sum = a.Sum(b => b.Score),
    8. });
    3.通过.ToList()转换并传到前台解析效果如下,点击表头就排序该字段(本章论后四个字段),第一次点击进行该字段升序排序,第二次则为该字段降序排序,第三次升,第四次降……:
    前台解析效果图
    4.从前台传入两个string参数,一个命名为RowName代表代表以该字段进行排序,另一个命名为OrderState代表进行排序的状态(“asc”升序/“des”降序);通过前台设置排除为空情况RowName的值应为Count/Max/Min/Sum其中一种,OrderState的值为asc/des其中一种

    解法一: if()判断(常规解法,字段少时宜用,多字段比较繁琐):

  • 思路:我们通过判断传入的RowName的值判断其哪个字段进行排序,再在其判断里判断传入的OrderState是进行升序还是降序,如下:
    1. if (RowName == "Count")
    2. {
    3. if (OrderState == "asc")
    4. User_Parent_Score = User_Parent_Score.OrderBy(a => a.Count);
    5. else
    6. User_Parent_Score = User_Parent_Score.OrderByDescending(a => a.Count);
    7. }
    8. if (RowName == "Max")
    9. {
    10. if (OrderState == "asc")
    11. User_Parent_Score = User_Parent_Score.OrderBy(a => a.Max);
    12. else
    13. User_Parent_Score = User_Parent_Score.OrderByDescending(a => a.Max);
    14. }
    15. if......

    解法二:表达式树动态生成Lambda表达式传入(EF核心)

  • 思路:从解法一中可以看出排除RowName和OrderState的值不同,RowName决定Lambda表达式不同,OrderState决定调用的方法不同,执行的分组代码其实大致相同,那么我们可以利用表达式树动态生成Lambda表达式进行传入,这样我们只需要简单判断是进行升序或降序即可,这样无论多少字段都只需判断两次即可:

    首先引入表达式树命名空间:

    1. using System.Linq.Expressions;

    再通过.Parameter(传入类型,自定义名称)生成Lambda左部:

    1. var Left = Expression.Parameter(typeof(Model), "a");

    再通过.Property(左部,字段名)生成Lambda左部:

    1. var Right = Expression.Property(Left, RowName);

    再通过Expression.Lambda(右部,左部)将左右两部分组成labmda表达式:

    1. Expression<Func<Model, int?>> CreateLambda = Expression.Lambda<Func<Model, int?>>(Body, Left);

    最后再通过判断OrderState进行不同方法的传值,此时只需传入上面生成的Lambda表达式CreateLambda即可!

  • 完整代码如下:
    1. //判断RowName是否为空
    2. if (!String.IsNullOrEmpty(RowName))
    3. {
    4. //表达式树,EF核心原理,可以借组表达式树来动态生成Lambda
    5. //生成Lambda左边部分
    6. var Left = Expression.Parameter(typeof(Model), "a");
    7. //生成Lambda右边部分
    8. var Right = Expression.Property(Left, RowName);
    9. //将左右两部分组成Lambda表达式
    10. Expression<Func<Model, int?>> CreateLambda = Expression.Lambda<Func<Model, int?>>(Right, Left);
    11. //判断OrderState后传入生成的Lambda表达式
    12. if (OrderState == "asc")
    13. User_Parent_Score = User_Parent_Score.OrderBy(CreateLambda);
    14. else
    15. User_Parent_Score = User_Parent_Score.OrderByDescending(CreateLambda);
    16. }

    将上述法二写成拓展方法,如下:

在静态类写一个静态方法,传入string RowName,string OrderState两个参数:

  1. public static IQueryable<TSource> MyOrderBy<TSource>(this IQueryable<TSource> source,string RowName,string OrderState){}

将上述例子中的对象Model换成泛型TSource,并返回其本身即可!

  • 完整代码如下:
    1. public static IQueryable<TSource> MyOrderBy<TSource>(this IQueryable<TSource> source,string RowName,string OrderState)
    2. {
    3. //判断RowName是否为空
    4. if (!String.IsNullOrEmpty(RowName))
    5. {
    6. //表达式树,EF核心原理,可以借组表达式树来动态生成Lambda
    7. //生成Lambda左边部分
    8. var Left = Expression.Parameter(typeof(TSource), "a");
    9. //生成Lambda右边部分
    10. var Body = Expression.Property(Left, RowName);
    11. //将左右两部分组成Lambda表达式
    12. Expression<Func<TSource, int?>> CreateLambda = Expression.Lambda<Func<TSource, int?>>(Body, Left);
    13. //判断后传入生成的Lambda表达式
    14. if (OrderState == "asc")
    15. source = source.OrderBy(CreateLambda);
    16. else
    17. source = source.OrderByDescending(CreateLambda);
    18. }
    19. //返回其本身
    20. return source;
    21. }
    在查询时调用该拓展方法即可实现相应排序,如下:
    1. var User_Parent_Score = (...省略查询...).Select(a => new Model {
    2. UserName=a.Key.UserName,
    3. Father=a.Key.Father,
    4. Count = a.Count(),
    5. Max = a.Max(b=>b.Score),
    6. Min = a.Min(b => b.Score),
    7. Sum = a.Sum(b => b.Score),
    8. }).MyOrderBy(RowName,OrderState);

上述方法二和由方法二写的拓展方法都是在字段为int?的前提下进行书写和运用,若再添加一平均分字段(double?类型),再以该字段排序则会报错,为使代码耦合度低,我们需要将上述拓展方法中的int?也使用泛型表示…..

使用泛型代替单一数据类型,降低上述拓展方法耦合度:

  • 问题详述:

    如果我们单纯地声明一个泛型,在调用该方法时需要说明泛型类型,否则就会:
    为说明泛型类型
    这样在调用方法时,的确麻烦,所以直接传入泛型代替int?类型不可取!我们需要在调用方法时只需要简单地传入排序字段和排序方式即可,就像上述的.MyOrder(RowName,OrderState)即可

  • 解决方案(我们利用一些基础的反射,通过反射来实现类型传入和方法调用):

    我们将上述的通过传入字段生成Lambda表达式的代码写成一个普通的静态方法(声明两个泛型,一个为传入的排序对象泛型,一个为排序字段泛型,传入排序对象和排序字段),名为CreateLambda():

    1. public static Expression<Func<TSource, Type>> CreateLambda<TSource, Type>(IQueryable<TSource> source, string RowName)
    2. {
    3. var Left = Expression.Parameter(typeof(TSource),"a");
    4. var Right = Expression.Property(Left, RowName);
    5. Expression<Func<TSource, Type>> Lambda = Expression.Lambda<Func<TSource, Type>>(Right, Left);
    6. return Lambda;
    7. }

    然后我们在写两个排序普通的静态方法(声明两个泛型,一个为传入的排序对象泛型,一个为排序字段泛型,传入排序对象和排序字段),分别名为OrderAsc()和OrderDes(),代表正序(小=>大)和反序:

    1. public static IQueryable<TSource> OrderAsc<TSource, Type>(IQueryable<TSource> source, string RowName)
    2. {
    3. return source.OrderBy(CreateLambda<TSource, Type>(source, RowName));
    4. }
    5. public static IQueryable<TSource> OrderDes<TSource, Type>(IQueryable<TSource> source, string RowName)
    6. {
    7. return source.OrderByDescending(CreateLambda<TSource, Type>(source, RowName));
    8. }

    万事俱备,只欠东风,接下来到了实现上述代码功能的关键操作,利用反射通过方法名调用方法并通过字段名获取类型传入

    1. 利用反射通过方法名获取方法(SortExtand包含上述所有方法的静态类名)
    1. //利用反射通过方法名获取方法,typeof()获取类型的System.Type对象,GetMethod()通过方法名获得类中方法
    2. var Method = typeof(SortExtand).GetMethod((OrderState == "asc") ? "OrderAsc" : "OrderDes");
    2.将泛型类型提供给方法,typeof(TSource).GetProperty(RowName).PropertyType通过传入字段名获取该字段类型
    1. //将泛型类型传入方法,并用Method接收返回值
    2. Method = Method.MakeGenericMethod(typeof(TSource), typeof(TSource).GetProperty(RowName).PropertyType);
    3.使用反射调用方法
    1. //执行方法,使用Invoke(类的实例化对象,方法参数)调用方法,这里类是静态的所以传入null代替
    2. source = (IQueryable<TSource>)Method.Invoke(null, new object[] { source, RowName });

    这样就可以实现使用该拓展方法时只需简单传入排序字段和排序方法即可,即.MyOrder(RowName,OrderState),看一哈最终完整代码……

  • 完整代码:
    1. public static IQueryable<TSource> MyOrderBy<TSource>(this IQueryable<TSource> source, string RowName, string OrderState)
    2. {
    3. //判断传入RowName是否为空
    4. if (!String.IsNullOrEmpty(RowName))
    5. {
    6. // 利用反射通过方法名获取方法(SortExtand包含上述所有方法的静态类名)
    7. var Method = typeof(SortExtand).GetMethod((OrderState == "asc") ? "OrderAsc" : "OrderDes");
    8. //将泛型类型传入方法,并用Method接收返回值
    9. Method = Method.MakeGenericMethod(typeof(TSource), typeof(TSource).GetProperty(RowName).PropertyType);
    10. //执行方法,使用Invoke(类的实例化对象,方法参数)调用方法,这里类是静态的所以传入null代替
    11. source = (IQueryable<TSource>)Method.Invoke(null, new object[] { source, RowName });
    12. }
    13. return source;
    14. }
    15. public static IQueryable<TSource> OrderAsc<TSource, Type>(IQueryable<TSource> source, string RowName)
    16. {
    17. return source.OrderBy(CreateLambda<TSource, Type>(source, RowName));
    18. }
    19. public static IQueryable<TSource> OrderDes<TSource, Type>(IQueryable<TSource> source, string RowName)
    20. {
    21. return source.OrderByDescending(CreateLambda<TSource, Type>(source, RowName));
    22. }
    23. public static Expression<Func<TSource, Type>> CreateLambda<TSource, Type>(IQueryable<TSource> source, string RowName)
    24. {
    25. var Left = Expression.Parameter(typeof(TSource),"a");
    26. var Right = Expression.Property(Left, RowName);
    27. Expression<Func<TSource, Type>> Lambda = Expression.Lambda<Func<TSource, Type>>(Right, Left);
    28. return Lambda;
    29. }
评价

Session

2021/4/1 10:31:15

[good][good][good]

剑轩

2021/4/9 10:38:51

很有深度!

心酸

2021/4/13 16:01:17

[赞]

模型验证(简化IF)

模型验证需要在对象部分验证//先引入命名空间 usingSystem.ComponentModel.DataAnnotations; //对象部分 //设置userna...

.NET实现ORM-EFLambda查询一对多转一对一即实现ListT.SelectMany()方法并对其进行为空处理

我们借助微软官方方法参数进行修改实现命名方法为MySelectMAny: public static IEnumerable&lt;TResult&gt; MySelectMA...

cAPS.NET 保存base64位格式图片

publicvoidUpload() { //取出图片对应的base64位字符 stringimgBase=Request[&quot;imgBase&quot;]; //c#里边的base6...

使用OLEDB读取不同版本Excel连接字符串设置

使用OleBD读取excel的时候,excel不同的版本,连接字符串的写法也会不一样。///&lt;summary&gt; ///读取excel ///&lt;/su...

vs2017 对 COM 组件调用返回了错误 HRESULT E_FAIL

vs2017添加引用报错 对 COM 组件的调用返回了错误 HRESULT E_FAIL 1.以管理员身份打开vs2017开发人员命令指示符 2...

分布式服务架构与微服务架构概念区别与联系

分布式:分散压力。微服务:分散能力。当下理解分布式:不同模块部署在不同服务器上作用:分布式解决网站高并发带来问题集...

分布式-微服务-集群区别

1.分布式将一个大的系统划分为多个业务模块,业务模块分别部署到不同的机器上,各个业务模块之间通过接口进行数据交互。区...

EasyUI弹窗批量修改combogrid下拉框

JS方法//点击弹出批量修改框 UpdateLot:function(){ varrow=$(&quot;#dg&quot;).datagrid(&quot;getChecked&quot;); if(...

js与Controller中分割字符串方法

js: varstr=OpenRule; varstrs=newArray(); strs=str.split(&quot;,&quot;); for(vari=0;i&lt;strs.length;i++){ $(&q...

如何修改重置MD5加密后SQL用户密码

二次开发时,要加一个忘记密码的功能,后台写了修改密码的方法,数据库执行也修改成功,但是登录一直提示密码错误。之所以...

如何修改CSS中存在element.style内联样式

改腾讯地图的时候调整了下样式,发现样式一直存在问题,修改style里面的值,一点用都没有,html中这个值还找不到是在哪里出...

微信交易单号和订单号区别

一般第三方在线支付系统中都会有两类订单号transactionId 为支付系统的订单号,由支付系统生成,并在回调时传回给商户,用...

C ?、?? 问号和2个问号用法(类型?、对象?)

C# ?C# ???:单问号1.定义数据类型可为空。可用于对int,double,bool等无法直接赋值为null的数据类型进行null的赋值如这...

C out、ref关键字用法和区别

说说自己对out、ref的认识,面试问到的几率很高哟。out:classProgram { /* *out、ref都是引用传递,传递后使用都会改变...

SQL Server几种分页方式和效率

--topnotin方式 selecttop条数*fromtablename whereIdnotin(selecttop条数*页数Idfromtablename) --ROW_NUMBER()O...