I'm troubleshooting a laundry list of exceptions that have recently begun to occur- We have a "single-use" service which we wrapped a bulk service around. The bulk service sets up all the variables required for each iteration, etc... The other variables are non-Entity/non-EF related. Roughly speaking:
foreach (var item in ItemsToRun)
{
using (var context = new MyDbContext(connectionString))
{
var service = new Service(context, other, variables);
var runResult = service.Run(item);
results.add(runResult);
}
}
Each run gets a "fresh context". The individual runs of this service are unit tested end-to-end, so this really should have been a no-brainer. HOWEVER, after the 4th or sometimes 8 (but never more than 10) iterations, I'm getting a mixed bag of exceptions during the first SaveChanges
call within the run.
Source: EntityFramework
Message: "Value cannot be null\r\n Parameter name: connection"
StackTrace: " at System.Data.Entity.Utilities.CheckNotNull[T](T value, String parameterName)"
Source: EntityFramework
Message: "Unable to cast object of type 'MyAssembly.Data.MyDbContext' to type 'System.Data.Entity.Core.Objects.ObjectContext'
StackTrace: " at System.Data.Entity.Infrastructure.TransactionHandler.get_ObjectContext()\r\n at System.Data.Entity.Infrastructure.TransactionHandler.Initialize(ObjectContext context)"
Source: System.Transactions
Message: "Unable to cast object of type 'System.Threading.Thread' to type 'System.Transactions.BucketSet'."
StackTrace: " at System.Transactions.TransactionTable.ThreadTimer(Object state)"
and a few others mentioning Transactions, ObjectContext, and SafeIUnknown.
It's worth noting that I had to use dotPeek to get a symbol file for EF to get the internal location of the Unable to cast to ObjectContext exception.
Things I have tried:
- Connection pooling (at the connection string level was set to true by default, I've set it to false for this bulk operation) This seemed to have the biggest impact, as it wouldn't make it more than 2 runs with pooling enabled.
- A delay 1 second after each run. No change in behavior.
- A
GC.Collect()
after every 3rd run. Other than flushing out some bugs in an incorrectly implemented finalizer, no change in behavior. - A call to
SqlConnection.ClearAllPools()
after every run/3rd run. Immediately causes the next run to fail with one of the above exceptions.
Other notables:
This is being run within a MVVMLight WPF application.
Net Framework: 4.5
EF Version: EF 6.4.4
MS Sql Server.
MultipleActiveResultSets (MARS) is enabled.
The service is synchronous, and I can't find any location where an open read is clashing with a save changes call (such as iterating over an un-enumerated query result, whilst calling save changes within the loop). However, I would suspect that any outright terrible behavior like that would have been caught in unit testing, or result in all runs failing.
The SaveChanges()
method is unaltered. We're not modifying EF's default transaction handling. Which from some quick googling, appears that the default behavior is to use a scoped transaction per SaveChanges()
call.
I have a feeling the resources from the previous iteration are not being released correctly, but I'm unsure how to "force" that to happen beyond the contexts using
statement and the things I've already tried.
Edit:
I have also tried overriding the SaveChanges()
method in MyDbContext, conditionally wrapping the base.SaveChanges()
as follows:
public bool UseDbContextTransactions { get; set; }
public override int SaveChanges()
{
var result = 0;
if (UseDbContextTransactions)
{
try
{
using (var dbContextTransaction = this.Database.BeginTransaction())
{
result = base.SaveChanges();
dbContextTransaction.Commit();
}
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine($"Commit Exception: {e.message}");
dbContextTransaction.RollBack();
}
}
else
{
result = base.SaveChanges();
}
return result;
}
From the try catch block, I now get the following exception:
Source: System.Transactions
Message: "Unable to cast object type 'System.Data.SqlClient.SqlTransaction' to type 'System.Transactions.SafeIUnknown'.
StackTrace: " at System.Transactions.Transaction.JitSaveGetContextTransaction(ContextData contextData)".