.NET深入--EF到底怎么写过滤条件 【2016-03-28 02:41更新】

.NET深入--EF到底怎么写过滤条件

.NET

257 2016/3/28 02:03

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

 

/// <summary> 

/// 只是简单举例,只用了两个过滤条件 

/// </summary> 

IEnumerable<UserInfo> Search(string username = "", string usertype = "") 

    var query = _context.UserInfoes.AsQueryable(); 

    if (!string.IsNullOrEmpty(username)) 

        query = query.Where(u => u.UserName == username); 

    if (!string.IsNullOrEmpty(usertype)) 

        query = query.Where(u => u.UserType == usertype); 

 

    return query.ToList(); 

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

 

private IEnumerable<UserInfo> Search(params string[] keys) 

      { 

          var query = _context.UserInfoes.AsQueryable(); 

 

          foreach (var key in keys) 

          { 

              query = query.Where(u => u.UserName.Contains(key)); 

          } 

 

          return query.ToList(); 

      } 

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

 

IQueryable<UserInfo> Search(params string[] keys) 

     { 

         var predicate = PredicateBuilder.False<UserInfo>(); 

 

         foreach (string keyword in keys) 

         { 

             predicate = predicate.Or(p => p.UserName.Contains(keyword)); 

         } 

         return _context.UserInfoes.Where(predicate); 

     } 

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

 

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

 

private IQueryable<UserInfo> Search(string key, string value) 

      { 

          return _context.UserInfoes.Where("@0 ='@1'", key, value); 

      } 

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

 

var users = Search("UserNmae", "yubaolee");  //过滤用户名 

ar users2 = Search("UserType", "administrator"); //过滤用户类型 

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

 

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

 

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

 

 

 

    分析一下我们的需求:

 

可以根据用户提交过来的字符串还得到他想查找的属性(或数据库字段);

可以把传过来的操作转成一个比较型的lambda表达式;

得把该表达式传到EF查询的Where中;

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

 

public class Filter 

   { 

       public string Key { get; set; } //过滤的关键字 

       public string Value { get; set; } //过滤的值 

       public string Contract { get; set; }// 过滤的约束 比如:'<' '<=' '>' '>=' 'like' 

   } 

 

   public static class DynamicLinq 

   { 

       /// <summary> 

       /// 创建lambda中的参数,c=>c.xxx==xx 中的

       /// </summary> 

       public static ParameterExpression CreateLambdaParam<T>(string name) 

       { 

           return Expression.Parameter(typeof(T), name); 

       } 

 

       /// <summary> 

       /// 创建linq表达示的body部分,c=>c.xxx==xx 中的c.xxx==xx 

       /// </summary> 

       public static Expression GenerateBody<T>(this ParameterExpression param, Filter filterObj) 

       { 

           PropertyInfo property = typeof(T).GetProperty(filterObj.Key); 

 

           //组装左边 

           Expression left = Expression.Property(param, property); 

           //组装右边 

           Expression right = null; 

 

           //todo: 下面根据需要,扩展自己的类型 

           if (property.PropertyType == typeof(int)) 

           { 

               right = Expression.Constant(int.Parse(filterObj.Value)); 

           } 

           else if (property.PropertyType == typeof(DateTime)) 

           { 

               right = Expression.Constant(DateTime.Parse(filterObj.Value)); 

           } 

           else if (property.PropertyType == typeof(string)) 

           { 

               right = Expression.Constant((filterObj.Value)); 

           } 

           else if (property.PropertyType == typeof(decimal)) 

           { 

               right = Expression.Constant(decimal.Parse(filterObj.Value)); 

           } 

           else if (property.PropertyType == typeof(Guid)) 

           { 

               right = Expression.Constant(Guid.Parse(filterObj.Value)); 

           } 

           else if (property.PropertyType == typeof(bool)) 

           { 

               right = Expression.Constant(filterObj.Value.Equals("1")); 

           } 

           else 

           { 

               throw new Exception("暂不能解析该Key的类型"); 

           } 

 

           //todo: 下面根据需要扩展自己的比较 

           Expression filter = Expression.Equal(left, right); 

           switch (filterObj.Contract) 

           { 

               case "<=": 

                   filter = Expression.LessThanOrEqual(left, right); 

                   break; 

 

               case "<": 

                   filter = Expression.LessThan(left, right); 

                   break; 

 

               case ">": 

                   filter = Expression.GreaterThan(left, right); 

                   break; 

 

               case ">=": 

                   filter = Expression.GreaterThanOrEqual(left, right); 

                   break; 

  

               case "like": 

                   filter = Expression.Call(left, typeof(string).GetMethod("Contains", new[] { typeof(string) }), 

                                Expression.Constant(filterObj.Value)); 

                   break; 

           } 

 

           return filter; 

       } 

 

       /// <summary> 

       /// 创建完整的lambda,c=>c.xxx==xx 

       /// </summary> 

       public static LambdaExpression GenerateLambda(this ParameterExpression param, Expression body) 

       { 

           return Expression.Lambda(body, param); 

       } 

 

       /// <summary> 

       /// 创建完整的lambda,为了兼容EF中的where语句 

       /// </summary> 

       public static Expression<Func<T, bool>> GenerateTypeLambda<T>(this ParameterExpression param, Expression body) 

       { 

           return (Expression<Func<T, bool>>)(param.GenerateLambda(body)); 

       } 

 

       public static Expression AndAlso(this Expression expression, Expression expressionRight) 

       { 

           return Expression.AndAlso(expression, expressionRight); 

       } 

 

       public static Expression Or(this Expression expression, Expression expressionRight) 

       { 

           return Expression.Or(expression, expressionRight); 

       } 

 

       public static Expression And(this Expression expression, Expression expressionRight) 

       { 

           return Expression.And(expression, expressionRight); 

       } 

   } 

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

 

//模拟过滤对象 

            var filters = new Filter[] 

            { 

                new Filter {Key = "UserName", Value = "yubaolee", Contract = "like"}, 

                new Filter {Key = "UserType", Value = "administrator", Contract = "="} 

            }; 

 

            var param = DynamicLinq.CreateLambdaParam<UserInfo>("c"); 

            Expression body = Expression.Constant(true); //初始默认一个true 

            foreach (var filter in filters) 

            { 

                body = body.AndAlso(param.GenerateBody<UserInfo>(filter)); //这里可以根据需要自由组合 

            } 

            var lambda = param.GenerateTypeLambda<UserInfo>(body);  //最终组成lambda 

            

            var users = _context.UserInfoes.Where(lambda);  //得到最终结果 

            Console.Read(); 

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

 

public static class DynamicExtention  

    public static IQueryable<T> Where<T>(this IQueryable<T> query, Filter[] filters) 

    { 

        var param = DynamicLinq.CreateLambdaParam<T>("c"); 

        Expression body = Expression.Constant(true); //初始默认一个true 

        foreach (var filter in filters) 

        { 

            body = body.AndAlso(param.GenerateBody<T>(filter)); //这里可以根据需要自由组合 

        } 

        var lambda = param.GenerateTypeLambda<T>(body); //最终组成lambda 

        return query.Where(lambda); 

    } 

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

 

//模拟过滤对象 

           var filters = new Filter[] 

           { 

               new Filter {Key = "UserName", Value = "yubaolee", Contract = "like"}, 

               new Filter {Key = "UserType", Value = "administrator", Contract = "="} 

           }; 

 

           var users = _context.UserInfoes.Where(filters);  //得到最终结果 

           Console.Read(); 

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


上一篇 下一篇
music cover

歌曲名称

作者

00:00/00:00

歌名 歌手 时长