将Expression<Func<T, V>>转换为Expression<Func<T, Nullable<V>>>。

4

我有一个方法,它接收一个IOrderedQueryable对象和Expression<Func<T, V>>表达式作为过滤器,并从SQL数据库中分页获取记录。

var query = contexBills.AsNoTracking().Where(x => x.Complete==true).OrderBy(x => x.BillID);

var reader = new BulkReader<Bill>(query, x => x.BillId, 10000);

批量读取器在代码中广泛使用,用于分页大量记录并批量处理它们,定义如下:

public BulkReader(IOrderedQueryable<T> queryable, Expression<Func<T, Object>> selector, int blockSize = 1000)

为了优化分页,从表中找到的最小值开始,结束于最大值。由于每个月数据库中有数百万条记录,使用Skip().Take()方法在表中数据达到几百万时会降至约13秒/页,处理整个月的数据可能需要多个小时。
考虑到集合中标记为complete == false的记录非常少,因此仅选择记录 >= [Page Start] AND < [Page End] 可以快速处理每分钟约一百万条记录。在某些情况下,您可以处理比传递的块大小略少的记录,但是所有介于最小值和最大值之间的记录都将被处理。
随着时间的推移,最小值会增加,因此假设0为最小值会浪费很多SQL调用,这些调用根本没有返回任何内容。
因此,我需要获取这些值:
var min = queryable.Select(selector).DefaultIfEmpty(0).Min();
var max = queryable.Select(selector).DefaultIfEmpty(0).Max();

这会生成类似于以下的SQL语句:
SELECT 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
MIN([Join1].[A1]) AS [A1]
FROM ( SELECT 
    CASE WHEN ([Project1].[C1] IS NULL) THEN 0 ELSE [Project1].[PrintSummaryID] END AS [A1]
    FROM   ( SELECT 1 AS X ) AS [SingleRowTable1]
    LEFT OUTER JOIN  (SELECT 
        [Extent1].[PrintSummaryID] AS [PrintSummaryID], 
        cast(1 as tinyint) AS [C1]
        FROM [dbo].[tblPrintSummary] AS [Extent1] ) AS [Project1] ON 1 = 1
)  AS [Join1]
)  AS [GroupBy1]
GO

如果我手写代码(作为一个测试)来进行这样的调用
var min = queryable.Min(x =>(int?)x.BillID) ?? 0;
var max = queryable.Max(x =>(int?)x.BillID) ?? 0;

如果产生 SQL,则其语句如下:
SELECT 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
MIN([Extent1].[PrintSummaryID]) AS [A1]
FROM [dbo].[tblPrintSummary] AS [Extent1]
)  AS [GroupBy1]
GO

可以通过声明以下内容来实现同样的效果:
Expression<Func<Bill, int?>> selector2 = x => x.BillID;

这样做可以简化和加快SQL执行,使代码变得更简洁:

var min = queryable.Select(selector2).Min() ?? 0;
var max = queryable.Select(selector2).Max() ?? 0;

采用显式重新定义所有选择器并为其提供覆盖的方法将意味着需要在整个应用程序中进行大量重复编码和重构。
我该如何以通用的方式获取原始选择器并将其转换为等效的可空版本,而不必明确编写每个选择器的代码。
var selector2 = selector.NullableExpression();

我希望能够将此作为Expression >的扩展方法NullableExpression(),以便我返回一个Expression >>,这样我就可以在代码中的其他位置使用它。
我正在努力想弄清楚如何在表达式中将V转换为Nullable或V?请帮忙解决。
1个回答

5

非常简单,真的。诀窍在于玩弄源表达式的主体,同时重用它的参数。

public static Expression<Func<T, V?>> ToNullableExpression<T, V> 
    (this Expression<Func<T, V>> source) where V : struct
{ 
    if(source == null)
       throw new ArgumentNullException("source");

    var body = Expression.Convert(source.Body, typeof(V?));
    var parameters = source.Parameters;

    return Expression.Lambda<Func<T, V?>>(body, parameters);
}

我个人会使用ExpressionVisitor,因为在某些情况下,如果源表达式不完全是瞬态的,你可能会遇到EntityFramework的问题。也就是说,如果结果和输入都与EntityFramework“绑定”,则会抛出异常。 - Aron
您还可以使用David Fowl的查询拦截器,在Execute()时动态地将.Min(predicate) ?? 0转换为.Min(predicate.ToNullableExpression()) - Aron
1
@Aron,你能解释一下在这种情况下ExpressionVisitor会更好吗?我不明白它如何有所改进。 - svick
默认情况下,ExpressionVisitor 将返回每个 Expression 的新实例。我过去曾遇到过 EntityFramework 绑定到表达式树的问题。每个表达式只能在绑定后在一个表达式树中使用一次... - Aron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接