Data conflict
.NET .NET Core ASP.NET Core Best Practices Blazor C# Entity Framework Core Razor Threading Visual Studio

How to use DbContextFactory to Safely Create Data Contexts in ASP.NET Core Blazor Applications

Welcome to today’s post.

In today’s post I will discuss how to use data contexts safely within a .NET Core Blazor Server application.

Data contexts are an additional access layer available within an application that is a library which allows us to access the data within a backend database and return the data in the form of a structured class which can easily be stored in-memory and queried through use of C# libraries including the LINQ library.

Data contexts are provided by an ORM provider, such as the commonly used Entity Framework Core library.

In many usage scenarios, retrieving and manipulating data from a backend data store through use of data contexts is quite safe, however the are cases where they can result in unpredictable behavior or memory loss within an application. I will outline this in the first section.

What do we mean by Safe Data Contexts?

Recall that in a previous post when I showed how when using data contexts within a Blazor Server application, the instances of data contexts are by default created with a scoped lifetime. When data contexts are scoped to the lifetime of the running application session, multiple calls into the same Razor components or service classes that use the same data context are vulnerable to the possibility of thread conflicts.

When we have a scenario where a call within the application to a component uses a shared data context is then followed up with a new call to the same or different component. If the data context is accessed simultaneously within the components, we will end up getting an invalid exception error like the one shown below: 

Microsoft.EntityFrameworkCore.Query[10100]
      An exception occurred while iterating over the results of a query for context type 'BookLoanBlazorServerApp.Data.ApplicationDbContext'.
      System.InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently 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.
         at Microsoft.EntityFrameworkCore.Infrastructure.Internal.ConcurrencyDetector.EnterCriticalSection()
         at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()

The above overlapping issue with the DbContext is a familiar concurrency issue we will encounter if we adopt the tried and trusted method of dependency injection that we use for ASP.NET Core web applications. With stateless HTTP web requests that are used in ASP.NET Core web applications, we can get away with using a DbContext that has a scoped lifetime and is injected within each service class in the dependency container. The reason why this works is that each request method in an asynchronous API method uses await blocking calls to prevent access from concurrent threads (web requests) to database resources within the data context.

In a previous post I showed how to create a data aware .NET Core Blazor Server application that used Entity Framework Core to read, write and update data from a SQL Server database. In the same post I used the same techniques of injecting data contexts into service classes with scoped lifetimes. On many occasions when running the application, I would not encounter the above error, however when I attempted to access multiple Razor component pages that access the same data context within a short time duration, the above error will eventually occur.

The remedy for the above issue is to treat each database access query (select, insert, update and deletions) as a unit of work. What this means is that we create a new instance of the data context, use it, then dispose of it before the service class or component is disposed.

I will show how this is done in the next section.

Creation of Data Contexts Per Request

To be able to create data contexts for each operation we will need to use a factory that dispenses instances of DbContext. Each instance will have its own unique ContextId and will not interfere with data contexts within other components within the same application.

We will make use of the Entity Framework Core service collection extension method:

AddDbContextFactory<TContext>(IServiceCollection, Action<DbContextOptionsBuilder>, ServiceLifetime)	

Which registers a IDbContextFactory<TContext> within the IServiceCollection to create instances of given DbContext type.

We pass the application data context, ApplicationDbContext as the TContext and the connection string through the UseSqlServer() option to complete initialization of each data context. This is done as shown in the code excerpt below:

Program.cs:

using BookLoanBlazorServerApp.Data;
using BookLoanBlazorServerApp.Services;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
string connStr = "AppDbContext";

try
{
    builder.Services.AddDbContextFactory<ApplicationDbContext>(options =>
        options.UseSqlServer(builder.Configuration.GetConnectionString(connStr))
    );
}
catch
{
    Console.WriteLine("Error: Cannot connect to database.");
}

When running the above data context factory within an ASP.NET Core Blazor server application, if we wished to use AddDbContext<TContext>(..) within service class dependencies, then we would need to specify its lifetime to Singleton as the factory method AddDbContextFactory<TContext>(..) which has a Singleton lifetimewill generate DbContext instances with the same lifetime. We would not be able to generate instances of DbContext that are defaulted to Scope lifetimes that would not outlive the Singleton lifetime of the DbContextFactory.

If we were to just add AddDbContext() before AddDbContextFactory() in our startup then build and run, the following error would occur:

