Monday, December 17, 2018

Accessing Entity Framework context on the background on .NET Core

You got the "Cannot access a disposed object using Entity Framework Core". What should you do?
So, you tried to access Entity Framework Core's db context on a background thread in .NET Core and got this error:
Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.

For context, this exception happens because EF Core disposes the connection just after the request to the controller is closed. Indeed that's the right approach and is the default behaviour as we don't want those resources hanging open for much longer. But before going forward, I'd like you to know that most times you don't need or want to access EF Core on a background context. A few good explanations are described here. And, if you need an introduction, I'd also recommend reading Scott Hanselman's introduction on the topic.

However, such an approach sometimes may be necessary. For example, I came across that issue writing a MVP, a proof of concept where a Vue.JS chat room using EF Core communicated with a SignalR Core backend running on Linux. In my opinion MVPs and proofs of concept are the only acceptable use cases for this solution. As always, the default approach should be accessing the service via the injected dependency.

Enough taklink. Let's take a look at how to address the problem.

IServiceScopeFactory

The key to doing this is using IServiceScopeFactory. Available on the Microsoft.Extensions.DependencyInjection Nuget package, IServiceScopeFactory provides us a singleton from which we can resolve services trough DI the same way the .NET Core framework does for us.

Microsoft describes it as:
A factory for creating instances of IServiceScope, which is used to create services within a scope. Create an IServiceScope which contains an IServiceProvider used to resolve dependencies from a newly created scope.

The Implementation

The implementation is divided in 3 (three) steps:
  1. Inject the IServiceScopeFactory singleton on your controller
  2. Pass the instance of IServiceScopeFactory to your background task or thread
  3. Resolve the service from the background task

Step 1 - Inject IServiceScopeFactory in your controller

First, you need to inject IServiceScopeFactory in your controller.

Step 2 -  Pass it to your background thread

Then, you have some code that supposedly invokes the bg thread/task. For example:

Step 3 -  Resolve the service from the background task

And finally, when your background thread is run, access the scope and have the framework initialize the EF context for you with:
And because it's a singleton, IServiceScopeFactory won't throw an exception when you try to access it.
at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed()
at Microsoft.EntityFrameworkCore.DbContext.Add[TEntity](TEntity entity)
at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.Add(TEntity entity)

Conclusion

While you shouldn't use this as a pattern to process background tasks, there are situations where this is necessary. Since the there isn't much documentation around IServiceScopeFactory I thought it was good to document it.

Hope it helps!

See Also

Copying data between Azure databases in 3 easy steps
Simplifying Razor logic with C# Local Functions in ASP.NET Core
Why you should start using .NET Core
Package Management in .NET Core
Building and Running ASP.NET Core apps on Linux
A simple chat room in Vue.Js

For more .NET Core posts on this blog, please click here.

References

IServiceScopeFactory Interface
StackOverflow - DbContext for background tasks via Dependency Injection
StackOverflow - .NET Core IServiceScopeFactory.CreateScope() vs IServiceProvider.CreateScope() extension
GitHub - CreateScope from IServiceProvider

Photo by Caspar Camille Rubin on Unsplash