Как запросить большой DbSet с помощью AsNoTracking и CancellationToken

Я знаю, что в EF6 было проделано много работы для поддержки асинхронных операций, таких как CountAsync, но я не могу отменить простой запрос. Вот история.

У меня есть запрос, который возвращает 4,5 миллиона строк. Мне нужно обработать каждую строку, но я не могу удержать их все в памяти. EF6 достаточно любезен, чтобы позволить мне сделать это:

foreach (var row in context.TableX.AsNoTracking())
{
...process each row
}

Это прекрасно работает и использует очень мало памяти, но отменить его не так-то просто. Я попробовал эту глупость:

foreach (var row in context.TableX.AsNoTracking().ToListAsync(token).Result)
{
...process each row
}

Конечно, это пытается загрузить весь запрос в List‹>, что приводит к сбою задолго до загрузки всех строк. К счастью, он очень отзывчив на отмену. :)

Самое близкое, что я получил, это обернуть весь беспорядок следующим образом:

Task.Run(() => DoQuery(), token);

Это не поглощает память, и я могу отменить его, но отмена требует вечности, чтобы ответить, и есть несколько неприятных исключений, потому что я вытаскиваю ковер.

Что мне здесь не хватает?


person Doug Clutter    schedule 29.04.2016    source источник
comment
Когда вы хотите иметь возможность отменить - до получения первой строки или во время обработки?   -  person Evk    schedule 30.04.2016
comment
@Evk - Оба. Это работает внутри службы, и запрос на отмену может прийти в любое время.   -  person Doug Clutter    schedule 30.04.2016
comment
занимает вечность, потому что на самом деле ничего не отменяет. После того, как задача запущена, отменить ее невозможно. В .NET нет некооперативной отмены.   -  person usr    schedule 30.04.2016
comment
Возможно, вам следует запросить отмену с помощью ленивого перечисления в качестве функции EF (на GitHub), предполагая, что ответа не будет. Ответ Evk должен работать, но он не такой компонуемый, как цикл foreach. Поддержка EF была бы лучше.   -  person usr    schedule 30.04.2016
comment
@usr, как должна выглядеть эта потенциальная функция? Как я понимаю, после получения первого результата - вы можете отменить в любое время даже в простом цикле foreach, просто разбив\выбросив исключение на токен. Или вы имеете в виду какой-то метод расширения для отмены самого запроса до получения первого результата?   -  person Evk    schedule 30.04.2016
comment
@usr - Евк прав. Вы можете отменить запись после того, как появится первая запись, таким образом, мой комментарий останется навсегда.   -  person Doug Clutter    schedule 30.04.2016
comment
Это может выглядеть как foreach (var row in context.TableX.AsNoTracking().WithCancellation(token)). Это сохраняет модель вытягивания. ForEachAsync - это push, который менее компонуем. Например, вы больше не можете обрабатывать результаты как IEnumerable.   -  person usr    schedule 30.04.2016


Ответы (1)


Вы можете сделать это следующим образом:

public async Task DoQuery(CancellationToken token) {
    await ctx.TableX.AsNoTracking().ForEachAsync(row =>
    {
         // process here
    }, token);
}
person Evk    schedule 29.04.2016
comment
Спасибо!!! Работает как шарм. Обработка всех различных исключений, которые это выкашляло, была проблематичной, но я думаю, что понял. Мне приходилось обрабатывать обычные исключения OperationCancelledException, AggregateExceptions, которые содержали OperationCancelledExceptions, InnerExceptions, которые были OperationCancelledExceptions, и (мой любимый) DataExceptions, выбрасываемый моей базой данных SQL (Oracle). Слишком весело, а? - person Doug Clutter; 30.04.2016