C# 如何在LINQ中重用字段过滤器到实体
我使用的是EF,有一个数据库表,其中有许多日期时间字段,在记录上执行各种操作时填充这些字段。 我目前正在构建一个报告系统,其中包括按这些日期进行过滤,但由于过滤器(该日期是否在某个范围内等)在每个字段上具有相同的行为,我希望重用我的过滤逻辑,因此我只编写一个日期过滤器,并在每个字段上使用它 我的初始筛选代码如下所示:C# 如何在LINQ中重用字段过滤器到实体,c#,entity-framework,linq-to-entities,C#,Entity Framework,Linq To Entities,我使用的是EF,有一个数据库表,其中有许多日期时间字段,在记录上执行各种操作时填充这些字段。 我目前正在构建一个报告系统,其中包括按这些日期进行过滤,但由于过滤器(该日期是否在某个范围内等)在每个字段上具有相同的行为,我希望重用我的过滤逻辑,因此我只编写一个日期过滤器,并在每个字段上使用它 我的初始筛选代码如下所示: DateTime? dateOneIsBefore = null; DateTime? dateOneIsAfter = null; DateTime? dateTwoIsBef
DateTime? dateOneIsBefore = null;
DateTime? dateOneIsAfter = null;
DateTime? dateTwoIsBefore = null;
DateTime? dateTwoIsAfter = null;
using (var context = new ReusableDataEntities())
{
IEnumerable<TestItemTable> result = context.TestItemTables
.Where(record => ((!dateOneIsAfter.HasValue
|| record.DateFieldOne > dateOneIsAfter.Value)
&& (!dateOneIsBefore.HasValue
|| record.DateFieldOne < dateOneIsBefore.Value)))
.Where(record => ((!dateTwoIsAfter.HasValue
|| record.DateFieldTwo > dateTwoIsAfter.Value)
&& (!dateTwoIsBefore.HasValue
|| record.DateFieldTwo < dateTwoIsBefore.Value)))
.ToList();
return result;
}
internal static IQueryable<TestItemTable> WhereFilter(this IQueryable<TestItemTable> source, Func<TestItemTable, DateTime> fieldData, DateTime? dateIsBefore, DateTime? dateIsAfter)
{
source = source.Where(record => ((!dateIsAfter.HasValue
|| fieldData.Invoke(record) > dateIsAfter.Value)
&& (!dateIsBefore.HasValue
|| fieldData.Invoke(record) < dateIsBefore.Value)));
return source;
}
我遇到的问题是使用Invoke来获取要查询的特定字段,因为这种技术不能很好地解决SQL问题,因为如果我将筛选代码修改为以下内容,它将运行时不会出错:
IEnumerable<TestItemTable> result = context.TestItemTables
.ToList()
.AsQueryable()
.WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter)
.WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter)
.ToList();
IEnumerable结果=context.TestItemTables
托利斯先生()
.AsQueryable()
.WhereFilter(记录=>record.DateFieldOne,dateOneIsBefore,dateOneIsAfter)
.WhereFilter(记录=>record.DateFieldTwo,dateTwoIsBefore,dateTwoIsAfter)
.ToList();
问题是代码(在使用扩展方法进行过滤之前,在整个表上使用ToList)会拉入整个数据库并将其作为对象查询,而不是查询底层数据库,因此不可伸缩
我还研究了使用Linqkit中的PredicateBuilder,但它无法找到不使用Invoke方法编写代码的方法
我知道有一些技术可以将查询的一部分表示为包含字段名的字符串,但我更愿意使用更安全的方式编写代码
此外,在理想情况下,我可以重新设计数据库,使其具有与单个“项”记录相关的多个“日期”记录,但我无权以这种方式更改数据库模式
我是否需要另外一种方法来编写扩展,使其不使用Invoke,或者我应该以另一种方式处理过滤代码的重用问题?是的,就是这样。但是,您的扩展方法中缺少了一些部分:
DateTime? dateOneIsBefore = null;
DateTime? dateOneIsAfter = null;
DateTime? dateTwoIsBefore = null;
DateTime? dateTwoIsAfter = null;
using (var context = new ReusableDataEntities())
{
IEnumerable<TestItemTable> result = context.TestItemTables
.WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter)
.WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter)
.ToList();
return result;
}
internal static IQueryable<TestItemTable> WhereFilter(this IQueryable<TestItemTable> source, Expression<Func<TestItemTable, DateTime>> fieldData, DateTime? dateIsBefore, DateTime? dateIsAfter)
{
source = source.AsExpandable().Where(record => ((!dateIsAfter.HasValue
|| fieldData.Invoke(record) > dateIsAfter.Value)
&& (!dateIsBefore.HasValue
|| fieldData.Invoke(record) < dateIsBefore.Value)));
return source;
}
在您的例子中,没有太多重复,但我仍将演示如何使用原始表达式(作为示例):
使用表达式一开始可能看起来很奇怪,但至少对于简单的情况来说,没有什么太复杂的。谢谢,不过我可能遗漏了一些东西。。。使用非空的日期筛选器值(很抱歉,我可能应该在示例代码中包含此值),例如:DateTime?dateOneIsAfter=新的日期时间(2000,12,31);我收到异常:ReusableDataFilter.FrontEnd.exe中发生类型为“System.InvalidCastException”的未处理异常其他信息:无法将类型为“System.Linq.Expressions.UnaryExpression”的对象强制转换为类型为“System.Linq.Expressions.MemberExpression”。行上:var dateFieldName=((MemberExpression)fieldData.Body).Member.Name@NvR更新了答案,以处理不可为空的日期。虽然OP抱怨此代码,但从例外情况来看,这意味着他必须以某种方式修改代码,
fieldData
类似于e=>e.DateFieldOne
,其主体
应该是MemberExpression
(但不知何故,这是一个unaryexpression
,他实际上可能使用e=>e.DateFieldOne.Value
)。您的原始代码(使用DateTime?
)应该适用于e=>DateFieldOne
,当前的代码应该适用于e=>e.DateFieldOne.Value
。所以我会为这项工作+1。OP可能会绕过学习表达式的机会。当然,这个网站是为了帮助理解,而不是为了现成的解决方案,我希望:)非常感谢-haim770和Evk解决方案都能完美工作,但不幸的是,尽管它们都是互补的,但我只允许将其中一个标记为公认的答案。
IEnumerable<TestItemTable> result = context.TestItemTables
.ToList()
.AsQueryable()
.WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter)
.WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter)
.ToList();
internal static IQueryable<TestItemTable> WhereFilter(this IQueryable<TestItemTable> source, Expression<Func<TestItemTable, DateTime>> fieldData, DateTime? dateIsBefore, DateTime? dateIsAfter)
{
source = source.AsExpandable().Where(record => ((!dateIsAfter.HasValue
|| fieldData.Invoke(record) > dateIsAfter.Value)
&& (!dateIsBefore.HasValue
|| fieldData.Invoke(record) < dateIsBefore.Value)));
return source;
}
IEnumerable<TestItemTable> result = context.TestItemTables
.WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter)
.WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter)
.ToList();
internal static class QueryableExtensions {
internal static IQueryable<T> WhereFilter<T>(this IQueryable<T> source, Expression<Func<T, DateTime>> fieldData, DateTime? dateIsBefore, DateTime? dateIsAfter) {
if (dateIsAfter == null && dateIsBefore == null)
return source;
// this represents you "record" parameter in lambda
var arg = Expression.Parameter(typeof(T), "record");
// this is the name of your field ("DateFieldOne")
var dateFieldName = ((MemberExpression)fieldData.Body).Member.Name;
// this is expression "c.DateFieldOne"
var dateProperty = Expression.Property(arg, typeof(T), dateFieldName);
Expression left = null;
Expression right = null;
// this is "c.DateFieldOne > dateIsAfter"
if (dateIsAfter != null)
left = Expression.GreaterThan(dateProperty, Expression.Constant(dateIsAfter.Value, typeof(DateTime)));
// this is "c.DateFieldOne < dateIsBefore"
if (dateIsBefore != null)
right = Expression.LessThan(dateProperty, Expression.Constant(dateIsBefore.Value, typeof(DateTime)));
// now we either combine with AND or not, depending on values
Expression<Func<T, bool>> combined;
if (left != null && right != null)
combined = Expression.Lambda<Func<T, bool>>(Expression.And(left, right), arg);
else if (left != null)
combined = Expression.Lambda<Func<T, bool>>(left, arg);
else
combined = Expression.Lambda<Func<T, bool>>(right, arg);
// applying that to where and done.
source = source.Where(combined);
return source;
}
}
WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter)