Создание эффективных левых анти-полусоединений в EF6 - PullRequest
0 голосов
/ 01 мая 2018

Я превращаю давнего, грубого спрока в 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 голосов
/ 02 мая 2018

Хотя в конечном итоге я не смог заставить Entity генерировать SQL так, как я хотел, я обнаружил, что могу выполнить отдельный запрос, чтобы получить необходимые данные из productsShipped, перетащить их в словарь и выполнить поиск оттуда .

0 голосов
/ 01 мая 2018

Вот метод расширения (из 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);

Возможно, это будет быстрее?

...