筛选SQL Server上的实体框架结果

本文关键字:实体 框架 结果 SQL Server 筛选 | 更新日期: 2023-09-27 18:21:04

首先:我们不是在做TPH(按层次结构的表),这会让事情简单得多。

我有大约20个POCO,它们在某些情况下都具有相似的特性。我关心的类似属性是___.CreatedDate___.UpdatedDate。对于一些POCO来说,UpdatedDate没有意义,所以它们没有这个属性。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上过滤的目标)。

筛选SQL Server上的实体框架结果

我最近在一个项目中做了类似的事情。我最终实现了存储库模式,并在存储库上创建了一个名为"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<TEntity>resultSet字段,并强制枚举结果(触发EF的数据库调用)。通过这种方式,在枚举结果之前,我可以应用所有动态创建的表达式。

我的代码和你的代码之间的真正区别是,我的代码将布尔谓词附加到最终表达式树中,当你试图在最终表达式树内使用你的布尔谓词时。我的方法基本上是在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);
    }

如果我理解的话,你想在多个结构相似的表上运行相同的查询吗?

如果是这种情况,请考虑创建一个定义这些属性的接口,并让POCO实现该接口。然后让您的泛型参数具有该接口的限制。从那里,您应该能够编写一个使用接口上属性的通用查询。