linq 在运行时构建EF核心Where() predicate

kpbpu008  于 2023-01-18  发布在  其他
关注(0)|答案(1)|浏览(162)

我写了一段代码,在运行时构建一个EF Core Where() predicate 。我以前从未这样做过,但出于需求,我不得不这样做。
要求只是在CreatedOnUpdatedOn的7天内从数据库获取数据。
重构之前的版本是在循环中调用数据库7次,这存在性能问题。
当前实现按预期工作并返回预期结果。
这是当前代码:

private async Task SevenDaysCashOutFloor(DateTimeOffset today, IQueryable<BillPaymentVoucher> pastBillPayments, IQueryable<JournalVoucherPaymentVoucher> pastJournalVoucherPayments, CancellationToken token)
{
        Expression<Func<BillPaymentVoucher, bool>> predicate = null!;
        Expression<Func<BillPaymentVoucher, bool>> aggregatedPredicate = null!;
        BinaryExpression binaryExpression = null!;

        var param = Expression.Parameter(typeof(BillPaymentVoucher));

        today = DateTimeOffset.UtcNow;

        for (int days = 0; days < 7; days++)
        {
            var date = today.AddDays(-days);

            predicate = (entity) => (
                                        (entity.UpdatedOn.HasValue && entity.UpdatedOn.Value.Date == date.Date) ||
                                        (entity.UpdatedOn.HasValue == false && entity.CreatedOn.Date == date.Date)
                                    );

            binaryExpression = Expression.OrElse(ExpressionReplacer.GetBody(aggregatedPredicate ?? predicate, param),
                                                 ExpressionReplacer.GetBody(predicate, param));

            aggregatedPredicate = Expression.Lambda<Func<BillPaymentVoucher, bool>>(binaryExpression, param);
        }

        var finalPredicate = Expression.Lambda<Func<BillPaymentVoucher, bool>>(binaryExpression, param);

        pastBillPayments = pastBillPayments.Where(finalPredicate);          
}

我在运行时构建for循环中Where()所需的 predicate 。
一切都如预期的那样,但我想知道:
1.这是不是正确的写法
1.我应该使用复杂的Expression Tree吗?
1.有没有一个简单的方法可以做到这一点?
1.这个代码可以被重构吗?
我以前从未使用过表达式树。

ie3xauqp

ie3xauqp1#

Where总是在运行时构造的。也不需要显式地检查空值。表达式将被转换成显式支持NULL值的SQL。
此方法搜索UpdatedOnCreatedOn在某个日期范围内的行。您可以通过创建这些日期的列表并使用Contains来替换它:

var daysInWeek=Enumerable.Range(0,7)
                         .Select(i=>DateTime.Today.AddDays(-i))
                         .ToList();

pastBillPayments = pastBillPayments.Where(p=>daysInWeek.Contains(p.UpdatedOn.Date) 
                          || daysInWeek.Contains(p.CreatedOn.Date) );

这将产生:

WHERE ... cast(UpdatedOn as date) IN (@d1,@d2,...,@d7) OR 
          cast(CreatedOn as date) IN (@d1,@d2,...,@d7)

虽然有些数据库(例如SQL Server)可以将cast(UpdatedOn as date) = ...转换为可以使用索引的范围搜索,但它们不能使用索引统计信息。
更好的查询是显式搜索日期范围:

var dateTo=DateTime.Today.AddDays(1);
var dateFrom=DateTime.Today.AddDays(-6);

pastBillPayments = pastBillPayments
    .Where(p=> 
        (p.UpdatedOn >= dateFrom && p.UpdateOn  < dateTo) ||
        (p.CreatedOn >= dateFrom && p.CreatedOn < dateTo));

这消除了任何强制转换,从而允许服务器同时使用索引和统计信息

相关问题