c# - 在 SQL Server 上筛选 Entity Framework 结果

标签 c# entity-framework lambda linq-to-entities expression

首先:我们做 TPH(每个层次结构的表),这会使事情变得简单得多。

我有大约 20 个 POCO,它们在某些情况下都具有相似的属性。我关心的类似属性是 ___.CreatedDate___.UpdatedDate .对于某些 POCO,UpdatedDate没有意义,所以他们没有那个属性(property)。 CreatedDate将永远存在,UpdatedDate可能为空。有时还有第三个字段。

无论我查询的是 20 个 POCO 中的哪一个,从数据库中检索对象的查询看起来总是一样的。但是,该查询被重复了 20 次,每个对象对应一种类型。我能够将检索部分分解为扩展方法(关闭 IDbSet<T> )以进行初始查找和连接,将日期范围过滤留给消费者作为练习。我现在想将 20 个看起来几乎相同的日期范围过滤器合并为一个,但遇到了查询理解方面的问题。

根据 POCO,日期过滤逻辑是应该检查 UpdatedDate 并将其值与阈值进行比较。如果 UpdatedDate 为 null(或属性不存在),则应改用 CreatedDate。

我首先在每个 POCO 上创建一个静态属性 getter,我将其命名为“DateFields”。它的类型为 IEnumerable<Expression<Func<T, DateTime?>>>看起来像这样:

get
{
    yield return x => x.UpdatedDate;
    yield return x => x.CreatedDate;
    yield return x => x.Date;
}

这些字段按照我希望它们被检查的顺序返回,并且对于每个 POCO 都是唯一的。

然后我创建了一个谓词来根据高低范围检查每个值:

public static bool DatesBetween<T> (T value, IEnumerable<Expression<Func<T, DateTime?>>> dates, DateTime? dateFrom, DateTime? dateTo)
{
    var firstDate = dates
        .Select(expression => expression.Compile())
        .FirstOrDefault(func => func(value) != null);

    if (firstDate == null)
        return false;

    var minDate = dateFrom.GetValueOrDefault(SqlDateTime.MinValue.Value);
    var maxDate = dateTo.GetValueOrDefault(SqlDateTime.MaxValue.Value);

    var actualDate = firstDate(value);

    return (minDate <= actualDate && actualDate <= maxDate);
}

然而,正如预期的那样,当我尝试在我的 IQueryable 中使用它时,我得到一个运行时异常,因为在 SQL 中没有对 DatesBetween 的转换。我希望通过保留表达式树,我可以……我不知道……让 EF 开心。整个事情是这样使用的:

return Entities
    .GetEntitiesBySomeField(field := "magic value")
    .Where(entity => RepositoryExtensions.DatesBetween(entity, MyPOCO.MyDates, dateFrom, dateTo));

我问的很清楚,有没有一种方法可以在不执行 AsEnumerable () 的情况下完成这种通用过滤(它确实有效,但没有实现我在 DB 上过滤的目标)。

最佳答案

我最近在一个项目中做了类似的事情。我最终做的是实现存储库模式并在存储库上创建一个名为“ApplyFilter”的方法,它看起来像:

void ApplyFilter(Expression<Func<TEntity, bool>> predicate)
{
    if(this.resultSet == null)
    {
        this.resultSet = this.context.Set<TEntity>().AsQueryable();
    }

    this.resultSet = this.resultSet.Where(predicate);
}

我的存储库中有一个名为“ResultSet”的属性,它刚刚返回 IQueryable resultSet 字段并强制枚举结果(触发 EF 的数据库调用)。这样我就可以在枚举结果之前明确地应用所有动态创建的表达式。

我的代码与您的代码之间的真正区别在于,我的代码将 bool 谓词附加到最终表达式树,而当您尝试在最终表达式树中使用您的谓词时。我的方式基本上是为 SQL 创建/附加 WHERE 子句,您的方式尝试从“DatesBetween”方法生成 SQL 函数调用。

这里有一些尝试:

    public static Expression<Func<TEntity, bool>> MakeDateRange<TEntity>(DateTime? dateFrom, DateTime? dateTo)
    {
        var et = typeof(TEntity);
        var param = Expression.Parameter(et, "a");
        var prop = et.GetProperty("UpdatedDate");
        Expression body = null, left = null, right = null;
        if (prop == null)
        {
            prop = et.GetProperty("CreatedDate");
            if (prop == null)
            {
                prop = et.GetProperty("Date");
            }
        }

        if (dateFrom.HasValue)
        {
            left = Expression.GreaterThanOrEqual(Expression.PropertyOrField(param, prop.Name), Expression.Constant(dateFrom.GetValueOrDefault()));
        }

        if (dateTo.HasValue)
        {
            right = Expression.LessThanOrEqual(Expression.PropertyOrField(param, prop.Name), Expression.Constant(dateTo.GetValueOrDefault()));
        }

        if (left != null && right != null)
        {
            body = Expression.AndAlso(left, right);
        }
        else if (left != null)
        {
            body = left;
        }
        else
        {
            body = right;
        }

        return Expression.Lambda<Func<TEntity, bool>>(body, param);
    }

关于c# - 在 SQL Server 上筛选 Entity Framework 结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8933939/

相关文章:

c# - List.Select() 方法不执行 Func<> 内的方法

c# - 类似于 WPF 的 Firebug 工具?

entity-framework - 带有连接字符串的 Entity Framework DbContext 构造函数

entity-framework - 有没有办法在不处理和重新实例化 DbContext 的情况下重置它?

Python "TypeError: can only concatenate tuple (not "int") 到元组"

C# 方法返回与 lambda 中的变量

c# - LINQ Where 子句抛出从字符串到 Guid 的转换异常,即使比较仅包含 Guid

javascript - 如何从对象输出中删除函数?

c# - JavaScriptSerializer 无效的 JSON 原语

entity-framework - 如何在 Entity Framework 中自定义 DbContext 属性名称并防止被覆盖?