I have the following OnAfterRenderAsync
function in my blazor page:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
try
{
if (firstRender)
{
await JSRuntime.InvokeVoidAsync("initializeDropZone");
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
var userSecurityInfo = await UserManager.GetUserAsync(user);
var userDbImages = (await ImageData.GetImagesByUser(userSecurityInfo.Id)).ToList();
foreach (var dbImage in userDbImages)
{
_userFiles.Add(new ImageModel()
{
ID = dbImage.ID,
DatasetName = dbImage.DatasetName,
Name = dbImage.FileName,
UploadDate = dbImage.UploadDate,
BucketUrl = dbImage.BucketUrl,
BucketUrlExpireDate = dbImage.BuckeUrlExpireDate
});
}
}
StateHasChanged();
}
}
catch(Exception e)
{
Console.WriteLine("OnAfterRenderError (DataManagement):" e.Message);
}
await base.OnAfterRenderAsync(firstRender);
return;
}
While normally this operation is quite fast (it only loads image URLs instead of actual image data) after the first startup of the server it takes a bit. If the user decides to change pages while the operation is still running I get the following error:
fail: Microsoft.EntityFrameworkCore.Query[10100] An exception occurred while iterating over the results of a query for context type 'WebApplication.Data.ApplicationDbContext'. System.InvalidOperationException: A second operation was started on this context 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.Internal.ConcurrencyDetector.EnterCriticalSection() at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable
1.AsyncEnumerator.MoveNextAsync() System.InvalidOperationException: A second operation was started on this context 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.Internal.ConcurrencyDetector.EnterCriticalSection() at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable
1.AsyncEnumerator.MoveNextAsync()
- The second page in this case also has some similar db operation
- The actual data accession is not done in the EF database ASP.NET creates automatically, I store my actual data in a separate MS SQL database, which is accessed using dapper.
- There are some similar posts on SO, such as this one. The answer suggest to use a *
DbContextFactory
. However, I have tried the suggested code, but with that my application crashes during startup with aSystem.AggregateException
. I don't fully understand why. - I don't quite understand, why there is a problem with multiple EF accesses, as I only access the security DB very shortly to get the user ID. This should in theory be done long before the user has the chance to switch pages.
Any suggestions on how to fix this and understand the issue better are appreciated. So far I simply catch the exception, so the application doesn't crash, but I would like to prevent it in the first place.
CodePudding user response:
My guess is that this line:
var userDbImages = (await ImageData.GetImagesByUser(userSecurityInfo.Id)).ToList();
is still executing on the DbContext when you change page, so whatever DB call gets made on the new page it fails because the only DbContext is already in use. I'm guesssing because at this point I don't know what ImageData
is!
The DbContext Factory solves these problems, so you need to sort out what issues you have with implementing the Factory because you can't run async database operations against a single context in a real application. Post a question on your Factory problem and we will see if we can help.
A further comment on design: use a scoped view service to hold the image data so you only get it once: ignore if I'm reading this code wrongly and you are already doing so in ImageData
.