C# 如何在LINQ中重用字段过滤器到实体

C# 如何在LINQ中重用字段过滤器到实体,c#,entity-framework,linq-to-entities,C#,Entity Framework,Linq To Entities,我使用的是EF,有一个数据库表,其中有许多日期时间字段,在记录上执行各种操作时填充这些字段。 我目前正在构建一个报告系统,其中包括按这些日期进行过滤,但由于过滤器(该日期是否在某个范围内等)在每个字段上具有相同的行为,我希望重用我的过滤逻辑,因此我只编写一个日期过滤器,并在每个字段上使用它 我的初始筛选代码如下所示: DateTime? dateOneIsBefore = null; DateTime? dateOneIsAfter = null; DateTime? dateTwoIsBef

我使用的是EF,有一个数据库表,其中有许多日期时间字段,在记录上执行各种操作时填充这些字段。 我目前正在构建一个报告系统,其中包括按这些日期进行过滤,但由于过滤器(该日期是否在某个范围内等)在每个字段上具有相同的行为,我希望重用我的过滤逻辑,因此我只编写一个日期过滤器,并在每个字段上使用它

我的初始筛选代码如下所示:

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)