views:

694

answers:

3

I am building an application which requires me to make use of DataContext's inside threads. My application keeps throwing InvalidOperationException's similar to:

There is already an open DataReader associated with this Command which must be closed first

ExecuteReader requires an open and available Connection. The connection's current state is connecting

These exceptions are intermittant.

Here is a snippet of my code:

var repo = new Repository();
var entities = repo.GetAllEntities();
foreach (var entity in entities)
{
    ThreadPool.QueueUserWorkItem(
        delegate
        {
            try
            {
                 ProcessEntity(entity);
            }
            catch (Exception)
            {
                throw;
            }
        });
}

I think it may have something to with passing an entity to a thread from the main thread as the error seems to throw as soon as I try and access a property of entity.

Anyone have any idea's why this is happening and how I can resolve it?

Update

Here is what I decided to go with:

var events = new Dictionary<int, AutoResetEvent>();
var repo = new Repository();
var entities = repo.GetAllEntities();
foreach (var entity in entities)
{
    events.Add(entity.ID, new AutoResetEvent(false));
    ThreadPool.QueueUserWorkItem(
        delegate
        {
            var repo = new Repository();
            try
            {
                ProcessHierarchy(repo.GetEntity(entity.ID), ReportRange.Weekly);
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                events[entity.ID].Set();
            }
        });
}

WaitHandle.WaitAll(events.Values.ToArray());

Improvements/Suggestions welcome, but this seems to have done the trick.

A: 

Try creating the Repository inside the new thread instead of passing it in.

Rory
I need to find the entity outside it. However, I suppose I could just send in the ID of the entity and re-fetch it inside the thread.
James
That sounds like your best bet, I don't think its safe to pass ObjectContext instances around the way you're trying to do.
Rory
@James given the way you are creating the workitem for the thread (using an anonymous delegate), you should be able to rely on c# closure support to capture the value of the ID instead of having to pass it in. Take a look at http://blogs.msdn.com/matt/archive/2008/03/01/understanding-variable-capturing-in-c.aspx for more info.
Rory
@James After reviewing your code again, I realised that my suggestion changes the way your query is processed (having the query and processing done on a background thread vs. having the query run on one thread and the processing done on multiple background threads). If you want to keep your processing semantics unchanged, Elisha's answer would be the better choice. The only catch is having to force the related entities you want to access during processing to load before creating the new thread. Sorry for wasting any time you may have spent on it already.
Rory
No worries Rory, thanks for your suggestions.
James
I passed in the ID and just created a new context and retrieved the entity within the Thread. You reckon this would suffice?
James
Yeah, that should prevent the exception, but like I said in my previous comment, it does mean that your processing now happens on a single thread instead of multiple background threads. If that isn't a problem for you, then I would stick with this solution, otherwise Elisha's is better.
Rory
+4  A: 

The exception is thrown since some properties of entity executes new query while a previous reader has not been closed yet. You cannot execute more than one query on the data context at the same time.

As a workaround you can "visit" the properties you access in ProcessEntity() and make the SQL run prior to the thread.

For example:

var repo = new Repository();
var entities = repo.GetAllEntities();
foreach (var entity in entities)
{
    var localEntity = entity; // Verify the callback uses the correct instance
    var entityCustomers = localEntity.Customers;
    var entityOrders = localEntity.Orders;
    var invoices = entityOrders.Invoices;
    ThreadPool.QueueUserWorkItem(
        delegate
        {
            try
            {
                 ProcessEntity(localEntity);
            }
            catch (Exception)
            {
                throw;
            }
        });
}

This workaround will execute the SQL only on the main thread and the processing will be done in other threads. You loose here some of the efficiency since all the queries are done in the single thread. This solution is good if you have a lot of logic in ProcessEntity() and the queries are not very heavy.

Elisha
So your suggesting I retrieve all the possible data I will need to do the processing inside my `ProcessEntity` method beforehand? Surely there must be a better way? Is it not safe enough to use DataContexts inside the threads?
James
The better way is to run on one context the the query for Entities and inside each WorkItem start new DataContext, but if DataContext does not use some sort of connections pool you can easily reach max number of connections and encounter new exception :)Another solution is to visit all properties marked as data members using reflection and avoid the explicit access to properties. It'll behave the same and your DB query won't run in background thread...
Elisha
Ah so in each of my anon delegates retrieve the entity again and just work off that datacontext! It's pretty unlikely (at the minute) that the connections will get to max at the moment however is there a better way of managing connection sizes? I though SQL managed this internally?
James
I am not sure about it, but I think that it manages the connection in a way where if you go over the limit it waits for a connection to be released. I am very unsure about it, I haven't played with it for long time :)
Elisha
Ok I will have a deeper look into it, thanks for your advice!
James
@James Elisha is right, SQL Server will stop accepting connections once it reaches its limit, all new connection attempts will block until one of the existing connections closes or the connection timeout on the connection attempt is reached. DbLinq should respect .NET's connection pool because under the hood it simply uses the ADO.NET provider to connect to the server.
Rory
A: 

Be aware that a SqlConnection instance is NOT thread safe. Whether you still have an open reader or not. In general, the access of a SqlConnection instance from multiple threads will cause you unpredictable intermittent problems.

See: http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnection.aspx

Alex
What is the best practise then when trying to develop a multi-threaded solution in this type of situation then? As Elisha suggested retrieving all the data before hand?
James