• Linq to Entity经验:动态查询


        上篇文章(Linq to Entity经验:表达式转换)我分享了在使用Ling to Entity时,遇到的一个表达式转换问题,其主要解决的是让UI层调用数据查询时能够实现最大程度上的封装,使得我们的业务逻辑层在处理数据查询时更为精简,不再需要每一个条件写段逻辑。这篇我来总结下我们项目是中如何处理动态条件查询的问题。
       
        问题:

        如何解决动态条件查询,而继续保证业务逻辑层的稳定性?
        场景:

        搜索学生信息,我们可能按学号搜索,也可能按姓名搜索,还有可能按班级搜索,当然也有可能是其它条件,最复杂的情况是同时按多个条件查询。
       
        传统解决方案:

        遇到这种情况,基本上有两种类似的方法:
       
        1:拼接动态SQL
        因为不知道查询条件,所以可以采用拼接SQL字符串的形式来完成,它的缺点如下:
        缺点一:需要注意SQL注入,尽管我们可以采用参数化来解决。
        缺点二:需要人工去做这件事情。
        缺点三:这样的需求多了,也会大大增加程序员的工作量。
       
        2:大的通用性存储过程
        在存储过程中定义多个参数,然后在存储过程内部判断使用哪些条件,这个方案也有缺点:
        缺点一:程序中需要写大量这种有动态条件查询需求的存储过程,且逻辑相对复杂。
        缺点二:存储过程有自身的一些缺点,这里就不多讲了。
       
        解决方案:

        充分利用表达式树的作用来动态构建查询条件,以保证业务逻辑层的稳定性,减轻工作量。
       
        下面是我们业务逻辑层的查询接口方法:
        

    IList<ObjectModel.ActionInfo> QueryByPage<TKey>(Expression<Func<ObjectModel.ActionInfo, bool>> filter, Expression<Func<ObjectModel.ActionInfo, TKey>> orderBy, int orderType, int pageSize, int pageIndex, out int recordsCount)

        
        优点:
        1:无论UI上的条件是什么,只要在UI层构建好查询表达式,业务逻辑层的查询接口是不需要变更的。
        2:避免在条件中使用字符串,之前提到的两种方法都需要传递条件以确定最终的表字段信息,这是极其不高明的。
        3:基于Linq式的查询,使得程序员更加容易理解及接受。
       
        注意:
        这里定义的查询接口,是以一张主键为基础的,只要定义好相关的关联表,无论怎样复杂,此接口都不需要额外编写方法。
        比如有一个学生表:Student,学生表有一个外键列ClassId,对应的是班级表,我们可以这样写查询:if(班级!="") p=>p.Class.Name=="初二2008班" 即查询初二2008班所有学生信息。但它不能解决某些特别复杂的查询.需要按情况来决定。
       
        调用示例:

        这里先看下最终的效果。我们可以定义And,还可以定义Or,如果有需要还可以扩展其方法。
        

    Expression<Func<AllocationPlan, bool>> predicate = p => p.IsActive;
                if (planCondition.Project != 0) { predicate = predicate.And(c => c.ProjectId == planCondition.Project); }
                if (planCondition.PlanType != 0) { predicate = predicate.And(c => c.AllocationTypeId.Value == planCondition.PlanType); }

        方案原理:
        将两个表达式合并在一起,其实无论如何组织条件,超不出两类常见的表达式:
        1:And,对应SQL中的=,比如Where Name="Tom",它可以将多个条件And在一起变成 Where Name="Tome" and ClassId=1
        2:Or,对应SQL中的or,比如 Where EmployeeId=0 or EmployeeId=2
       
        这里不讨论SQL中的一些高级函数用法,只解决常见问题,下面是一老外写的,能够很好的解决动态条件查询时的表达式创建问题,可供参考:
        

    public static class PredicateBuilderUtility
    {
        public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
        {
            // build parameter map (from parameters of second to parameters of first)
            var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);
            // replace parameters in the second lambda expression with parameters from the first
            var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
            // apply composition of lambda expression bodies to parameters from the first expression 
            return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
        }
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.AndAlso);
        }
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.Or);
        }
    }

        这里有一个需要特别注意,就是多个表达式中的参数问题,有的时候将多个表达式合并在一起后,虽然程序中看起来没有什么问题,但当EntityFramwork执行数据库查询时会提示:参数p没有绑定之类的异常信息,它的目的就是统一多个表达式中的参数p。
        比如:
        表达式1:Expression<Func<AllocationPlan, bool>> predicate = p => p.IsActive;
        表达式1:Expression<Func<AllocationPlan, bool>> predicate2 = p => p.Id>0;
        某些情况下我们需要将上面两个表达式合并成一个,然后调用数据库查询,处理不当就会出现上面的错误。
        

    public class ParameterRebinder : ExpressionVisitor
    {
        private readonly Dictionary<ParameterExpression, ParameterExpression> map;
        public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
        {
            this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
        }
        public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
        {
            return new ParameterRebinder(map).Visit(exp);
        }
        protected override Expression VisitParameter(ParameterExpression p)
        {
            ParameterExpression replacement;
            if (map.TryGetValue(p, out replacement))
            {
                p = replacement;
            }
            return base.VisitParameter(p);
        }
    }

        

         总结:
         有了表达式合并的工具类,再结合仓储接口,我们可以写出简单容易理解动态条件查询的程序,也解决了其它传统方案的一些缺点,但这种方案自身也可能有自身的适用场景,适用自身项目的就是最优的,这是我的座右铭。

  • 相关阅读:
    密码盐 -- 为什么要在密码里加点“盐”
    Linux远程连接ssh工具(FinalShell)xshell替代神器
    模板文件不存在,无法解析文档”的几种解决办法
    9个PNG透明图片免费下载网站推荐
    我最近买的书里面带的CD盘,放电脑里后,说是0字节,但是可以播放,不能把里面的东西复制出来
    ASP程序加密/解密方法大揭密
    最稳定万能vip视频解析接口 支持HTTPS
    pycharm安装pyinstaller将pygame打包成exe
    Arduino101学习笔记(六)—— 高级IO
    Arduino101学习笔记(五)—— 模拟IO
  • 原文地址:https://www.cnblogs.com/ASPNET2008/p/2743053.html
Copyright © 2020-2023  润新知