代码之家  ›  专栏  ›  技术社区  ›  Code Uniquely

将表达式<Func<T,V>>转换为表达式<Func<T,Nullable<V>>>

  •  4
  • Code Uniquely  · 技术社区  · 12 年前

    我有一个方法,它采用IOrderedQueryable和Expression<函数<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 End]的工作速度非常快,每分钟大约有一百万张唱片。在某些情况下,处理量略小于传入的blockSize,但会处理最小值和最大值之间的所有记录。

    随着月份的推移,最小值会增加,因此假设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;
    

    采用显式重新定义所有选择器并为其提供重写的方法将意味着在整个应用程序中进行大量重复和重新编码

    我怎么能取一个原始的选择器,并一般地转换为可为null的等效版本,而不是必须显式地对每个选择器进行编码。

    var selector2 = selector.NullableExpression();
    

    我想将其作为Expression<函数<T、 V>>使得我返回ExpressionExpression<函数<T、 可为空<V>>>这样,我就可以在整个代码的其他位置使用它。

    我正在努力解决如何将V转换为Nullable或V?在表达式中。

    1 回复  |  直到 12 年前
        1
  •  5
  •   Ani    12 年前

    真的很简单。诀窍是在重用其参数的同时,处理源表达式的主体。

    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);
    }