Skip to content

Commit 4497955

Browse files
authored
Multiple concurrent queries InvalidOperationException (#34882)
1 parent 966ee12 commit 4497955

File tree

1 file changed

+91
-0
lines changed

1 file changed

+91
-0
lines changed

aspnetcore/blazor/components/quickgrid.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,3 +567,94 @@ dotnet aspnet-codegenerator blazor -h
567567
---
568568

569569
For an example use of the QuickGrid scaffolder, see <xref:blazor/tutorials/movie-database-app/index>.
570+
571+
<!-- UPDATE 10.0 - PU work tracked by https://github.com/dotnet/aspnetcore/issues/58716.
572+
Versioning out at 10.0 for now. -->
573+
574+
:::moniker range="< aspnetcore-10.0"
575+
576+
## Multiple concurrent EF Core queries trigger `System.InvalidOperationException`
577+
578+
Multiple concurrent EF Core queries can trigger the following <xref:System.InvalidOperationException?displayProperty=fullName>:
579+
580+
> :::no-loc text="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.":::
581+
582+
This scenario is scheduled for improvement in an upcoming release of ASP.NET Core. For more information, see [[Blazor] Improve the experience with QuickGrid and EF Core (`dotnet/aspnetcore` #58716)](https://github.com/dotnet/aspnetcore/issues/58716).
583+
584+
In the meantime, you can address the problem using an <xref:Microsoft.AspNetCore.Components.QuickGrid.QuickGrid%601.ItemsProvider%2A> with a cancellation token. The cancellation token prevents concurrent queries by cancelling the previous request when a new request is issued.
585+
586+
Consider the following example, which is based on the movie database `Index` component for the <xref:blazor/tutorials/movie-database-app/index> tutorial. The simpler version scaffolded into the app can be seen in the article's [sample app](xref:blazor/tutorials/movie-database-app/index#sample-app). The `Index` component scaffolded into the app is replaced by the following component.
587+
588+
`Components/Pages/MoviePages/Index.razor`:
589+
590+
```razor
591+
@page "/movies"
592+
@rendermode InteractiveServer
593+
@using Microsoft.EntityFrameworkCore
594+
@using Microsoft.AspNetCore.Components.QuickGrid
595+
@using BlazorWebAppMovies.Models
596+
@using BlazorWebAppMovies.Data
597+
@inject IDbContextFactory<BlazorWebAppMovies.Data.BlazorWebAppMoviesContext> DbFactory
598+
599+
<PageTitle>Index</PageTitle>
600+
601+
<h1>Index</h1>
602+
603+
<div>
604+
<input type="search" @bind="titleFilter" @bind:event="oninput" />
605+
</div>
606+
607+
<p>
608+
<a href="movies/create">Create New</a>
609+
</p>
610+
611+
<div>
612+
<QuickGrid Class="table" TGridItem="Movie" ItemsProvider="GetMovies"
613+
ItemKey="(x => x.Id)" Pagination="pagination">
614+
<PropertyColumn Property="movie => movie.Title" Sortable="true" />
615+
<PropertyColumn Property="movie => movie.ReleaseDate" Title="Release Date" />
616+
<PropertyColumn Property="movie => movie.Genre" />
617+
<PropertyColumn Property="movie => movie.Price" />
618+
<PropertyColumn Property="movie => movie.Rating" />
619+
620+
<TemplateColumn Context="movie">
621+
<a href="@($"movies/edit?id={movie.Id}")">Edit</a> |
622+
<a href="@($"movies/details?id={movie.Id}")">Details</a> |
623+
<a href="@($"movies/delete?id={movie.Id}")">Delete</a>
624+
</TemplateColumn>
625+
</QuickGrid>
626+
</div>
627+
628+
<Paginator State="pagination" />
629+
630+
@code {
631+
private BlazorWebAppMoviesContext context = default!;
632+
private PaginationState pagination = new PaginationState { ItemsPerPage = 5 };
633+
private string titleFilter = string.Empty;
634+
635+
public async ValueTask<GridItemsProviderResult<Movie>> GetMovies(GridItemsProviderRequest<Movie> request)
636+
{
637+
using var context = DbFactory.CreateDbContext();
638+
var totalCount = await context.Movie.CountAsync(request.CancellationToken);
639+
IQueryable<Movie> query = context.Movie.OrderBy(x => x.Id);
640+
query = request.ApplySorting(query).Skip(request.StartIndex);
641+
642+
if (request.Count.HasValue)
643+
{
644+
query = query.Take(request.Count.Value);
645+
}
646+
647+
var items = await query.ToArrayAsync(request.CancellationToken);
648+
649+
var result = new GridItemsProviderResult<Movie>
650+
{
651+
Items = items,
652+
TotalItemCount = totalCount
653+
};
654+
655+
return result;
656+
}
657+
}
658+
```
659+
660+
:::moniker-end

0 commit comments

Comments
 (0)