C
C#2mo ago
Tom

✅ Disposing dbContext with dependency injection

Hi! I am developing an Avalonia UI app. I am displaying there some data, which is stored in the sqlite database. The data - rows in that matter - can be deleted or added, but once they are there - they are not modified (at least for now ;)). I have a factory which gets data from db and then creates a view model passing there the data (and few other things). The factory itself I've registered in service container as a singleton and dbContext as a transient. dbContext is injected into the factory. As I am not tracking entities changes and not adding or deleting data in that place, I've configured the context with optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);. I have not encountered any issues so far, however I am concerned about proper dbContext disposing. If a factory is singleton, even when dbContext is transient, once it's injected is still there, right? The view model can be created few times during app lifetime, rows can be added/removed in the meantime. Can I leave it as it is or should I do smth about it? Eg. instead of injecting dbContext itself I can create and inject another factory which will instantiate dbContext via constructor each time needed, or maybe you can suggest other solutions? Any help appreciated 😉
16 Replies
becquerel
becquerel2mo ago
a transient injected into a singleton will indeed be "captured" and hoisted up to a singleton lifecycle dbcontexts are intended to be short-lived, on the scale of a single method call or a http request so I would create and dispose your client on a short enough timeframe to get the data you need each time you need it for that, you can either create your dbcontexts manually with new(), or use the dedicated dbcontextfactory (though that may be asp.net core only, I forget)
Klarth
Klarth2mo ago
It's not. I use that approach in an Avalonia app. eg.
private readonly IDbContextFactory<SomeContext> _dbFactory;

public CompletionService(IDbContextFactory<SomeContext> dbFactory)
{
_dbFactory = dbFactory;
}

public async Task AddCompletion(string familyName, string objectiveName, Instant completionTime)
{
using var context = _dbFactory.CreateDbContext();

var family = await GetOrCreateFamily(context, familyName).ConfigureAwait(false);
var objective = await GetOrCreateObjective(context, objectiveName, false, family).ConfigureAwait(false);
objective.LastCompletion = completionTime;

var completion = new Completion()
{
Family = family,
Objective = objective,
CompletionTime = completionTime
};

context.Completions.Add(completion);
await context.SaveChangesAsync().ConfigureAwait(false);
}
private readonly IDbContextFactory<SomeContext> _dbFactory;

public CompletionService(IDbContextFactory<SomeContext> dbFactory)
{
_dbFactory = dbFactory;
}

public async Task AddCompletion(string familyName, string objectiveName, Instant completionTime)
{
using var context = _dbFactory.CreateDbContext();

var family = await GetOrCreateFamily(context, familyName).ConfigureAwait(false);
var objective = await GetOrCreateObjective(context, objectiveName, false, family).ConfigureAwait(false);
objective.LastCompletion = completionTime;

var completion = new Completion()
{
Family = family,
Objective = objective,
CompletionTime = completionTime
};

context.Completions.Add(completion);
await context.SaveChangesAsync().ConfigureAwait(false);
}
becquerel
becquerel2mo ago
that seems totally fine to me though I think you can use "await using" since it's an async method
Klarth
Klarth2mo ago
And for the DI configuration side (though you don't need NodaTime):
public void ConfigureDbContext(IServiceCollection services)
{
services.AddDbContextFactory<SomeContext>(
options => options.UseSqlite(_connectionString, x => x.UseNodaTime())
);
}
public void ConfigureDbContext(IServiceCollection services)
{
services.AddDbContextFactory<SomeContext>(
options => options.UseSqlite(_connectionString, x => x.UseNodaTime())
);
}
I'm pretty sure await using wasn't a feature when this was made.
becquerel
becquerel2mo ago
fair enough 🙂
Klarth
Klarth2mo ago
Oh, maybe not. await using is a lot older than I thought. C# 8.
becquerel
becquerel2mo ago
i rarely remember to use it when my ide isn't configured to lint about it since it's rarely intuitive what classes have implemented iasyncdisposable over plain idisposable in my experience
Tom
TomOP2mo ago
@becquerel @Klarth thank you for your input! I'll use the factory then. Btw. by await using you mean invoking CreateDbContextAsync? @Klarth in you example, you've used .ConfigureAsync(false) because there was no need to keep that in UI thread, or did you have any other reasons?
becquerel
becquerel2mo ago
yeah, dbcontexts implement iasyncdisposable so you can await their using statement
Klarth
Klarth2mo ago
Correct, though I probably should have omitted it for the SaveChangesAsync at the end.
Tom
TomOP2mo ago
@Klarth why would you omitt at the end? Is it redundant at that point? I don't have much experience in async/await programming that's why I ask 😉
Klarth
Klarth2mo ago
Nevermind on that. When you await the call on the GUI side (without .ConfigureAwait(false)), the continuation will run on the UI thread.
Tom
TomOP2mo ago
it's much outside the topic but wouldn't one ConfigureAwait(false) in a row be enough to separate from UI thread?
Klarth
Klarth2mo ago
So say AddCompletions is a library method (it effectively is), then you want .ConfigureAwait(false) on each call so it can resume on any available thread. Granted, ASP.NET Core doesn't have a SynchronizationContext, so that happens no matter what. Well, unless you create your own SynchronizationContext there for some reason. It's still best practice to .ConfigureAwait(false) each call in a library.
Tom
TomOP2mo ago
Thank you Klarth. I'll close the post now, not to confuse any further readers, maybe we meet again in new threads ;D

Did you find this page helpful?