Подтвердить что ты не робот

Отмена запроса структуры Entity Framework

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

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

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

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

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

4b9b3361

Ответ 1

Похоже, вы нашли ошибку в 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.

Edit:

Btw. другой подход просто остановить текущий запрос находится внутри перечисления:

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;
    }
}