Is it thread safe to use DbContext in Blazor server referencing it as injected?

David Thielen 2,256 Reputation points
2023-05-10T09:05:46.9633333+00:00

I have a Blazor server app using Entity Framework Core. Like pretty much every example ever for this, I do the following:

[Inject]
private TrackingDbContext TrackingDbContext { get; set; } = default!;

And then when I need to do something on the database call:

TrackingDbContext.Users.Add(appUser);
_ = await TrackingDbContext.SaveChangesAsync();

The thing is, DbContext is not thread safe and is supposed to be created, used, disposed. But the documentation I found for AddDbContextFactory() says:

The lifetime with which to register the factory and options. The default is Singleton.

Do I need to be specifying a lifetime of transient? Or is this set to transient already for Blazor? There's little documentation I can find about this and with all examples I find working as above, I'm guessing this is ok. But better to ask and be sure.

Blazor
Blazor
A free and open-source web framework that enables developers to create web apps using C# and HTML being developed by Microsoft.
1,383 questions
0 comments No comments
{count} votes

Accepted answer
  1. Zhi Lv - MSFT 32,011 Reputation points Microsoft Vendor
    2023-05-11T01:42:56.4066667+00:00

    Hi @David Thielen

    Do I need to be specifying a lifetime of transient? Or is this set to transient already for Blazor?

    You can check this article: ASP.NET Core Blazor Server with Entity Framework Core (EF Core)

    EF Core provides the AddDbContext extension for ASP.NET Core apps that registers the context as a scoped service by default. In Blazor Server apps, scoped service registrations can be problematic because the instance is shared across components within the user's circuit. DbContext isn't thread safe and isn't designed for concurrent use. The existing lifetimes are inappropriate for these reasons:

    • Singleton shares state across all users of the app and leads to inappropriate concurrent use.
    • Scoped (the default) poses a similar issue between components for the same user.
    • Transient results in a new instance per request; but as components can be long-lived, this results in a longer-lived context than may be intended.

    So, in Blazor server application, you can use the DbContext factory to create the DbContext instance for each operation.

    for example, you can create the DbContext factory like this:

    builder.Services.AddDbContextFactory<ContactContext>(opt =>
        opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db"));
    

    then, inject the factory into your components, and create the DbContext instance, like this:

    @inject IDbContextFactory<ContactContext> DbFactory
    

    and

    private async Task DeleteContactAsync()
    {
        using var context = DbFactory.CreateDbContext();
        Filters.Loading = true;
    
        if (Wrapper is not null && context.Contacts is not null)
        {
            var contact = await context.Contacts
                .FirstAsync(c => c.Id == Wrapper.DeleteRequestId);
    
            if (contact is not null)
            {
                context.Contacts?.Remove(contact);
                await context.SaveChangesAsync();
            }
        }
    
        Filters.Loading = false;
    
        await ReloadAsync();
    }
    

    Then, the context is disposed when the component is disposed:

    
    public void Dispose()
    {
        Context?.Dispose();
    }
    

    Note: As you said, the DbContextFacotry's default lifetime is Singleton, not the DbContext. Notice that the DbContext instances created in this way are not managed by the application's service provider and therefore must be disposed by the application.


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    Best regards,

    Dillion

    1 person found this answer helpful.

1 additional answer

Sort by: Most helpful
  1. Bruce (SqlWork.com) 55,366 Reputation points
    2023-05-12T22:44:57.4833333+00:00

    You should use transient or the factory. It easy to have two blazor components with overlapping async calls to the dbcontext if it’s a singleton.

    You have the same issue with mvc or razor pages, but you have more control on the order of operations as it’s a template engine.

    0 comments No comments