My software using EF Core in combination with a SQLite database within an ASP.NET Core Web API using dependency injection, has a memory leak.
I have a background job using Quartz which gets called every 9 seconds.
My context looks like this:
public class TeslaSolarChargerContext : DbContext, ITeslaSolarChargerContext
{
    public DbSet<ChargePrice> ChargePrices { get; set; } = null!;
    public DbSet<HandledCharge> HandledCharges { get; set; } = null!;
    public DbSet<PowerDistribution> PowerDistributions { get; set; } = null!;
    public string DbPath { get; }
    public void RejectChanges()
    {
        foreach (var entry in ChangeTracker.Entries())
        {
            switch (entry.State)
            {
                case EntityState.Modified:
                case EntityState.Deleted:
                    entry.State = EntityState.Modified; //Revert changes made to deleted entity.
                    entry.State = EntityState.Unchanged;
                    break;
                case EntityState.Added:
                    entry.State = EntityState.Detached;
                    break;
            }
        }
    }
    public TeslaSolarChargerContext()
    {
    }
    public TeslaSolarChargerContext(DbContextOptions<TeslaSolarChargerContext> options)
        : base(options)
    {
    }
}
With the interface
public interface ITeslaSolarChargerContext
{
    DbSet<ChargePrice> ChargePrices { get; set; }
    DbSet<HandledCharge> HandledCharges { get; set; }
    DbSet<PowerDistribution> PowerDistributions { get; set; }
    ChangeTracker ChangeTracker { get; }
    Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken());
    DatabaseFacade Database { get; }
    void RejectChanges();
}
In my Program.cs I add the context and Quartz job to the dependency injection like that:
builder.Services.AddDbContext<ITeslaSolarChargerContext, TeslaSolarChargerContext>((provider, options) =>
    {
        options.UseSqlite(provider.GetRequiredService<IDbConnectionStringHelper>().GetTeslaSolarChargerDbPath());
        options.EnableSensitiveDataLogging();
        options.EnableDetailedErrors();
    }, ServiceLifetime.Transient, ServiceLifetime.Transient)
    .AddTransient<IChargingCostService, ChargingCostService>();
builder.Services
    .AddSingleton<JobManager>()
    .AddTransient<PowerDistributionAddJob>()
    .AddTransient<IJobFactory, JobFactory>()
    .AddTransient<ISchedulerFactory, StdSchedulerFactory>();
I am using my own JobManager because job intervalls can be configured in various ways so I inject a wrapper into my JobManager and it is Singleton as I need to stop my jobs at any time as the job intervall can be updated during runtime, so I need to stop and start the jobs:
public class JobManager
{
    private readonly ILogger<JobManager> _logger;
    private readonly IJobFactory _jobFactory;
    private readonly ISchedulerFactory _schedulerFactory;
    private readonly IConfigurationWrapper _configurationWrapper;
    private IScheduler _scheduler;
    
    public JobManager(ILogger<JobManager> logger, IJobFactory jobFactory, ISchedulerFactory schedulerFactory, IConfigurationWrapper configurationWrapper)
    {
        _logger = logger;
        _jobFactory = jobFactory;
        _schedulerFactory = schedulerFactory;
        _configurationWrapper = configurationWrapper;
    }
    public async Task StartJobs()
    {
        _logger.LogTrace("{Method}()", nameof(StartJobs));
        _scheduler = _schedulerFactory.GetScheduler().GetAwaiter().GetResult();
        _scheduler.JobFactory = _jobFactory;
        var powerDistributionAddJob = JobBuilder.Create<PowerDistributionAddJob>().Build();
        var powerDistributionAddTrigger = TriggerBuilder.Create()
            .WithSchedule(SimpleScheduleBuilder.RepeatSecondlyForever((int)_configurationWrapper.JobIntervall().TotalSeconds)).Build();
        var triggersAndJobs = new Dictionary<IJobDetail, IReadOnlyCollection<ITrigger>>
        {
            {powerDistributionAddJob, new HashSet<ITrigger> {powerDistributionAddTrigger}},
        };
        await _scheduler.ScheduleJobs(triggersAndJobs, false).ConfigureAwait(false);
        await _scheduler.Start().ConfigureAwait(false);
    }
    public async Task StopJobs()
    {
        await _scheduler.Shutdown(true).ConfigureAwait(false);
    }
}
The job looks like this:
[DisallowConcurrentExecution]
public class PowerDistributionAddJob : IJob
{
    private readonly ILogger<ChargeTimeUpdateJob> _logger;
    private readonly IChargingCostService _service;
    public PowerDistributionAddJob(ILogger<ChargeTimeUpdateJob> logger, IChargingCostService service)
    {
        _logger = logger;
        _service = service;
    }
    public async Task Execute(IJobExecutionContext context)
    {
        _logger.LogTrace("{method}({context})", nameof(Execute), context);
        await _service.AddPowerDistributionForAllChargingCars().ConfigureAwait(false);
    }
}
The context is injected to the service like that:
public ChargingCostService(ILogger<ChargingCostService> logger,
        ITeslaSolarChargerContext teslaSolarChargerContext)
{
    _logger = logger;
    _teslaSolarChargerContext = teslaSolarChargerContext;
}
I use the context within a service and just call this method:
var chargePrice = await _teslaSolarChargerContext.ChargePrices
                                                 .FirstOrDefaultAsync().ConfigureAwait(false);
Calling this results in the app blowing using 1GB RAM after a week.
After analyzing a memory dump I found that after about 8 hours I have over two thousand instances of TeslaSolarChargerContext.
 
    