我正在使用 LINQ->WCF 数据服务->EF,它支持 LINQ 的一个子集,但有一些注意事项。一旦学习了各种技巧和解决方法,我就没有遇到任何问题,但我想制作一个可重用的表达式生成器,用于仅比较 Date
DateTime
的一部分.
对于常规 EF,您可以使用 EntityFunctions.TruncateTime
(EF<6) 或 DbFunctions.TruncateTime
(EF6+),但这不适用于数据服务。
到目前为止,我的解决方案一直是重复构建这个困惑的 where 子句:
.Where(x => x.DateProperty.Year == DateToCompare.Year &&
x.DateProperty.Month == DateToCompare.Month &&
x.DateProperty.Day == DateToCompare.Day);
不得不重复编写(但它有效)只是令人讨厌,所以我试图创建类似的东西:
.WhereDate(x => x.DateProperty, DateToCompare);
任何类似的东西都可以,只是简短、甜美和可读 - 我讨厌重复的不必要的代码。
结构不是问题,我知道我需要的是 IQueryable<T>
, Func<T, DateTime>
(或 Expression<Func<T, DateTime>>
)和 DateTime
并返回 IQueryable<T>
.
public static IQueryable<T> WhereDate<T>(this IQueryable<T> data, Func<T, DateTime>> selector, DateTime date)
{
return data.Where(/*Something*/);
};
我遇到麻烦的地方是采用它并构建一个可以放入该 where 子句而不违反表达式树限制的表达式。我不完全确定如何在不执行 .Where
的情况下采用现有查询并将我自己的 where 语句添加到表达式中,我认为这可能是这里的关键。我想我需要接受 Expression<Func<T, DateTime>>
并构建一些使用它来添加 Expression<Func<T, bool>> to the tree and return it as an
的东西IQueryable`。
有人对此有一些经验,或者知道我应该阅读哪些文档吗?
这里最大的障碍是您无法将基于语句的 lambda 转换为表达式,并且您无法将不受支持的函数传递到数据服务或 EF。这使得所有天真的解决方案都变得不可能,据我所知,还需要手动操作表达式。
这是我在阅读了很多有关该主题的内容后得出的解决方案:
private static IQueryable<T> _whereDate<T>(this IQueryable<T> 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<T>(whereClause);
}
public static IQueryable<T> WhereDate<T>(this IQueryable<T> data, Expression<Func<T, DateTime?>> 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<T> WhereDate<T>(this IQueryable<T> data, Expression<Func<T, DateTime>> 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);
}
拆分为多个函数以减少冗余代码,同时支持DateTime
和DateTime?
。
我意识到没有检查可空版本是否缺少值(value)——这是我很快就会添加的内容,但我想为其他人提供解决方案以供学习,并确保没有人浪费时间解释这个大部头书。为了效率和可读性,我总是检查我的代码几次,记录功能,评论不明确的事情,确保不会出现意外的 Exception
,但这是在此之前。如果您逐字使用此代码,请记住这一点(如果您这样做,请告诉我,我想知道我没有浪费精力发布此代码)。