Quantcast
Channel: Second Life of a Hungarian SharePoint Geek
Viewing all articles
Browse latest Browse all 206

How to detect if your DbConext is already disposed

$
0
0

A little background (or let’s say context) to the story. Recently I had to create a tool (of course it was SharePoint-related) that needs to import a quite large number of entries into a SQL database for further analysis. I chose Entity Framework (DB-first) as a wrapper to our database entities. Having the knowledge that I already have now, I should admit, it was not the perfect choice, but rather a great opportunity to learn from our failures. In the development environment, where we have a smaller amount of data, the tool was quite quick, so I anticipated, it would be quick enough in the test and productive environment either. Just to have an idea about the order of magnitude, in these environments we have around 0,5 Mio. entries to export. It is a large number of entries, but I don’t think it is extreme large. After the first run in the test environment I knew, I was completely wrong with my assumption.

After letting the tool run on the weekend, and even a few day after, we stopped it, because it simply have not finished, and we haven’t seen the end of the tunnel. I analyzed the log data and found, that at the beginning the tool saved a batch of 100 entries in approximately 50 milliseconds. But later it was slower and slower, and after having saved already about 15.000 entries, the saving of the same batch of 100 entries took more than 7 seconds. What was the problem, and how have we solved it? You can read more about the issue here and here, and about the solution we applied here.

As part of the solution, we disposed and re-created our data context after saving every batch of items. However, this solution has introduced a new problem.

In the original version we have called the SaveChanges method at the end of our processing in an outer code block to ensure saving any remaining items that do not completed an entire batch. But in the new version we got this exception at this point:

InvalidOperationException: The operation cannot be completed because the DbContext has been disposed.
at System.Data.Entity.Internal.LazyInternalContext.InitializeContext()
at System.Data.Entity.Internal.LazyInternalContext.get_ObjectContext()
at System.Data.Entity.Internal.InternalContext.DetectChanges(Boolean force)
at System.Data.Entity.Internal.InternalContext.GetStateEntries(Func`2 predicate)
at System.Data.Entity.Infrastructure.DbChangeTracker.Entries()
at System.Data.Entity.DbContext.GetValidationErrors()
at System.Data.Entity.Internal.InternalContext.SaveChanges()

Note: you have this error on a disposed DbContext object only if there were something to save. If no object has been changed, no exception is thrown on calling SaveChanges on a disposed DbContext object.

This exception was due to disposing of the data context we created in a using block. We passed the data context to a method, that disposed (and re-created it) as part of the pattern suggested for performance tuning for importing of large number of items via the Entity Framework. See the sample code with Exporter class and it static DoSomething method further down. The data context was disposed in the DoSomething method. In the original version we tried to invoke dbContext.SaveChanges after returning from the DoSomething method, although the data context was already disposed in the DoSomething method, and it resulted the exception above.

Although some disposable object types might provide a kind of check if the object has been already disposed (see the IsDisposed property of the System.Windows.Forms.Control class, for example), it is definitely not part of the IDisposable interface. If the object belongs to your solution, you can implement a similar property yourself. If you can’t change the source code of the class (it is a class in the .NET libraries or any 3rd party class), you can derive a subclass from it, and extend it by the functionality as shown in this answer. This method works of course only as long as the base class is not marked as sealed.

In the case of DbContext I found a private field of type System.Data.Entity.Internal.InternalContext called _internalContext. InternalContext is an abstract internal class, you can find references for it and for its derived class LazyInternalContext in the error stack trace above as well. The InternalContext class has a public property called IsDisposed. The LazyInternalContext class invokes in its InitializeContext method the CheckContextNotDisposed method of the base class, that throws the InvalidOperationException above if the context has been already disposed.

I created the extension method below using Reflection to read the value of the IsDisposed property of the _internalContext field.

  1. public static bool IsDisposed(this DbContext context)
  2. {
  3.     var result = true;
  4.  
  5.     var typeDbContext = typeof(DbContext);
  6.     var typeInternalContext = typeDbContext.Assembly.GetType("System.Data.Entity.Internal.InternalContext");
  7.  
  8.     var fi_InternalContext = typeDbContext.GetField("_internalContext", BindingFlags.NonPublic | BindingFlags.Instance);
  9.     var pi_IsDisposed = typeInternalContext.GetProperty("IsDisposed");
  10.  
  11.     var ic = fi_InternalContext.GetValue(context);
  12.  
  13.     if (ic != null)
  14.     {
  15.         result = (bool)pi_IsDisposed.GetValue(ic);
  16.     }
  17.  
  18.     return result;
  19. }

You can test the functionality in a code block like this one:

  1. using (dbContext = new YourDbContext())
  2. {
  3.     Console.WriteLine("Disposed: {0}", dbContext.IsDisposed());
  4.     Exporter.DoSomething(dbContext);
  5.     Console.WriteLine("Disposed: {0}", dbContext.IsDisposed());
  6. }
  7. Console.WriteLine("Disposed: {0}", dbContext.IsDisposed());

The Exporter class and its static DoSomething method in the code above are just some arbitrary functionality you would like to perform.

Having the IsDisposed method defined, you can check if the context is disposed before calling SaveChanges (or any other method that are dangerous to be invoked on a disposed context):

  1. if ((dbContext != null) && (!dbContext.IsDisposed()))
  2. {
  3.     dbContext.SaveChanges();
  4. }

Note: The code in this post was tested with Entity Framework 6.1.3. Other versions might behave in another way, so there is no guarantee that the same code works with those versions too.


Viewing all articles
Browse latest Browse all 206

Trending Articles