【问题标题】:Convert Expression<Func<T, V>> to Expression<Func<T, Nullable<V>>>将 Expression<Func<T, V>> 转换为 Expression<Func<T, Nullable<V>>>
【发布时间】:2013-08-23 05:43:38
【问题描述】:

我有一个方法,它采用 IOrderedQueryable 和 Expression> 作为过滤器和来自 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 秒,当您到达表中的高数百万时,处理整个月的数据可能需要很多时间小时。

鉴于集合中标记为完成的记录非常少 == false,那么只需选择记录 >= [Page Start] AND

随着月份的进展,最小值会增加,因此假设 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() 以便我返回 ExpressionExpression>> 并且我可以使用这种方式它在我的代码中的其他位置。

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

【问题讨论】:

    标签: c# linq lambda expression-trees nullable


    【解决方案1】:

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

    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,则会抛出异常。
    • 您还可以使用 David Fowl 的查询拦截器在 Execute() 处将 .Min(predicate) ?? 0 动态转换为 .Min(predicate.ToNullableExpression())
    • @Aron 你能解释一下在这种情况下ExpressionVisitor 会更好吗?我看不出它有什么不同。
    • 默认情况下ExpressionVisitor 将返回每个Expression 的新实例。我过去遇到过 EntityFramework “绑定”到表达式树的问题。每个表达式一旦绑定就只能在一个表达式树中使用...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-20
    相关资源
    最近更新 更多