Linq to Sql : 动态构造Expression进行动态查询
2014-08-21 13:12
393 查看
原文:Linq to Sql : 动态构造Expression进行动态查询 前一篇在介绍动态查询时,提到一个问题:如何根据用户的输入条件,动态构造这个过滤条件表达式呢?
Expression<Func<ProductExt, bool>> predicate t => t.ProductName.Contains("che") && t.UnitPrice >= 22;
理想情况下,我希望可以像下面这样来构造predicate,这样,我们就可以使用&、| 、&=、|=来任意拼接过滤条件了:
但是理想与现实之间,似乎总有不可逾越的鸿沟……
前面的代码中,我们总是要写一常串Expression<Func<T, bool>>,写得都有点儿烦了,我妄想自定义一个类型,这样就不用每次都写这么长了,然后再针对这个自定义类型再进行运算符重载……然后我发现,Expression<Delegate>是密封类型(sealed),不给重载,没办法,只好老老实实的写,运算符重载也泡汤了。
无奈之下,只好实现了下面的这种方式,勉强凑合用着:
运算得到的结果如下:p => (((p.CompanyName.Length > 2) || p.ProductName.Contains("che")) && (p.UnitPrice >= 22))
有了这个OrElse和AndAlso扩展,我们就可以对Expression<Func<T, bool>>为所欲为了……
不过跟之前文章中提到的一样,这里有个限制:虽然我们的Expression中,第一个可以凡泛型参数可以传任意值(譬如传ProductExt或Products或其他的,可以不必要求是同一种类型),但条件中用到的对象属性(譬如CompanyName、ProductName、UnitPrice ),必须在T中存在同名属性。
2010-12-31更新:
更新了ParameterConverter类,之前对二元运算符的右侧和方法参数直接求值的,所以不能处理Expression中含有&& 和 || 等二元运算符、及方法参数中包含Expression参数(例如list.Contains(p.CompanyName))的情况;现在增加了参数访问计数,如果二元表达式&&和||、及方法的参数中不包含Expression参数,则进行求值,否则不求值。代码如下:
1 public class ParameterConverter : ExpressionVisitor
2 {
3 protected LambdaExpression SourceExpression { get; set; }
4 protected ParameterExpression Parameter { get; set; }
5 protected bool UseOuterParameter = false;
6
7 public ParameterConverter(LambdaExpression expression)
8 {
9 this.SourceExpression = (LambdaExpression)expression;
}
public ParameterConverter(LambdaExpression expression, ParameterExpression parameter) : this(expression)
{
this.Parameter = parameter;
this.UseOuterParameter = true;
}
public LambdaExpression Replace(Type targetType)
{
if (this.SourceExpression == null)
return null;
if (!this.UseOuterParameter)
this.Parameter = Expression.Parameter(targetType, this.SourceExpression.Parameters[0].Name);
Expression body = this.Visit(this.SourceExpression.Body);
this.SourceExpression = Expression.Lambda(body, this.Parameter);
return this.SourceExpression;
}
protected override Expression VisitParameter(ParameterExpression p)
{
return this.Parameter;
}
protected override Expression VisitMemberAccess(MemberExpression m)
{
Expression exp = this.Visit(m.Expression);
PropertyInfo propertyInfo = m.Member as PropertyInfo;
return propertyInfo == null ? m : Expression.Property(exp, propertyInfo.Name);
}
protected override Expression VisitBinary(BinaryExpression b)
{
Expression left = this.Visit(b.Left);
Expression right = Calc(b.Right);//对二元运算符右边的表达式进行求值
Expression conversion = this.Visit(b.Conversion);
if (b.NodeType == ExpressionType.Coalesce && b.Conversion != null)
return Expression.Coalesce(left, right, conversion as LambdaExpression);
else
return Expression.MakeBinary(b.NodeType, left, right, b.IsLiftedToNull, b.Method);
}
protected override ReadOnlyCollection<Expression> VisitExpressionList(
ReadOnlyCollection<Expression> original)
{
if (original == null || original.Count == 0)
return original;
//对参数进行求值运算
List<Expression> list = new List<Expression>();
for (int i = 0, n = original.Count; i < n; i++)
{
list.Add(Calc(original[i])); //对调用函数的输入参数进行求值
}
return list.AsReadOnly();
}
private static Expression Calc(Expression e)
{
LambdaExpression lambda = Expression.Lambda(e);
return Expression.Constant(lambda.Compile().DynamicInvoke(null), e.Type);
}
}
最后,上代码:LinqToSqlExtension
博客园的文件貌似有缓存,删掉重新上传,总是覆盖不了,只好单独上传了 ParameterConverter.rar
Expression<Func<ProductExt, bool>> predicate t => t.ProductName.Contains("che") && t.UnitPrice >= 22;
理想情况下,我希望可以像下面这样来构造predicate,这样,我们就可以使用&、| 、&=、|=来任意拼接过滤条件了:
1: Expression<Func<ProductExt, bool>> predicate = null;
2: predicate &= (t => t.ProductName.Contains("che")) | (t => t.UnitPrice >= 22);
但是理想与现实之间,似乎总有不可逾越的鸿沟……
前面的代码中,我们总是要写一常串Expression<Func<T, bool>>,写得都有点儿烦了,我妄想自定义一个类型,这样就不用每次都写这么长了,然后再针对这个自定义类型再进行运算符重载……然后我发现,Expression<Delegate>是密封类型(sealed),不给重载,没办法,只好老老实实的写,运算符重载也泡汤了。
无奈之下,只好实现了下面的这种方式,勉强凑合用着:
1: Expression<Func<ProductExt, bool>> predicate = null;
2: predicate = predicate.AndAlso(p => p.CompanyName.Length > 2)
3: .OrElse((Products p) => p.ProductName.Contains("che"))
4: .AndAlso((Products p) => p.UnitPrice >= 22);
运算得到的结果如下:p => (((p.CompanyName.Length > 2) || p.ProductName.Contains("che")) && (p.UnitPrice >= 22))
有了这个OrElse和AndAlso扩展,我们就可以对Expression<Func<T, bool>>为所欲为了……
不过跟之前文章中提到的一样,这里有个限制:虽然我们的Expression中,第一个可以凡泛型参数可以传任意值(譬如传ProductExt或Products或其他的,可以不必要求是同一种类型),但条件中用到的对象属性(譬如CompanyName、ProductName、UnitPrice ),必须在T中存在同名属性。
2010-12-31更新:
更新了ParameterConverter类,之前对二元运算符的右侧和方法参数直接求值的,所以不能处理Expression中含有&& 和 || 等二元运算符、及方法参数中包含Expression参数(例如list.Contains(p.CompanyName))的情况;现在增加了参数访问计数,如果二元表达式&&和||、及方法的参数中不包含Expression参数,则进行求值,否则不求值。代码如下:
1 public class ParameterConverter : ExpressionVisitor
2 {
3 protected LambdaExpression SourceExpression { get; set; }
4 protected ParameterExpression Parameter { get; set; }
5 protected bool UseOuterParameter = false;
6
7 public ParameterConverter(LambdaExpression expression)
8 {
9 this.SourceExpression = (LambdaExpression)expression;
}
public ParameterConverter(LambdaExpression expression, ParameterExpression parameter) : this(expression)
{
this.Parameter = parameter;
this.UseOuterParameter = true;
}
public LambdaExpression Replace(Type targetType)
{
if (this.SourceExpression == null)
return null;
if (!this.UseOuterParameter)
this.Parameter = Expression.Parameter(targetType, this.SourceExpression.Parameters[0].Name);
Expression body = this.Visit(this.SourceExpression.Body);
this.SourceExpression = Expression.Lambda(body, this.Parameter);
return this.SourceExpression;
}
protected override Expression VisitParameter(ParameterExpression p)
{
return this.Parameter;
}
protected override Expression VisitMemberAccess(MemberExpression m)
{
Expression exp = this.Visit(m.Expression);
PropertyInfo propertyInfo = m.Member as PropertyInfo;
return propertyInfo == null ? m : Expression.Property(exp, propertyInfo.Name);
}
protected override Expression VisitBinary(BinaryExpression b)
{
Expression left = this.Visit(b.Left);
Expression right = Calc(b.Right);//对二元运算符右边的表达式进行求值
Expression conversion = this.Visit(b.Conversion);
if (b.NodeType == ExpressionType.Coalesce && b.Conversion != null)
return Expression.Coalesce(left, right, conversion as LambdaExpression);
else
return Expression.MakeBinary(b.NodeType, left, right, b.IsLiftedToNull, b.Method);
}
protected override ReadOnlyCollection<Expression> VisitExpressionList(
ReadOnlyCollection<Expression> original)
{
if (original == null || original.Count == 0)
return original;
//对参数进行求值运算
List<Expression> list = new List<Expression>();
for (int i = 0, n = original.Count; i < n; i++)
{
list.Add(Calc(original[i])); //对调用函数的输入参数进行求值
}
return list.AsReadOnly();
}
private static Expression Calc(Expression e)
{
LambdaExpression lambda = Expression.Lambda(e);
return Expression.Constant(lambda.Compile().DynamicInvoke(null), e.Type);
}
}
最后,上代码:LinqToSqlExtension
博客园的文件貌似有缓存,删掉重新上传,总是覆盖不了,只好单独上传了 ParameterConverter.rar
相关文章推荐
- Linq to Sql : 动态构造Expression进行动态查询
- Linq to Sql:N层应用中的查询(下) : 根据条件进行动态查询
- Linq to Sql:N层应用中的查询(下) : 根据条件进行动态查询
- Linq to Sql:N层应用中的查询(下) : 根据条件进行动态查询
- LINQ体验(17)——LINQ to SQL语句之动态查询
- [导入]LINQ体验(17)——LINQ to SQL语句之动态查询
- linq to sql的多条件动态查询
- linq to sql的多条件动态查询(下)
- linq to sql的多条件动态查询(下)
- LINQ to SQL 运行时动态构建查询条件
- MVC中使用Linq To Sql进行数据查询及分页
- LINQ to SQL语句之动态查询高级特性
- Linq To SQL动态查询 之 IQueryBuilder
- LINQ to SQL 运行时动态构建查询条件
- LINQ体验(17)——LINQ to SQL语句之动态查询
- linq to sql的多条件动态查询(上)
- LINQ : 对LINQ TO SQL 查询进行编译
- LINQ : 对LINQ TO SQL 查询进行编译
- QueryBuilder : 打造优雅的Linq To SQL动态查询
- LINQ体验(17)——LINQ to SQL语句之动态查询