- Home
- .NET tutorials
- Using Entity Framework in a Blazor Server application
Using Entity Framework in a Blazor Server application
Published: Thursday 9 April 2020
So, you are overriding the async methods in Blazor. You are overriding methods such as OnInitializedAsync or OnParametersSetAsync. And you are using Entity Framework to retrieve data from the database.
But there's a problem. You come across this problem:
InvalidOperationException: A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
So how do you go about fixing this problem? Well you are about to find out.
The Reason
The first thing to investigate is why are you getting this problem. It's probably because you are calling Entity Framework in an async method on your razor component. If your razor application includes multiple razor components, such as a header or a navigation, this is where the problem lies.
C# coding challenges
Just say you are overriding OnInitializedAsync and using Entity Framework within this method. And you are using it on your header and navigation components. When OnInitializedAsync runs on header, the navigation component is going to run OnInitializedAsync method before the header component has finished OnInitializedAsync. Of course it is! It's running asynchronously.
However, this presents a problem for Entity Framework. By default, each instance of Entity Framework is only designed to run one query at one time. But, if your Blazor application uses Entity Framework in multiple async razor components, there's a high chance that more than one query will be ran at one time.
Dependency Injection with Entity Framework
It's worth reminding us some of the scopes we can have when setting up Dependency Injection (DI) in .NET Core:
- Singleton - Service stays initialised for the lifetime of the application once requested.
- Scoped - The service is initialised every time a new Dependency Injection scope is created. A new DI scope would typically happen for every thread ran on a web application
- Transient - The service is initialised every time it is injected as a parameter or explicitly requested
By knowing these scopes, we can pin point the problem. By default, when you add Entity Framework (EF) to the service collection, it's registered as a "Scoped" type.
// Startup.cs
services.AddDbContext<MyDbContext>();
And this is where the problem lies. EF is scoped, meaning a DI scope has to be created to initialise a new instance of EF. With Blazor, this is with every thread ran, like any other web application. So, you can have multiple async razor components in the same thread running the same EF instance at the same time.
How to Fix
So how do you go about fixing this problem? Well, there are two ways of doing it.
Changing EF Core's Scope
The simple fix is to change EF Core's scope when adding it to the service collection. So how do you do that?
Well, you use the AddDbContext method to your service collection. This registers EF. In addition, you can extend AddDbContext to include extra parameters. One of those extra parameters is the service lifetime, which you can set to "ServiceLifetime.Transient".
// Startup.cs
services.AddDbContext<BPerformanceDbContext>(options => { }, ServiceLifetime.Transient);
Just a note, you will need to include the options parameter as an empty function, even if you are not overriding them.
Creating a New Service Scope for each Razor Component
The other option is to create a new service scope for each Razor Component. The first thing to do is to inject the IServiceScope into your Razor component. As a result of doing this, you can create a new service scope when you need to.
The example below shows an example of a navigation menu razor component. The IServiceScopeFactory has been injected into the component. We are overriding the OnInitializedAsync method. Within that, using the IServiceScopeFactory injection, we can create a new scope, calling the "CreateScope" method. Any service, such as our CategoryService instance comes from our new localised scope.
// NavMenu.razor
@inject IServiceScopeFactory serviceScopeFactory
@using Microsoft.Extensions.DependencyInjection
@using BPerformance.Services
<div class="top-row pl-4 navbar navbar-dark">
<a class="navbar-brand" href="">My Blog</a>
<button class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
@if (Categories != null)
{
foreach (var category in Categories)
{
<li class="nav-item px-3">
<a class="nav-link" href="@("/category/" + category.Slug)">
<span class="oi oi-cat-@category.Id" aria-hidden="true"></span> @category.Name
</a>
</li>
}
}
</ul>
</div>
@code {
private bool collapseNavMenu = true;
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
public IEnumerable<BPerformance.Data.Category> Categories { get; set; }
protected override async Task OnInitializedAsync()
{
using (var scope = serviceScopeFactory.CreateScope())
{
Categories = await scope.ServiceProvider.GetService<ICategoryService>().GetWithArticlesAsync();
await InvokeAsync(() =>
{
StateHasChanged();
});
}
}
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}
By creating a new scope, it creates a new instance of EF everytime the navigation razor component is initialised.
Demonstration
You can watch the following video to see the error being recreated, and both fixes being applied to resolve the issue.
Conclusion
Of course, another way to avoid this is to use the synchronise methods in Blazor. Overriding methods like OnInitialized and OnParametersSet and providing synchronous Entity Framework calls within them will do the trick.
But, one thing to note is that Blazor seems to have problems converting an asynchronous method into a synchronise one. Why that happens is up for investigation. But expect to have issues if you are using .ConfigureAwait or .Result.
Personally, I think that creating a new scope in your Blazor component is a better solution then changing the EF's scope to Transient. You may not want to create a new instance of EF in all areas of your Blazor component and it gives you a bit more control.