27

I'm in the process of writing a query manager for a WinForms application that, among other things, needs to be able to deliver real-time search results to the user as they're entering a query (think Google's live results, though obviously in a thick client environment rather than the web). Since the results need to start arriving as the user types, the search will get more and more specific, so I'd like to be able to cancel a query if it's still executing while the user has entered more specific information (since the results would simply be discarded, anyway).

If this were ordinary ADO.NET, I could obviously just use the DbCommand.Cancel function and be done with it, but we're using EF4 for our data access and there doesn't appear to be an obvious way to cancel a query. Additionally, opening System.Data.Entity in Reflector and looking at EntityCommand.Cancel shows a discouragingly empty method body, despite the docs claiming that calling this would pass it on to the provider command's corresponding Cancel function.

I have considered simply letting the existing query run and spinning up a new context to execute the new search (and just disposing of the existing query once it finishes), but I don't like the idea of a single client having a multitude of open database connections running parallel queries when I'm only interested in the results of the most recent one.

All of this is leading me to believe that there's simply no way to cancel an EF query once it's been dispatched to the database, but I'm hoping that someone here might be able to point out something I've overlooked.

TL/DR Version: Is it possible to cancel an EF4 query that's currently executing?

Adam Robinson
  • 182,639
  • 35
  • 285
  • 343
  • I have asked question about this problem on MSDN forum: http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/d5ea8036-73e5-4566-9407-fa7a6a5fca3c This original question is linked. Hopefully somebody from MS will provide another solution. – Ladislav Mrnka Mar 01 '11 at 12:23

1 Answers1

12

Looks like you have found some bug in EF but when you report it to MS it will be considered as bug in documentation. Anyway I don't like the idea of interacting directly with EntityCommand. Here is my example how to kill current query:

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();

It is result of my playing with if after 30 minutes so it is probably not something which should be considered as final solution. I'm posting it to at least get some feedback with possible problems caused by this solution. Main points are:

  • Context is handled inside the thread
  • Result is not tracked by context
  • If you kill the thread query is terminated and context is disposed (connection released)
  • If you kill the thread before you start a new thread you should use still one connection.

I checked that query is started and terminated in SQL profiler.

Edit:

Btw. another approach to simply stop current query is inside enumeration:

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;
    }
}
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • Interesting; as a point of clarification, I wasn't suggesting interacting with `EntityCommand` was the right approach, but that the fact that the method body is empty meant that the likelihood that it's possible at an even higher level was slim-to-none. This is an interesting scenario and may work in the end, but right now I do need change tracking if at all possible, so disposing of the context is not something I'm ready to do. If I can't find an alternative, I'll accept this. – Adam Robinson Feb 28 '11 at 21:45
  • @Adam: Ok, I will think about scenario with change tracking tomorrow because it has new consequences - Even if you cancel query all loaded instances will already be tracked by context. My initial suggestion is new context for each search but I have to think about it. – Ladislav Mrnka Feb 28 '11 at 22:30
  • I'm not really concerned with cancelling a query while it's loading. That's achieved easily enough with the approach you have above. What I'm looking for is cancelling it during the *query* stage (while it's executing server-side). – Adam Robinson Feb 28 '11 at 23:15
  • I'm going to go ahead and accept your answer. Even though it isn't *exactly* what I want, I have a feeling what I want isn't currently possible. Thanks! – Adam Robinson Mar 01 '11 at 13:33
  • I've now tried all sorts here - grabbing the DbConnection object from the context and dispose()ing it, close()ing it, etc., and also wrapping things in a transaction that I call Rollback() on. Nothing works except aborting the thread. :-( – Alastair Maw Dec 23 '11 at 11:49
  • 4
    [Why would try/finally rather than a "using" statement help avoid a race condition?](http://stackoverflow.com/questions/14830534/why-would-try-finally-rather-than-a-using-statement-help-avoid-a-race-conditio) – sloth Feb 12 '13 at 10:41
  • 1
    Doesn't work according to my tests in EF6. Even when a thread is aborted, the query keeps running on the database server. If I instead dispose the context, it blocks until the query is done. If I close the enumerator, it blocks until the query is done. If I close the connection, it blocks until the query is done. I wonder if that behavior changed - as @LadislavMrnka and others appear to think this should work. – John Jun 26 '17 at 06:24
  • Since as I said in my last comment none of this works for me, I now successfully got it to work by recording the session id before the query and then killing the session from a different connection. Obviously I need to give the database user excessive rights with this approach, but so far it's the only thing I've seen working. – John Jun 26 '17 at 07:18
  • doesn't work with Oracle - `thread.Abort();` hangs until the query is executed. – Toolkit Apr 19 '19 at 04:53