Blazor QuickGrid SetCurrentPageIndexAsync error

457 Views Asked by At

I use QuickGrid for userManager.Users in initializedasync like this:

itemsQueryable = userManager.Users.AsQueryable();
 pagination.TotalItemCountChanged += (sender, eventArgs) => StateHasChanged();

I use the quickgrid official filtering and custom paging examples combined and using Identity's userManager.Users as data. the grid works good except two points, when I drag the second slider for maxmedals and when I use custom paging using this method:

private async Task GoToPageAsync(int pageIndex)
 {
     await pagination.SetCurrentPageIndexAsync(pageIndex);
 }

That I get the error:

    Error: 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.

I tried using factories:

services1.AddDbContextFactory<AppDbContext>(options =>
options.UseSqlServer(appConnectionString,
                x => { x.UseNetTopologySuite(); }), ServiceLifetime.Transient);

services1.AddDbContextFactory<IdentityContext>(options =>
    options.UseSqlServer(identityConnectionString), ServiceLifetime.Transient);

But still I get this error. How to solve this?

The complete code:

 <div class="page-size-chooser mb-3">
        Items per page:
        <select @bind="@pagination.ItemsPerPage">
            <option>2</option>
            <option>5</option>
            <option>10</option>
            <option>20</option>
            <option>50</option>
        </select>
    </div>
    <div class="grid table-responsive table table-sm caption-top" style="min-height: 25vh;">
        <QuickGrid Items="@FilteredCountries" Pagination="@pagination">
            <PropertyColumn Property="@(c => c.UserName)" Sortable="true" Class="user-name">
                <ColumnOptions>
                    <div class="search-box">
                        <input type="search" autofocus @bind="nameFilter" @bind:event="oninput" placeholder="User name..." />
                    </div>
                </ColumnOptions>
            </PropertyColumn>
            <PropertyColumn Property="@(c => c.Email)" Sortable="true" Align="Microsoft.AspNetCore.Components.QuickGrid.Align.Center" />
            <PropertyColumn Property="@(c => c.CreatedDate)" Sortable="true" Align="Microsoft.AspNetCore.Components.QuickGrid.Align.Center" />
            <PropertyColumn Property="@(c => c.LastLoginDate)" Sortable="true" Align="Microsoft.AspNetCore.Components.QuickGrid.Align.Center" />
            <TemplateColumn Title="Approved" Sortable="true" SortBy="sortByBool" Align="Microsoft.AspNetCore.Components.QuickGrid.Align.Center">
                <div class="flex items-center">
                    <nobr>
                        <strong>@(context.IsApproved ? "Yes" : "No")</strong>
                    </nobr>
                </div>
            </TemplateColumn>
            <PropertyColumn Property="@(c => c.AccessFailedCount)" Sortable="true" Align="Microsoft.AspNetCore.Components.QuickGrid.Align.Center">
                <ColumnOptions>
                    <p>Min: <input type="range" @bind="minMedals" @bind:event="oninput" min="0" max="10" /> <span class="inline-block w-10">@minMedals</span></p>
                    <p>Max: <input type="range" @bind="maxMedals" @bind:event="oninput" min="0" max="20" /> <span class="inline-block w-10">@maxMedals</span></p>
                </ColumnOptions>
            </PropertyColumn>
        </QuickGrid>
    </div>
    @*<Paginator Value="@pagination" />*@
    <div class="page-buttons">
        Page:
        @if (pagination.TotalItemCount.HasValue)
        {
            for (var pageIndex = 0; pageIndex <= pagination.LastPageIndex; pageIndex++)
            {
                var capturedIndex = pageIndex;
                <button @onclick="@(() => GoToPageAsync(capturedIndex))"
                        class="@PageButtonClass(capturedIndex)"
                        aria-current="@AriaCurrentValue(capturedIndex)"
                        aria-label="Go to page @(pageIndex + 1)">
                    @(pageIndex + 1)
                </button>
            }
        }
    </div>
    

code section:

@code {

protected override async Task OnInitializedAsync()
{
    await base.OnInitializedAsync();

    itemsQueryable = userManager.Users.AsQueryable();
    pagination.TotalItemCountChanged += (sender, eventArgs) => StateHasChanged();
}

    PaginationState pagination = new PaginationState { ItemsPerPage = 10 };
    IQueryable<AppUser> itemsQueryable;
    string nameFilter;
    int minMedals;
    int maxMedals = 20;

    GridSort<AppUser> sortByBool = GridSort<AppUser>
        .ByAscending(p => p.IsApproved)
        .ThenDescending(p => p.LastLoginDate);

    IQueryable<AppUser> FilteredCountries
    {
        get
        {
            var result = itemsQueryable?.Where(c => c.AccessFailedCount <= maxMedals);

            if (!string.IsNullOrEmpty(nameFilter))
            {
                result = result.Where(c => c.UserName.Contains(nameFilter));
            }

            if (minMedals > 0)
            {
                result = result.Where(c => c.AccessFailedCount >= minMedals);
            }

            return result;
        }
    }

    private async Task GoToPageAsync(int pageIndex)
    {
        await pagination.SetCurrentPageIndexAsync(pageIndex);
    }

    private string? PageButtonClass(int pageIndex)
        => pagination.CurrentPageIndex == pageIndex ? "current" : null;

    private string? AriaCurrentValue(int pageIndex)
        => pagination.CurrentPageIndex == pageIndex ? "page" : null;
}
1

There are 1 best solutions below

0
On BEST ANSWER

I solved my problem using:

bool dontrender = false;
private async Task GoToPageAsync(int pageIndex)
{
    dontrender = true;
    await pagination.SetCurrentPageIndexAsync(pageIndex);
}

protected override bool ShouldRender()
{
    base.ShouldRender();

    bool shouldrender = !dontrender;
    dontrender = false;
    return shouldrender;
}

and Also removing: @bind:event="oninput" from:

<ColumnOptions>
   <p>Min: <input type="range" @bind="minMedals" @bind:event="oninput" min="0" max="10" /> <span class="inline-block w-10">@minMedals</span></p>
   <p>Max: <input type="range" @bind="maxMedals" @bind:event="oninput" min="0" max="20" /> <span class="inline-block w-10">@maxMedals</span></p>
</ColumnOptions>

To:

 <ColumnOptions>
     <p>Min: <input type="range" @bind="minMedals" min="0" max="10" /> <span class="inline-block w-10">@minMedals</span></p>
     <p>Max: <input type="range" @bind="maxMedals" min="0" max="20" /> <span class="inline-block w-10">@maxMedals</span></p>@*both removed@bind:event="oninput"*@                
 </ColumnOptions>