为IQueryable生成表达式

我正在使用LINQ-> WCF数据服务 – > EF,它支持LINQ的一个子集,但有一些注意事项。 一旦学习了各种各样的技巧和变通方法,我就没有遇到过麻烦,但是我想制作一个可重用的表达式生成器,用于仅比较DateTimeDate部分。

使用常规EF,您可以使用EntityFunctions.TruncateTime (EF <6)或DbFunctions.TruncateTimeDbFunctions.TruncateTime +),但这不适用于数据服务。

到目前为止,我的解决方案是反复构建where子句的混乱:

 .Where(x => x.DateProperty.Year == DateToCompare.Year && x.DateProperty.Month == DateToCompare.Month && x.DateProperty.Day == DateToCompare.Day); 

这只是令人讨厌的必须反复写(但它有效),所以我试图创建类似的东西:

 .WhereDate(x => x.DateProperty, DateToCompare); 

任何类似的东西都会做,只是简短,甜蜜和可读 – 我厌恶重复的不必要的感觉代码。

结构不是问题,我知道我需要一些需要IQueryableFunc (或Expression<Func> )和DateTime并返回IQueryable

 public static IQueryable WhereDate(this IQueryable data, Func> selector, DateTime date) { return data.Where(/*Something*/); }; 

我遇到麻烦的地方就是采用这个并构建一个可以放入where子句而不违反表达式树的限制的表达式。 我不完全确定如何获取现有查询并将我自己的where语句添加到表达式而不执行.Where ,我认为这可能是关键。 我想我需要接受一个Expression<Func>并构建一些使用它来Expression<Func> to the tree and return it as an添加一个Expression<Func> to the tree and return it as an IQueryable` Expression<Func> to the tree and return it as an

任何人都有这方面的经验,或者知道我应该阅读哪些文档?

这里最大的障碍是你无法将基于语句的lambda转换为表达式,并且不能将不支持的函数传递给数据服务 EF。 这使得所有天真的解决方案都变得不可能,据我所知,这会留下手动表达式操作。

这是我在阅读了很多关于这个主题后提出的解决方案:

 private static IQueryable _whereDate(this IQueryable data, MemberExpression date1Expression, ParameterExpression parameter, DateTime date) { var date1Year = Expression.Property(date1Expression, "Year"); var date1Month = Expression.Property(date1Expression, "Month"); var date1Day = Expression.Property(date1Expression, "Day"); var date2Year = Expression.Constant(date.Year); var date2Month = Expression.Constant(date.Month); var date2Day = Expression.Constant(date.Day); var yearsEqual = Expression.Equal(date1Year, date2Year); var monthsEqual = Expression.Equal(date1Month, date2Month); var daysEqual = Expression.Equal(date1Day, date2Day); var allPartsEqual = Expression.AndAlso(Expression.AndAlso(daysEqual, monthsEqual), yearsEqual); //Day->Month->Year to efficiently remove as many as possible as soon as possible. var whereClause = Expression.Call(typeof(Queryable), "Where", new Type[] { data.ElementType }, data.Expression, Expression.Lambda(allPartsEqual, parameter)); return data.Provider.CreateQuery(whereClause); } public static IQueryable WhereDate(this IQueryable data, Expression> selector, DateTime date) { var selectorMemberExpression = ((MemberExpression)selector.Body); var nullableDateProperty = (PropertyInfo)selectorMemberExpression.Member; var entityExpression = Expression.Parameter(typeof(T)); var date1Expression = Expression.Property(entityExpression, nullableDateProperty); return data._whereDate(Expression.PropertyOrField(date1Expression, "Value"), entityExpression, date); } public static IQueryable WhereDate(this IQueryable data, Expression> selector, DateTime date) { var selectorMemberExpression = ((MemberExpression)selector.Body); var dateProperty = (PropertyInfo)selectorMemberExpression.Member; var entityExpression = Expression.Parameter(typeof(T)); return data._whereDate(Expression.Property(entityExpression, dateProperty), entityExpression, date); } 

它分为多个函数来减少冗余代码并支持DateTimeDateTime?

我意识到可以对可空版本缺乏价值进行检查 – 这是我很快就会添加的内容,但我想让其他任何人都可以学习解决方案,并确保没有人浪费时间向我解释这一点。 我总是通过几次代码来提高效率和可读性,记录函数,评论不清楚的事情,确保不会出现意外的Exception ,但事先就是这样。 如果你逐字地使用这个代码,请记住这一点(如果你这样做,请告诉我,我想知道我并没有浪费时间来发布这个)。

您始终可以使用System.Linq.Expressions.Expression类方法构建所需的表达式。 然而,它很烦人,棘手且容易出错。

相反,您可以使用编译时原型表达式,使用我为Entity Framework + DayOfWeek的答案创建的小助手实用程序将参数替换为实际值:

 public static class ExpressionUtils { public static Expression> Expr(Expression> e) => e; public static Expression> Expr(Expression> e) => e; public static Expression> Expr(Expression> e) => e; public static Expression> Expr(Expression> e) => e; public static Expression> Expr(Expression> e) => e; public static Expression WithParameters(this LambdaExpression expression, params Expression[] values) { return expression.Parameters.Zip(values, (p, v) => new { p, v }) .Aggregate(expression.Body, (e, x) => e.ReplaceParameter(xp, xv)); } public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target) { return new ParameterReplacer { Source = source, Target = target }.Visit(expression); } class ParameterReplacer : ExpressionVisitor { public ParameterExpression Source; public Expression Target; protected override Expression VisitParameter(ParameterExpression node) { return node == Source ? Target : base.VisitParameter(node); } } } 

这个想法很简单。 您可以使用参数创建原型lambda表达式:

 var protoExpr = ExpressionUtils.Expr((DateTime x, DateTime y) => x.Year == y.Year && x.Month == y.Month && x.Day == y.Day); 

然后用实际表达式替换参数。

 var actualExpr = protoExpr.WithParameters(expr1, expr2); 

例如,有问题的方法可以像这样实现:

 public static class WcfQueryableExtensions { public static IQueryable WhereEqual(this IQueryable source, Expression> selector, DateTime date) { var dateValue = ExpressionUtils.Expr(() => date).Body; var predicate = Expression.Lambda>( ExpressionUtils.Expr((DateTime x, DateTime y) => x.Year == y.Year && x.Month == y.Month && x.Day == y.Day) .WithParameters(selector.Body, dateValue), selector.Parameters); return source.Where(predicate); } } 

但是,还有更通用的方法,它也适用于查询语法。 您使用自然的LINQ to Objects样式(使用CLR方法/属性/运算符)编写查询,然后使用单个扩展方法将查询转换为WCF兼容格式。 该方法本身将使用ExpressionVistor来重写查询表达式。 例如,以下是实现DateTime相等性的注意点:

 public static class WcfQueryableExtensions { public static IQueryable AsWcfQueryable(this IQueryable source) { var expression = new WcfConverter().Visit(source.Expression); if (expression == source.Expression) return source; return source.Provider.CreateQuery(expression); } class WcfConverter : ExpressionVisitor { protected override Expression VisitBinary(BinaryExpression node) { if (node.NodeType == ExpressionType.Equal && node.Left.Type == typeof(DateTime)) return ExpressionUtils.Expr((DateTime x, DateTime y) => x.Year == y.Year && x.Month == y.Month && x.Day == y.Day) .WithParameters(Visit(node.Left), Visit(node.Right)); return base.VisitBinary(node); } } } 

您可以在需要时相对轻松地添加其他转化。 它可以在上面的方法中,或者通过拦截链接post中的其他Visit方法。

样品用法:

 var query = (from x in myQueryable where x.DateProperty == DateToCompare ... select ... ).AsWcfQueryable()