Data fetching in components?
I'm using Blazor Server.
I have set up a partial class called DataContextComponent that uses DBContextFactory to create an instance of dbContext for the lifetime of that component. I thought this would be enough to keep components isolated, but I'm finding that if there is a parent and child component that both inherit from DataContextComponent and fetch data using EF Core, I get an error saying "a second operation was started on this context instance before a previous operation completed." Am I doing something wrong?
I'd like to make reusable components that fetch their own data that I can use throughout the application. Is this an anti pattern?
Thanks for the advice!
10 Replies
This is what I'm using
And then you can just inherit it in your component with the
@inherits
directive.
But you are responsible to ensure that the user cannot trigger two actions at the same time.
For example if you have two buttons, both resulting in a database call, it would throw a concurrency exception, if the user clicks both buttons in rapid succession, before the first database call finishes.Thanks for the response! Somehow I missed this message until just now. My implementation looks very similar. It's been working for the most part, but when I tried fetching data in OnParametersSetAsync in the child component, I'm getting that error. If I move the data loading to OnInitializedAsync in the child component or comment out the data fetching in the parent component, the error goes away. So it has something to do with the interaction between the two components.
Here's my version (essentially the same):
It's probably not important, but why is your class
partial
?
Also could you share a minimal example, of how to produce that error?It might be important... I'm fairly new to C# and OOP in general, so I thought I needed to make it partial so that the inheriting class had access to the protected dbContext. I'll put together a minimal example tomorrow morning.
partial
allows you to split a class between different files. Like you could now go into a diferent .cs file, redeclare the class and add some properties or methods.
Most of the time it is only done to allow source generators to extend your class.
protected
as an accessibility modifier means "Only I and inheriting classes can access this variable". So that already accomplishes your goal.
In Blazor partial
is also used for codebehind files mycomponent.razor.cs
.
That is possible because the .razor
files are actually being translated into a C# class behind the scenes by a source generator.Alright, I spent some time this morning making a minimal reproduction of the error and have determined that it's not caused by the parent and child components both using dbContext. It has more to do with OnParametersSetAsync being called twice in rapid succession on a component with a @bind-Value directive. Here's some code that causes the error:
[Parent.razor]
[Child.razor]
If you use prerendering, then OnParameterSet will always be called twice.
https://github.com/dotnet/aspnetcore/issues/30037
It will also always be rerun, when blazor believes the parameter may have changed.
If your db call does not depend on the parameter I suggest moving it into OnInitializedAsync after the call to the base method.
If it has to be in OnParameterSet you could create a CancellationToken using a CancellationTokenSource and cancel the previous db call.
Or just create a new DbContext.
GitHub
OnParametersSetAsync is being invoked multiple times in certain sce...
Describe the bug I have a page which can be visited through out a parameter. in my case @page "/Test{CustomerId:int}" When I visit the page with a direct link within my blazor project OnP...
Oh, that's an interesting idea. I could have a variation of the DataContextComponent that creates a new dbContext every time OnParametersSetAsync gets called. I think I'll look more into the Cancellation token approach. In this case, I need the value from the parameter in order to do the data fetching. Thanks for the help!
If you create a new one every time you should just use the IDbContextFactory directly.
That's true...