Best practice for Blazor server side using MVVM and EF

Viewed 521

I've got a Blazor server app using MVVM with Entity Framework. I've got a scenario where the user can change the search criteria very quickly resulting in a second async call to EF before the first call is complete causing the following exception from EF: "There is already an open DataReader associated with this Connection which must be closed first". I can get around it by creating scope for the DbContext with IServiceScopeFactory but it got me thinking that if I need this work around I might not have things set up in the best way possible.

My view model is scoped which gets a reference to my model (also scoped) via constructor injection, and the model gets a reference to the EF context (transient) also via constructor injection. Given that I'm using Blazor server, scoped vs transient isn't really going matter in my scenario because there's only one request for the object instances. So my question is this: Am I appropriately preventing a second async call to EF by using IServiceScopeFactory or is there a better way?

Thanks in advance for your thoughts.

2 Answers

In a normal Blazor componenent, I'd just use a bool flag, something like the following. I'd rather eat my keyboard than go back to MVVM/MVC, so this is the best I can offer you:

@inject MyDataService DBS

<input @bind="SearchString"/><button @onclick="DoSearch">Search</button>

@code{
    string SearchString {get; set;} = "";
    bool IsSearching;
    async Task DoSearch (){
         if (!IsSearching){
             IsSearching = true;
             var results = await DBS.DoDataBaseSearch(SearchString);
             IsSearching = false;
         }
    }
}

Microsoft's best practice for dealing with this situation is to use DbContextFactory.

ASP.NET Core Blazor Server with Entity Framework Core

Pre-.NET 5 you have to implement IDbContextFactory but the code is in the documentation if you switch the version drop down in the link to 3.1. .NET 5 and later it's implemented for you.

Startup.cs
    services.AddDbContextFactory<MyContextClass>(options =>
    {
        options.UseSqlServer(Configuration["MyConnectionString"]);
    }, ServiceLifetime.Transient);

Data access class constructor:
    public MyDataAccessClass(IDbContextFactory<MyContextClass> contextFactory)
    {
        _contextFactory = contextFactory;
    }

Data access method:
    using var context = _contextFactory.CreateDbContext();

Then use the context like you normally would. This is a great solution for controlling the lifespan of the DbContext instance in Blazor server given the persistent connection.

Related