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 thecontextvariable that is referenced by that same method.Disposemethod is called. This is done by calling GC.SuppressFinalize. Entity Framework'sDbContextcorrectly implements the Dispose Pattern, which would also cause you not to see your finalizer being hit.contextwas 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(). CallingWaitForPendingFinalizersis 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.Servicesin 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 sameDbContextinstance 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: