FvB
FvB

Reputation: 723

How do I make data calls from different Blazor components simultaneously?

I'm new to Blazor and trying to make a page with several separate components to handle a massive form. Each individual component covers a part of the form.

The problem I'm facing is that each of my components needs access to data from the back-end, and not every component uses the same data. When the page loads, each components makes an attempt to fetch data from the server, which causes a problem with Entity Framework.

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.

This is obviously caused by the fact that my components are initialized at the same time, and all make their attempt to load the data simultaneously. I was under the impression that the way DI is set up in Blazor, this wouldn't be a problem, but it is.

Here are the components in my template:

<CascadingValue Value="this">
    <!-- BASE DATA -->
    <CharacterBaseDataView />

    <!-- SPECIAL RULES -->
    <CharacterSpecialRulesView />
</CascadingValue>

Here is how my components are initialized:

protected async override Task OnInitializedAsync()
{
    CharacterDetailsContext = new EditContext(PlayerCharacter);
    await LoadCharacterAsync();
}

private async Task LoadCharacterAsync()
{
    PlayerCharacter = await PlayerCharacterService.GetPlayerCharacterAsync(ViewBase.CharacterId.Value);
    CharacterDetailsContext = new EditContext(PlayerCharacter);
}

When two components with the above code are in the same view, the mentioned error occurs. I thread using the synchronous version "OnInitialized()" and simply discarding the task, but that didn't fix the error.

Is there some other way to call the data so that this issue doesn't occur? Or am I going about this the wrong way?

Upvotes: 2

Views: 986

Answers (2)

Dani&#235;l J.M. Hoffman
Dani&#235;l J.M. Hoffman

Reputation: 2157

Initially to solve the threading issues, I used DbContextFactory to create contexts for each operation - however this resulted in database in-consistency issues across components, and I realised I need change tracking across components.

Therefore instead, I keep my DbContext as scoped, and I don't create a new context before each operation.

I then adapted my OnInitializedAsync() methods to check if the calls to the database have completed, before making these calls through my injected services. This works really well for my app:

 @code {
        static Semaphore semaphore;
        //code ommitted for brevity
        
         protected override async Task OnInitializedAsync()
         {
             try
             {
                //First open global semaphore
                semaphore = Semaphore.OpenExisting("GlobalSemaphore");

                while (!semaphore.WaitOne(TimeSpan.FromTicks(1)))
                {
                    await Task.Delay(TimeSpan.FromSeconds(1));
                }
                //If while loop is exited or skipped, previous service calls are completed.
                ApplicationUsers = await ApplicationUserService.Get();        
             
             }
             finally
             {
                try
                {
                    semaphore.Release();
                }
                catch (Exception ex)
                {
                    Console.WriteLine("ex.Message");
                }
            }
        }

Upvotes: 1

MrC aka Shaun Curtis
MrC aka Shaun Curtis

Reputation: 30177

You've hit a common problem in using async operations in EF - two or more operations trying to use the same context at once.

Take a look at the MS Docs article about EF DBContexts - there's a section further down specific to Blazor. It explains the use of a DbContextFactory and CreateDbContext to create contexts for units-of-work i.e. one context per operation so two async operations each have a separate context.

Upvotes: 1

Related Questions