zoukankan      html  css  js  c++  java
  • .NET深入实战系列--EF到底怎么写过滤条件

    对于系统开发来说,按不同字段进行过滤查询是一种常见的需求。在EF中通常的做法是:

    1. /// <summary>  
    2. /// 只是简单举例,只用了两个过滤条件  
    3. /// </summary>  
    4. IEnumerable<UserInfo> Search(string username = "", string usertype = "")  
    5. {  
    6.     var query = _context.UserInfoes.AsQueryable();  
    7.     if (!string.IsNullOrEmpty(username))  
    8.         query = query.Where(u => u.UserName == username);  
    9.     if (!string.IsNullOrEmpty(usertype))  
    10.         query = query.Where(u => u.UserType == usertype);  
    11.   
    12.     return query.ToList();  
    13. }  

        这时如果我有一个新的需求,比如查询用户名中必须包含不定个数关键字的用户。那我们可以用参数数组做类似下面的升级

    1. private IEnumerable<UserInfo> Search(params string[] keys)  
    2.       {  
    3.           var query = _context.UserInfoes.AsQueryable();  
    4.   
    5.           foreach (var key in keys)  
    6.           {  
    7.               query = query.Where(u => u.UserName.Contains(key));  
    8.           }  
    9.   
    10.           return query.ToList();  
    11.       }  

        上面的代码都是能够良好运行的,这时如果需求变成了:查询用户名中至少包含一个关键字的用户,那我们该如何处理?很明显要用到Or运算,但怎么处理才是最合理的?普通的查询已经不能很好的解决该问题。于是Joe Albahari大神在他的一篇博文中使用PredicateBuilder轻松地解决了该问题:

    1. IQueryable<UserInfo> Search(params string[] keys)  
    2.      {  
    3.          var predicate = PredicateBuilder.False<UserInfo>();  
    4.   
    5.          foreach (string keyword in keys)  
    6.          {  
    7.              predicate = predicate.Or(p => p.UserName.Contains(keyword));  
    8.          }  
    9.          return _context.UserInfoes.Where(predicate);  
    10.      }  

        至于PredicateBuilder的实现可以去他的博文中查看或者直接在nuget中查找添加LINQKit引用。PredicateBuilder很好的解决的动态生成Lambda问题,支持And/Or等主流运算。但它仍没能解决一个问题:如果查询条件中的属性(即数据库中的字段)也是不确定的,这样该如何处理?

        这时Scott大神站出来了。在他的博客Dynamic LINQ (Part 1: Using the LINQ Dynamic Query Library)中,他把EF整成了拼接SQL的方式来实现这个需求。如下:

    1. private IQueryable<UserInfo> Search(string key, string value)  
    2.       {  
    3.           return _context.UserInfoes.Where("@0 ='@1'", key, value);  
    4.       }  

        这样我们就不怕无尽变更的需求,想怎么调用都可以:

    1. var users = Search("UserNmae", "yubaolee");  //过滤用户名  
    2. ar users2 = Search("UserType", "administrator"); //过滤用户类型  

        你也可以使用Key-Value之类的组合成更强大的查询函数。然,世界上的事情总不是那么美好的。你会在实践中用着用着发现,丫竟然不支持 like,用着用着发现,丫竟然不支持guid等等。

        唉!我去,我等向来拿来就用之流,竟然会碰到这种鸟事。还是自己动手吧!

        好吧,下面才是博文主要内容,如果少年你没看到下面,啧啧!着实有些可惜…

        分析一下我们的需求:

    1. 可以根据用户提交过来的字符串还得到他想查找的属性(或数据库字段);
    2. 可以把传过来的操作转成一个比较型的lambda表达式;
    3. 得把该表达式传到EF查询的Where中;

        根据上面的需求,我们可以借鉴一下PredicateBuilder的实现方式,用表达式树来生成动态lambda,然后传到ef的过滤条件中。如下:

    1. public class Filter  
    2.    {  
    3.        public string Key { get; set; } //过滤的关键字  
    4.        public string Value { get; set; } //过滤的值  
    5.        public string Contract { get; set; }// 过滤的约束 比如:'<' '<=' '>' '>=' 'like'等  
    6.    }  
    7.   
    8.    public static class DynamicLinq  
    9.    {  
    10.        /// <summary>  
    11.        /// 创建lambda中的参数,即c=>c.xxx==xx 中的c  
    12.        /// </summary>  
    13.        public static ParameterExpression CreateLambdaParam<T>(string name)  
    14.        {  
    15.            return Expression.Parameter(typeof(T), name);  
    16.        }  
    17.   
    18.        /// <summary>  
    19.        /// 创建linq表达示的body部分,即c=>c.xxx==xx 中的c.xxx==xx  
    20.        /// </summary>  
    21.        public static Expression GenerateBody<T>(this ParameterExpression param, Filter filterObj)  
    22.        {  
    23.            PropertyInfo property = typeof(T).GetProperty(filterObj.Key);  
    24.   
    25.            //组装左边  
    26.            Expression left = Expression.Property(param, property);  
    27.            //组装右边  
    28.            Expression right = null;  
    29.   
    30.            //todo: 下面根据需要,扩展自己的类型  
    31.            if (property.PropertyType == typeof(int))  
    32.            {  
    33.                right = Expression.Constant(int.Parse(filterObj.Value));  
    34.            }  
    35.            else if (property.PropertyType == typeof(DateTime))  
    36.            {  
    37.                right = Expression.Constant(DateTime.Parse(filterObj.Value));  
    38.            }  
    39.            else if (property.PropertyType == typeof(string))  
    40.            {  
    41.                right = Expression.Constant((filterObj.Value));  
    42.            }  
    43.            else if (property.PropertyType == typeof(decimal))  
    44.            {  
    45.                right = Expression.Constant(decimal.Parse(filterObj.Value));  
    46.            }  
    47.            else if (property.PropertyType == typeof(Guid))  
    48.            {  
    49.                right = Expression.Constant(Guid.Parse(filterObj.Value));  
    50.            }  
    51.            else if (property.PropertyType == typeof(bool))  
    52.            {  
    53.                right = Expression.Constant(filterObj.Value.Equals("1"));  
    54.            }  
    55.            else  
    56.            {  
    57.                throw new Exception("暂不能解析该Key的类型");  
    58.            }  
    59.   
    60.            //todo: 下面根据需要扩展自己的比较  
    61.            Expression filter = Expression.Equal(left, right);  
    62.            switch (filterObj.Contract)  
    63.            {  
    64.                case "<=":  
    65.                    filter = Expression.LessThanOrEqual(left, right);  
    66.                    break;  
    67.   
    68.                case "<":  
    69.                    filter = Expression.LessThan(left, right);  
    70.                    break;  
    71.   
    72.                case ">":  
    73.                    filter = Expression.GreaterThan(left, right);  
    74.                    break;  
    75.   
    76.                case ">=":  
    77.                    filter = Expression.GreaterThanOrEqual(left, right);  
    78.                    break;  
    79.   
    80.                case "like":  
    81.                    filter = Expression.Call(left, typeof(string).GetMethod("Contains", new[] { typeof(string) }),  
    82.                                 Expression.Constant(filterObj.Value));  
    83.                    break;  
    84.            }  
    85.   
    86.            return filter;  
    87.        }  
    88.   
    89.        /// <summary>  
    90.        /// 创建完整的lambda,即c=>c.xxx==xx  
    91.        /// </summary>  
    92.        public static LambdaExpression GenerateLambda(this ParameterExpression param, Expression body)  
    93.        {  
    94.            return Expression.Lambda(body, param);  
    95.        }  
    96.   
    97.        /// <summary>  
    98.        /// 创建完整的lambda,为了兼容EF中的where语句  
    99.        /// </summary>  
    100.        public static Expression<Func<T, bool>> GenerateTypeLambda<T>(this ParameterExpression param, Expression body)  
    101.        {  
    102.            return (Expression<Func<T, bool>>)(param.GenerateLambda(body));  
    103.        }  
    104.   
    105.        public static Expression AndAlso(this Expression expression, Expression expressionRight)  
    106.        {  
    107.            return Expression.AndAlso(expression, expressionRight);  
    108.        }  
    109.   
    110.        public static Expression Or(this Expression expression, Expression expressionRight)  
    111.        {  
    112.            return Expression.Or(expression, expressionRight);  
    113.        }  
    114.   
    115.        public static Expression And(this Expression expression, Expression expressionRight)  
    116.        {  
    117.            return Expression.And(expression, expressionRight);  
    118.        }  
    119.    }  

        来看看我们客户端的调用:

    1. //模拟过滤对象  
    2.             var filters = new Filter[]  
    3.             {  
    4.                 new Filter {Key = "UserName", Value = "yubaolee", Contract = "like"},  
    5.                 new Filter {Key = "UserType", Value = "administrator", Contract = "="}  
    6.             };  
    7.   
    8.             var param = DynamicLinq.CreateLambdaParam<UserInfo>("c");  
    9.             Expression body = Expression.Constant(true); //初始默认一个true  
    10.             foreach (var filter in filters)  
    11.             {  
    12.                 body = body.AndAlso(param.GenerateBody<UserInfo>(filter)); //这里可以根据需要自由组合  
    13.             }  
    14.             var lambda = param.GenerateTypeLambda<UserInfo>(body);  //最终组成lambda  
    15.             
    16.             var users = _context.UserInfoes.Where(lambda);  //得到最终结果  
    17.             Console.Read();  

        这时我们可以自由组合,但客户端代码量看起来好像不少。我们来优化封装一下:

    1. public static class DynamicExtention   
    2. {  
    3.     public static IQueryable<T> Where<T>(this IQueryable<T> query, Filter[] filters)  
    4.     {  
    5.         var param = DynamicLinq.CreateLambdaParam<T>("c");  
    6.         Expression body = Expression.Constant(true); //初始默认一个true  
    7.         foreach (var filter in filters)  
    8.         {  
    9.             body = body.AndAlso(param.GenerateBody<T>(filter)); //这里可以根据需要自由组合  
    10.         }  
    11.         var lambda = param.GenerateTypeLambda<T>(body); //最终组成lambda  
    12.         return query.Where(lambda);  
    13.     }  
    14. }  

        最后看看我们客户端的调用:

    1. //模拟过滤对象  
    2.            var filters = new Filter[]  
    3.            {  
    4.                new Filter {Key = "UserName", Value = "yubaolee", Contract = "like"},  
    5.                new Filter {Key = "UserType", Value = "administrator", Contract = "="}  
    6.            };  
    7.   
    8.            var users = _context.UserInfoes.Where(filters);  //得到最终结果  
    9.            Console.Read();  

        代码如此的干净整洁。而且因为扩展的Where语句是基于泛型的,所以无论你的EF集合是哪种DbSet,都可以直接拿来使用。如果再把过滤类Filter功能深化,扩展成树状结构,那么可以实现种组合查询。哪怕是联表查询也不在话下。

  • 相关阅读:
    dljd_008_jdbc中调用Statement的execute()执行DQL,DDL,DML
    dljd_007_jdbc编程中的statement执行DML/DDL
    【数据结构】可持久化线段树
    【数据结构】可持久化并查集
    【图论】TarjanLCA算法
    【图论】KruskalMST算法
    【基础】标准模板
    【数学】位运算
    【数据结构】Trie
    【数据结构】线段树(名次树)
  • 原文地址:https://www.cnblogs.com/Alex80/p/5190130.html
Copyright © 2011-2022 走看看