在EF6中构建高效的左反半连接

时间:2018-04-30 22:01:43

标签: c# entity-framework entity-framework-6

我一直在将一个长期的,粗糙的sproc转换成EF,以使其进入一个更加可测试和可维护的位置。大部分都进​​展顺利,除了这部分:

select p.idProduct from products p
where
// {some random filtering bringing us down to a manageable number of rows}
AND p.idProduct NOT IN (SELECT idProduct from productsShipped)

我已将其转换为:

        var results = dbc.products.Where(p =>
            p.warehouse == warehouse
            && p.BarConversion.Bar.BarDate > minDate
            && !dbc.productsShipped.Any(ps => ps.idInventory == p.idInventory)
            //&& p.productsShipped == null
            && p.OPR.Order.Payment != null
            && !(p.OPR.Order.PaymentType == 5 &&
                 (p.OPR.Order.Payment.paymentStatus == null ||
                  p.OPR.Order.Payment.paymentStatus != "accepted"))
            && p.OPR.Order.OrderSla.expectedShipDate <= dueDateCutoff);

我遇到的问题是productsShipped表绝对是巨大的。在原始SQL中,where子句必须理解它不需要提取整个productsShipped表,而只需获取与先前查询相关的产品。 EF等价物将其分解为子查询并询问productsShipped表中的每个条目,导致查询花费超过五分钟,而不是在没有此过滤器的情况下运行几秒钟。我尝试在两个实体之间添加关系,结果相似。

有没有办法可以强制Entity进行正确的左外部独占连接而不是子查询,或类似地提高性能,或者我被迫要么将性能提升或将部分逻辑推入难以实现的状态测试sproc?

2 个答案:

答案 0 :(得分:0)

以下是左反半连接的扩展方法(来自MSDN):

public static IQueryable<TLeft> LeftAntiSemiJoin<TLeft, TRight>(this IQueryable<TLeft> left, IQueryable<TRight> right, Expression<Func<TLeft, TRight, bool>> predicate) {
    var leftPrm = predicate.Parameters[0];
    var rightPrm = predicate.Parameters[1];

    // retrieve methods
    var anyMethod = ((Func<IQueryable<TRight>, bool>)Queryable.Any).Method;
    var whereMethod = ((Func<IQueryable<TRight>, Expression<Func<TRight, bool>>, IQueryable<TRight>>)Queryable.Where).Method;

    // l => !right.Where(r => predicate(l, r)).Any()
    var leftPredicate = Expression.Lambda<Func<TLeft, bool>>(
        Expression.Not(
            Expression.Call(anyMethod,
                Expression.Call(whereMethod,
                    Expression.Constant(right),
                    Expression.Lambda<Func<TRight, bool>>(predicate.Body, rightPrm)))),
        leftPrm);

    return left.Where(leftPredicate);
}

您可以这样使用:

var results2 = dbc.products.LeftAntiSemiJoin(dbc.productsShipped, (p, ps) => p.idInventory == ps.idInventory)
                  .Where(p =>
                        p.warehouse == warehouse &&
                        p.BarConversion.Bar.BarDate > minDate &&
                        p.OPR.Order.Payment != null &&
                        !(p.OPR.Order.PaymentType == 5 &&
                          (p.OPR.Order.Payment.paymentStatus == null ||
                           p.OPR.Order.Payment.paymentStatus != "accepted")) &&
                        p.OPR.Order.OrderSla.expectedShipDate <= dueDateCutoff);

也许会更快?

答案 1 :(得分:0)

虽然我最终无法让Entity以我想要的方式生成SQL,但我发现我能够运行单独的查询以从productsShipped获取必要的数据,将其放入字典中,然后从那里进行查找