Multiple Blazor components accessing database via single DbContext at same time

What is the most straightforward way to handle multiple Blazor components trying to access the database via a DbContext in EF Core at once? I'm creating an indeterminate number of components on a page at once that all make a call within OnInitializedAsync() to a scoped service in order to pull a record from the database. A problem seems to arise from the fact that they are all using the same DbContext, which is not thread safe? Can someone point me in the right direction? Hopefully that's not too broad a question, still new to this area .
13 Replies
Maverick (Shaun)
Maverick (Shaun)OP10mo ago
I'm seeing some recommendations of using a DbContextFactory - would that be the best solution?
Jimmacle
Jimmacle10mo ago
yes, you can't use a dbcontext concurrently and since blazor's scoped lifetime is more like a singleton lifetime the factory is the way to go in my current project i just don't access the db from components directly and use mediatr and a helper service to put each logical "request" in a new service scope
Maverick (Shaun)
Maverick (Shaun)OP10mo ago
I've had a think about it, and I think this is the only instance in my application where this problem will occur. Since I'm only needing access to a couple of strings (Title and Description of the relevant model), I've decided just to get the object in the parent component and then pass the Title and Description to the child components directly, avoiding concurrent calls to dbcontext. I think this maybe breaks some "rules" regarding security, but since these strings don't contain any sensitive information, I think it's probably okay. Would you agree? Avoids adding a layer of complexity to the project and gets the same job done
Jimmacle
Jimmacle10mo ago
the other problem is the fact you're holding onto the dbcontext longer than intended, you aren't supposed to keep reusing the same one because the change tracker will get bloated/stale it shouldn't really live longer than one "thing" being done
Maverick (Shaun)
Maverick (Shaun)OP10mo ago
How much of a problem is that likely to be? This is for a university project and won't actually be running for too long at once. I'm assuming that'd be more of an issue for an application that's running for long periods of time? Just trying to balance between best practices/ease of development/simplicity due to the time constraints. Cutting a few corners where acceptable
Jimmacle
Jimmacle10mo ago
IMO the value of doing it right the first time and not having to rewrite it later if you run into an issue is better than the convenience of ignoring it now using the factory is essentially just an extra line of code
Maverick (Shaun)
Maverick (Shaun)OP10mo ago
If it's pretty straightforward, maybe I'll look further into it then - my main concern was that I'd have to rewrite a chunk of logic
Jimmacle
Jimmacle10mo ago
no, you basically just inject IDbContextFactory<YourDbContext> and call await using var db = await factory.CreateDbContextAsync() to get a dbcontext when you need one
Maverick (Shaun)
Maverick (Shaun)OP10mo ago
Wow, that' s a lot simpler than I was expecting. I was having a skim read of some StackOverflow threads covering this topic and there seemed to be a lot of try-catch stuff bloating the code... not sure what they were doing to end up with that, my first thought was "that isn't very DRY". Seems it's definitely worthwhile then, I'd of course like to use best practices wherever I can as long as it doesn't add a ton of complexity
Jimmacle
Jimmacle10mo ago
you wouldn't need any more error handling than using the directly injected dbcontext
Maverick (Shaun)
Maverick (Shaun)OP10mo ago
I assume the change to my services (e.g.):
public class DrawService(ApplicationDbContext context, IHttpContextAccessor httpContextAccessor) : IDrawService
{
/* Create Operations */
public async Task AddNewDraw(DrawModel drawModel)
{
context.Draws.Add(drawModel);
await context.SaveChangesAsync();
}
........
public class DrawService(ApplicationDbContext context, IHttpContextAccessor httpContextAccessor) : IDrawService
{
/* Create Operations */
public async Task AddNewDraw(DrawModel drawModel)
{
context.Draws.Add(drawModel);
await context.SaveChangesAsync();
}
........
Would be as simple as just passing the context to the CRUD methods rather than having it in the service constructor?
public class DrawService(IHttpContextAccessor httpContextAccessor) : IDrawService
{
/* Create Operations */
public async Task AddNewDraw(ApplicationDbContext context, DrawModel drawModel)
{
context.Draws.Add(drawModel);
await context.SaveChangesAsync();
}
public class DrawService(IHttpContextAccessor httpContextAccessor) : IDrawService
{
/* Create Operations */
public async Task AddNewDraw(ApplicationDbContext context, DrawModel drawModel)
{
context.Draws.Add(drawModel);
await context.SaveChangesAsync();
}
Or would it be better to actually create a new context inside of each CRUD method, rather than passing it in?
Jimmacle
Jimmacle10mo ago
inject the factory, use it to create a dbcontext for each call
public class DrawService(IDbContextFactory<ApplicationDbContext> factory, IHttpContextAccessor httpContextAccessor) : IDrawService
{
/* Create Operations */
public async Task AddNewDraw(DrawModel drawModel)
{
await using var context = await factory.CreateDbContextAsync();
context.Draws.Add(drawModel);
await context.SaveChangesAsync();
}
public class DrawService(IDbContextFactory<ApplicationDbContext> factory, IHttpContextAccessor httpContextAccessor) : IDrawService
{
/* Create Operations */
public async Task AddNewDraw(DrawModel drawModel)
{
await using var context = await factory.CreateDbContextAsync();
context.Draws.Add(drawModel);
await context.SaveChangesAsync();
}
Maverick (Shaun)
Maverick (Shaun)OP10mo ago
Awesome, I'll have a go at it - it sounds simple enough and will give me a bit more flexibility at the very least. Thanks for the help Made the change, tested that existing functionality was behaving as before and checked if the code that was causing issues with concurrency is now working - everything seems fine, very simple change and can't see any problems yet. Awesome stuff :goodthinking:
Want results from more Discord servers?
Add your server