System.AggregateException: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.EntityFrameworkCore.IDbContextFactory`1[BookLoanBlazorServerApp.Data.ApplicationDbContext] 
Lifetime: Singleton ImplementationType: Microsoft.EntityFrameworkCore.Internal.DbContextFactory`1[BookLoanBlazorServerApp.Data.ApplicationDbContext]': 
Cannot consume scoped service 'Microsoft.EntityFrameworkCore.DbContextOptions`1[BookLoanBlazorServerApp.Data.ApplicationDbContext]' 
from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory`1[BookLoanBlazorServerApp.Data.ApplicationDbContext]'.) (Error while validating the service descriptor 'ServiceType: BookLoanBlazorServerApp.Services.IBookService Lifetime: Transient ImplementationType: BookLoanBlazorServerApp.Services.BookService': Cannot consume scoped service 'Microsoft.EntityFrameworkCore.DbContextOptions`1[BookLoanBlazorServerApp.Data.ApplicationDbContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory`1[BookLoanBlazorServerApp.Data.ApplicationDbContext]'.)

In the next section, I will show how to avoid the above error when using data contexts.

Injecting Data Context Factories within Service Classes

Given that we have setup the data context factory service within the application’s service dependency collection, we are now able to use the data context factory within any service class that requires instances of a data context to be generated. This will then ensure that the lifetime scope of each generated data context is a singleton that is used and disposed of within the scope of the block it is created.

When using the previous technique of injecting IDbContext into each service class, which are Scoped lifetime instances we ended up with the following error sometimes when within the application we have multiple requests attempting to access a service instance or Razor component that uses a data context that is scoped to the web application session:

fail: Microsoft.EntityFrameworkCore.Query[10100]
      An exception occurred while iterating over the results of a query for context type 'BookLoanBlazorServerApp.Data.ApplicationDbContext'.
      System.InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently 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.
         at Microsoft.EntityFrameworkCore.Infrastructure.Internal.ConcurrencyDetector.EnterCriticalSection()
         at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()

The diagram below shows us why this occurs:

During the requests that call the Razor component, they both at some point access the service class that has a DbContext injected into its class. During an overlapping access to any method that uses the data context, the error on the critical section to the Entity Framework Core occurs.

To remediation this situation we use a IDBContextFactory service to allow generation of DbContext instances that have unique contexts that are thread safe. We first declare the factory within the class properties:

readonly IDbContextFactory<ApplicationDbContext> _dbFactory;

As we can see from the screenshot below, the data context factory is injected into the class through the constructor much like we did for a IDbContext service:

After the data context factory instance is injected through the constructor, we can make us of factory to provision instances of the DbContext as shown:

using var context = _dbFactory.CreateDbContext();

Whenever a call is made to the CreateDbContext() method, a different DbContext instance with a unique ContextID is generated.

Below is the first call to create a data context:

And below is the second call to create a data context:

From here we can use the context and it will be disposed of within the scope of the method block.

Below is a modified version of the BookService service class that uses the IDBContextFactory as I discussed above:

BookService.cs:

namespace BookLoanBlazorServerApp.Services
{
    public class BookService : IBookService
    {
        readonly IDbContextFactory<ApplicationDbContext> _dbFactory;

        private readonly ILogger _logger;

        public BookService(
            IDbContextFactory<ApplicationDbContext> dbContextFactory,
            ILogger<BookService> logger)
        {
            _dbFactory = dbContextFactory;
            _logger = logger;
        }

        public async Task<List<BookViewModel>> GetBooks()
        {
            using var context = _dbFactory.CreateDbContext();
            return await context.Books.ToListAsync(); 
        }

        public async Task<BookViewModel> GetBook(int id)
        {
            using var context = _dbFactory.CreateDbContext();
            var book = await context.Books.Where(b => b.ID == id).SingleOrDefaultAsync();
            return book!;
        }
        ...
    }
}

What the above factory has done is to create a unique DbContext instance that has its own ContextId property. This is illustrated in the following diagram:

With the Razor component we don’t have to change the way we inject the service class. It is the same as before.

An example illustrating service injection is shown below:

@page "/viewbook"
…
@inject IBookService LibraryBookService
@inject ILoanService LibraryLoanService

We then use the service as before:

private BookViewModel book = new();

protected override async Task OnInitializedAsync()
{
    book = await LibraryBookService.GetBook(ID);
    ... 
   
}

The application will run as expected with calls to the GetBooks() method successfully returning data for the viewing of all books shown below:

And the call to the GetBook() method successfully returning data for the viewing of a single book:

In both cases above, the generated DbContext instance has unique ContextId properties.

We have seen from the above discussion and demonstration how to safely create instances of data contexts within a .NET Core Blazor Server application. In addition, we now know how to inject instances of a data context factory within a service class and generate unique instances of a IDbContext services within class methods that are then used to access our data context using Entity Framework Core.

That is all for today’s post.

I hope you have found this post useful and informative.

Social media & sharing icons powered by UltimatelySocial