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:
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 the context
variable that is referenced by that same method.Dispose
method is called. This is done by calling GC.SuppressFinalize. Entity Framework's DbContext
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 to GC.Collect()
. You can, however, halt your application and wait until the finalizers are called by calling GC.WaitForPendingFinalizers(). Calling WaitForPendingFinalizers
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 your DbContext
) to be cached in the root container. This causes them to effectively become singletons. In other words, the same DbContext
instance will be reused for the duration of the application, no matter how often you request it from the Host.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:
var factory = Host.Services.GetRequiredService<IServiceScopeFactory>();
using (var scope = factory.CreateScope())
{
var service = scope.ServiceProvider.GetRequiredService<ISomeService>();
service.DoYourMagic();
}