Entity Framework Transaction With Multiple Threads
I have an application running multiple threads. The threads do NOT share an ObjectContext (each thread has its own - I know they are not thread safe).
However, the th开发者_JAVA技巧reads are all operating under a shared Transaction. The original thread creates a TransactionScope and each thread it spawns creates a TransactionScope using a DependentTransaction from the Transaction on the main thread.
When multiple ObjectContext requests run at the same time, I sometimes (not consistently) get the error:
System.Data.EntityException occurred
Message=An error occurred while closing the provider connection. See the inner exception for details.
InnerException: System.Transactions.TransactionException
Message=The operation is not valid for the state of the transaction.
Source=System.Transactions
StackTrace:
at System.Transactions.TransactionStatePSPEOperation.get_Status(InternalTransaction tx)
at System.Transactions.TransactionInformation.get_Status()
at System.Data.ProviderBase.DbConnectionInternal.CloseConnection(DbConnection owningObject, DbConnectionFactory connectionFactory)
at System.Data.SqlClient.SqlInternalConnection.CloseConnection(DbConnection owningObject, DbConnectionFactory connectionFactory)
at System.Data.SqlClient.SqlConnection.Close()
at System.Data.EntityClient.EntityConnection.StoreCloseHelper()
InnerException:
I only know they are running at the same time because when I run my unit tests in debug mode and this exception pops up, if I look at the different threads that are running, I always see at least one other thread halted at an ObjectContext operation.
Also, after doing some reading, I tried adding multipleactiveresultsets=False
to my connection string and that made no difference.
Is this a bug in Entity Framework?
The problem is described here:
http://www.b10g.dk/2007/09/07/dependenttransaction-and-multithreading/
It's easy enough to lock the SaveChanges and Refresh calls, but in order to make sure locks occur during query execution I had to make a dummy query provider that locks when executing queries. I really shouldn't have had to do this. Entity Framework should have been robust enough to handle this out of the box...especially considering you're not meant to handle your own connection creation.
Here is the code for the query provider wrapper. The IQueryables themselves and the base QueryProvider class are simple reusable implementations based off here http://blogs.msdn.com/b/mattwar/archive/2007/07/30/linq-building-an-iqueryable-provider-part-i.aspx
/// <summary>
/// A wrapper for queries executed by EF.
/// </summary>
internal class EntityFrameworkQueryProvider : QueryProvider
{
protected override object Execute(Expression expression)
{
try
{
// this is required due to a bug in how EF multi-threads when Transactions are used.
if (Transaction.Current != null) Monitor.Enter(EntityFrameworkExtensions.SyncRoot);
// enumerate is a simple extension method that forces enumeration of the IQueryable, thus making it actually get executed during the lock
return Expression.Lambda(expression).Compile().DynamicInvoke().Enumerate();
}
finally
{
if (Transaction.Current != null) Monitor.Exit(EntityFrameworkRepositoryProvider.SyncRoot);
}
}
}
If you are passing the same instance of the dependent clone to multiple threads, and then disposing of them on each thread, that could lead to behavior like this (e.g. committing a finished transaction and such). AFAIK, you need a separate dependent clone per thread.
Another possibility is the "parent" transaction is getting completed or disposed before the threads finish with their transaction. Make sure your async work is done before you leave the main TranscationScope (although this can be set to block on unfinished child transactions).
Sorry, don't have more than that with what you've described.
Good luck, Michael
精彩评论