I am writing an application targeting the dotnet core framework 3.1. I use dependency injection to configure, among others, the database context. In my Program.cs I have the following code:
var host = new HostBuilder()
.ConfigureHostConfiguration(cfgHost =>
{
...
})
.ConfigureAppConfiguration((hostContext, configApp) =>
{
....
})
.ConfigureServices((hostContext, services) =>
{
...
services.AddDbContext<MyHomeContext>(options =>
{
options.UseNpgsql(hostContext.Configuration.GetConnectionString("DbContext"));
}, ServiceLifetime.Transient);
...
})
.ConfigureLogging((hostContext, logging) =>
{
...
})
.Build();
I pass host
to another class. In that other class I have, as part of a longer method, the following code:
using (var context = Host.Services.GetService(typeof(MyHomeContext)) as MyHomeContext)
{
StatusValues = context.Status.ToDictionary(kvp => kvp.Name, kvp => kvp.Id);
}
GC.Collect();
GC.Collect();
The GC.Collect
calls are there for testing / investigation purposes. In MyHomeContext
I, for testing purposes, implemented a destructor and an override of Dispose().
Dispose() gets called, but the destructor never gets called.
This results in a memory leak for every instance of MyHomeContext
I create.
What am I missing? What can I do the make sure the the instance of MyHomeContext
gets deleted when I no longer need it.
I moved to this implement because of a few reasons:
- I only need a database connection for a short amount of time.
- I insert a lot of data (not in the above reduced example / test code), resulting in the DbContext keeping a large cache. I expected disposing the object would free the memory, but now I only made it worse :(
When I replace Host.Services.GetService(typeof(MyHomeContext)) as MyHomeContext
by new MyHomeContext()
the destructor of MyHomeContext
is being called. Seems, to me, that something in the dependency injection framework is holding a reference to the object. Is this true? If so, how can I tell it to release it?
It's really hard to give a good answer to your question, because there are quite a few misconceptions that need to be addressed. Here are a few pointers for things to look out for:
GC.Collect()
will not be able to clean up thecontext
variable that is referenced by that same method.Dispose
method is called. This is done by calling GC.SuppressFinalize. Entity Framework'sDbContext
correctly implements the Dispose Pattern, which would also cause you not to see your finalizer being hit.context
was de-referenced and was eligible for garbage collection, the finalizer is unlikely to be called immediately after the calls toGC.Collect()
. You can, however, halt your application and wait until the finalizers are called by calling GC.WaitForPendingFinalizers(). CallingWaitForPendingFinalizers
is hardly ever something you want to do in production, but it can be useful for testing and benchmarking purposes.Apart from these CLR specific parts, here's some feedback on the DI part:
IServiceScope
. Services are cached within such scope and when the scope is disposed of, it will ensure its cached disposable services are disposed of as well, and it will ensure this is done in opposite order of creation.Host.Services
in your case) is a bad idea, because it causes scoped services (such as yourDbContext
) to be cached in the root container. This causes them to effectively become singletons. In other words, the sameDbContext
instance will be reused for the duration of the application, no matter how often you request it from theHost.Services
. This can lead to all sorts of hard to debug problems. The solution is, again, to instead create a scope and resolve from that scope. Example: