Отмена запроса Entity Framework - PullRequest
27 голосов
/ 28 февраля 2011

Я нахожусь в процессе написания диспетчера запросов для приложения WinForms, которое, помимо прочего, должно быть в состоянии доставлять результаты поиска в режиме реального времени пользователю, когда он вводит запрос (представьте, что результаты Google в режиме реального времени хотя, очевидно, в среде толстых клиентов, а не в Интернете). Поскольку результаты должны начать поступать по мере ввода пользователем текста, поиск будет становиться все более и более конкретным, поэтому я хотел бы иметь возможность отменить запрос, если он все еще выполняется, пока пользователь вводит более конкретную информацию (поскольку результаты во всяком случае, просто отказаться)

Если бы это был обычный ADO.NET, я, очевидно, мог бы просто использовать функцию DbCommand.Cancel и покончить с этим, но мы используем EF4 для нашего доступа к данным, и не существует очевидного способа отменить запрос. Кроме того, открытие System.Data.Entity в Reflector и просмотр EntityCommand.Cancel показывает обескураживающе пустое тело метода, несмотря на то, что docs утверждает, что вызов этого параметра передаст его соответствующей функции Cancel команды провайдера.

Я подумал о том, чтобы просто запустить существующий запрос и ускорить новый контекст для выполнения нового поиска (и просто избавиться от существующего запроса после его завершения), но мне не нравится идея, что один клиент имеет множество открытых соединений с базой данных, выполняющих параллельные запросы, когда меня интересуют только результаты самого последнего.

Все это наводит меня на мысль, что просто невозможно отменить запрос EF после его отправки в базу данных, но я надеюсь, что кто-то здесь сможет указать на то, что я пропустил.

Версия TL / DR: возможно ли отменить запрос EF4, который выполняется в данный момент?

1 Ответ

12 голосов
/ 01 марта 2011

Похоже, вы нашли какую-то ошибку в EF, но когда вы сообщите об этом MS, это будет считаться ошибкой в ​​документации.В любом случае, мне не нравится идея прямого взаимодействия с EntityCommand.Вот мой пример, как убить текущий запрос:

var thread = new Thread((param) =>
    {
        var currentString = param as string;

        if (currentString == null)
        {
            // TODO OMG exception
            throw new Exception();
        }

        AdventureWorks2008R2Entities entities = null;
        try // Don't use using because it can cause race condition
        {
            entities = new AdventureWorks2008R2Entities();

            ObjectQuery<Person> query = entities.People
                .Include("Password")
                .Include("PersonPhone")
                .Include("EmailAddress")
                .Include("BusinessEntity")
                .Include("BusinessEntityContact");
            // Improves performance of readonly query where
            // objects do not have to be tracked by context
            // Edit: But it doesn't work for this query because of includes
            // query.MergeOption = MergeOption.NoTracking;

            foreach (var record in query 
                .Where(p => p.LastName.StartsWith(currentString)))
            {
                // TODO fill some buffer and invoke UI update
            }
        }
        finally
        {
            if (entities != null)
            {
                entities.Dispose();
            }
        }
    });

thread.Start("P");
// Just for test
Thread.Sleep(500);
thread.Abort();

Это результат моей игры через 30 минут, так что, вероятно, это не то, что следует рассматривать как окончательное решение.Я публикую его, чтобы хотя бы получить отзыв о возможных проблемах, вызванных этим решением.Основные моменты:

  • Контекст обрабатывается внутри потока
  • Результат не отслеживается контекстом
  • Если вы уничтожите поток, запрос будет прерван, а контекст удален (соединение разорвано)
  • Если вы уничтожаете поток перед тем, как начинать новый, вы должны использовать еще одно соединение.

Я проверил, что запрос запускается и завершается в профилировщике SQL.

Редактировать:

Кстати.Другой способ просто остановить текущий запрос - внутри перечисления:

public IEnumerable<T> ExecuteQuery<T>(IQueryable<T> query)
{
    foreach (T record in query)
    {
        // Handle stop condition somehow
        if (ShouldStop())
        {
            // Once you close enumerator, query is terminated
            yield break;
        }
        yield return record;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...