Linq to Entity经验 - 动态查询

[摘要]本文介绍Linq to Entity如何处理动态条件查询的问题,并提供详细的示例代码供参考。

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

问题:

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

场景:

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

传统解决方案:

遇到这种情况,基本上有两种类似的方法:

1、拼接动态SQL

因为不知道查询条件,所以可以采用拼接SQL字符串的形式来完成,它的缺点如下:

缺点一:需要注意SQL注入,尽管我们可以采用参数化来解决。

缺点二:需要人工去做这件事情。

缺点三:这样的需求多了,也会大大增加程序员的工作量。

2、大的通用性存储过程

在存储过程中定义多个参数,然后在存储过程内部判断使用哪些条件,这个方案也有缺点:

缺点一:程序中需要写大量这种有动态条件查询需求的存储过程,且逻辑相对复杂。

缺点二:存储过程有自身的一些缺点,这里就不多讲了。

解决方案:

充分利用表达式树的作用来动态构建查询条件,以保证业务逻辑层的稳定性,减轻工作量。

下面是我们业务逻辑层的查询接口方法:

 

1 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,如果有需要还可以扩展其方法。

 

1 Expression<Func<AllocationPlan, bool>> predicate = p => p.IsActive;
2             if (planCondition.Project != 0) { predicate = predicate.And(c => c.ProjectId == planCondition.Project); }
3             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中的一些高级函数用法,只解决常见问题,下面是一老外写的,能够很好的解决动态条件查询时的表达式创建问题,可供参考:

 

1 public static class PredicateBuilderUtility
2 {
3     public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
4     {
5         // build parameter map (from parameters of second to parameters of first)
6         var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);
7         // replace parameters in the second lambda expression with parameters from the first
8         var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
9         // apply composition of lambda expression bodies to parameters from the first expression
10         return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
11     }
12     public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
13     {
14         return first.Compose(second, Expression.AndAlso);
15     }
16     public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
17     {
18         return first.Compose(second, Expression.Or);
19     }
20 }

 

这里有一个需要特别注意,就是多个表达式中的参数问题,有的时候将多个表达式合并在一起后,虽然程序中看起来没有什么问题,但当EntityFramwork执行数据库查询时会提示:参数p没有绑定之类的异常信息,它的目的就是统一多个表达式中的参数p。

比如:

表达式1:Expression<Func<AllocationPlan, bool>> predicate = p => p.IsActive;

表达式2:Expression<Func<AllocationPlan, bool>> predicate2 = p => p.Id>0;

某些情况下我们需要将上面两个表达式合并成一个,然后调用数据库查询,处理不当就会出现上面的错误。

 

1 public class ParameterRebinder : ExpressionVisitor
2 {
3     private readonly Dictionary<ParameterExpression, ParameterExpression> map;
4     public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
5     {
6         this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
7     }
8     public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
9     {
10         return new ParameterRebinder(map).Visit(exp);
11     }
12     protected override Expression VisitParameter(ParameterExpression p)
13     {
14         ParameterExpression replacement;
15         if (map.TryGetValue(p, out replacement))
16         {
17             p = replacement;
18         }
19         return base.VisitParameter(p);
20     }
21 }

 

总结:

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

转至:http://dotnet.9sssd.com/entfwk/art/960

posted @ 2013-01-14 13:58  不弃的追求  阅读(251)  评论(0编辑  收藏  举报