dear support, i am trying to implement Database-side pagination with service classes.
the client shows only the first page and 50 records. it does not allow to go to other pages.
here is the console output. can you please help ?
***
nfo: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (2,422ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT COUNT(*)
FROM [Contacts] AS [c]
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (22ms) [Parameters=[@__p_0='?' (DbType = Int32), @__p_1='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SELECT [c].[Id], [c].[Arc], [c].[Comment], [c].[ContactPersonInOrganization], [c].[ContactType], [c].[CreatedAt], [c].[DateOfBirth], [c].[Email], [c].[FirstName], [c].[GdprConsent], [c].[Gender], [c].[Jurisdiction], [c].[LastName], [c].[NationalId], [c].[OrganizationFormationDate], [c].[OrganizationName], [c].[OrganizationRegistrationNumber], [c].[OrganizationType], [c].[OurReference], [c].[Passport], [c].[PhoneHome], [c].[PhoneMobile], [c].[PhoneOther], [c].[PhoneWork], [c].[RowVersion], [c].[Status], [c].[TaxIdentificationCode], [c].[UpdatedAt], [c].[Vat]
FROM [Contacts] AS [c]
ORDER BY [c].[Id]
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
LoadData: Retrieved 50 contacts
LoadData: totalRecords=4320620
LoadData: PageCount=86413
LoadData: Grid refreshed******
*****************
@page "/contactspage3"
@inject ToastService ToastService
@inject IContactService ContactService
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Grids
@using Microsoft.EntityFrameworkCore
<PageTitle>Contacts</PageTitle>
<SfGrid Height="100%" Width="100%" EnableAdaptiveUI="true" EnableStickyHeader="true" @ref="gridContacts" PageSettings="@PageSettings" TotalItemsCount="@totalRecords" TValue="Contact" DataSource="@Contacts" AllowPaging="true" EnableVirtualization=false AllowGrouping=false AllowFiltering=true AllowExcelExport=true>
<GridEvents TValue="Contact" OnActionBegin="OnActionBegin" />
<GridFilterSettings Type="Syncfusion.Blazor.Grids.FilterType.Menu" />
<GridFilterSettings Mode="FilterBarMode.Immediate" ImmediateModeDelay="700"></GridFilterSettings>
<GridSelectionSettings Mode="Syncfusion.Blazor.Grids.SelectionMode.Row" Type="Syncfusion.Blazor.Grids.SelectionType.Multiple" CheckboxOnly="true" PersistSelection="true"></GridSelectionSettings>
<GridColumns>
<GridColumn HeaderText="Manage Records" Width="100">
<GridCommandColumns>
<GridCommandColumn Type="CommandButtonType.Edit" ButtonOption="@(new CommandButtonOptions() { IconCss = "e-icons e-edit", CssClass = "e-flat" })"></GridCommandColumn>
</GridCommandColumns>
</GridColumn>
<GridColumn Type="ColumnType.CheckBox" Width="30"></GridColumn>
<GridColumn Field=@nameof(Contact.Id) HeaderText="dbId" TextAlign="TextAlign.Left" IsPrimaryKey="true" Width="120"></GridColumn>
<GridColumn Field=@nameof(Contact.ContactType) HeaderText="Type" Width="120"></GridColumn>
<GridColumn Field=@nameof(Contact.LastName) HeaderText=@nameof(Contact.LastName) Width="120"></GridColumn>
<GridColumn Field=@nameof(Contact.FirstName) HeaderText=@nameof(Contact.FirstName) Width="120"></GridColumn>
<GridColumn Field=@nameof(Contact.OrganizationName) HeaderText=@nameof(Contact.OrganizationName) Width="120"></GridColumn>
<GridColumn Field=@nameof(Contact.DateOfBirth) HeaderText=@nameof(Contact.DateOfBirth) Width="120"></GridColumn>
<GridColumn Field=@nameof(Contact.PhoneMobile) HeaderText="Mobile" Width="120"></GridColumn>
</GridColumns>
</SfGrid>
@code {
private SfGrid<Contact>? gridContacts;
private List<Contact> Contacts { get; set; } = new List<Contact>();
private GridPageSettings PageSettings { get; set; } = new GridPageSettings { PageSize = 50 };
private const int PageSize = 50;
private int totalRecords=5;
protected override async Task OnInitializedAsync()
{
await LoadData(0, PageSize);
}
private async Task OnActionBegin(ActionEventArgs<Contact> args)
{
if (args.RequestType == Syncfusion.Blazor.Grids.Action.Paging)
{
var currentPage = args.CurrentPage;
var pageSize = PageSize;
var skip = (currentPage - 1) * pageSize;
Console.WriteLine($"OnActionBegin: currentPage={currentPage}, pageSize={pageSize}, skip={skip}");
await LoadData(skip, pageSize);
}
}
private async Task LoadData(int skip, int take)
{
Console.WriteLine($"LoadData: skip={skip}, take={take}");
var result = await ContactService.GetContactsAsyncv2(skip, take);
Console.WriteLine($"LoadData: Retrieved {result.Contacts.Count} contacts");
Contacts = result.Contacts.ToList(); // Ensure complete list replacement
totalRecords = result.TotalCount;
Console.WriteLine($"LoadData: totalRecords={totalRecords}");
// Calculate PageCount based on total records and PageSize
PageSettings.PageCount = (int)Math.Ceiling((double)totalRecords / PageSize);
Console.WriteLine($"LoadData: PageCount={PageSettings.PageCount}");
// Force a grid re-render for debugging purposes
await Task.Delay(100); // Adjust delay as needed
await gridContacts.Refresh();
Console.WriteLine($"LoadData: Grid refreshed");
StateHasChanged();
}
}
=====
public interface IContactService
{
Task<(List<Contact> Contacts, int TotalCount)> GetContactsAsyncv2(int skip,int take);
Task<List<Contact>> GetContactsAsync(QuerySpecification<Contact> spec);
Task<(List<Contact> Contacts, int TotalCount)> GetContactsAsync2(QuerySpecification<Contact> spec);
Task<Contact?> GetContactByIdAsync(int id);
Task<Contact> CreateContactAsync(Contact contact);
Task<Contact> UpdateContactAsync(Contact contact);
Task DeleteContactAsync(int id);
}
public class ContactService : IContactService
{
private readonly ApplicationDbContext _context;
private readonly MyQueryBuilder _myqueryBuilder;
public ContactService(ApplicationDbContext context, ILogger<MyQueryBuilder> logger)
{
_context = context;
_myqueryBuilder = new MyQueryBuilder(logger);
}
public async Task<(List<Contact> Contacts, int TotalCount)> GetContactsAsyncv2(int skip, int take)
{
var totalCount = await _context.Contacts.CountAsync();
var contacts = await _context.Contacts
.OrderBy(c => c.Id)
.Skip(skip)
.Take(take)
.ToListAsync(); // Using ToListAsync() to return List<Contact>
return (contacts, totalCount);
}
Hi Daniel,
Greetings from Syncfusion.
Query: “dear support, i am trying to implement Database-side pagination with service classes. the client shows only the first page and 50 records. it does not allow to go to other pages. here is the console output. can you please help ?”
We suspect that you are encountering issues with pagination on your end. After reviewing your shared code, we observed that only the current page data is being fetched. As a result, the pager element only displays the current page.
To render the pager correctly and allow navigation through all pages, we need to provide the total count of records to the DataGrid. This enables the pager to calculate and display all available pages.
In the example below, we utilized the custom adaptor feature of the DataGrid. With this approach, the DataGrid handles the skip and take requests internally, eliminating the need for external events (OnActionBegin) to manage pagination.
Kindly check the below attached sample and code snippet for your reference.
|
@inject IJSRuntime Runtime @inject HttpClient Http
@if(@IsVisible) {
<SfGrid ID="Grid" @ref="@Grid" TValue="Order" Height="315" Toolbar="@(new List<string>() { "Add", "Edit","Update","Delete","Cancel" , "Search", "ExcelExport" , "CsvExport" ,"PdfExport"})" AllowFiltering="true" AllowSelection="true" AllowSorting="true" AllowResizing="true" AllowReordering="true" AllowGrouping="true" AllowPaging="true" AllowExcelExport="true" AllowPdfExport="true"> <GridEditSettings AllowAdding="true" AllowDeleting="true" AllowEditing="true" Mode="EditMode.Normal"></GridEditSettings> <SfDataManager AdaptorInstance="@typeof(CustomAdaptor)" Adaptor="Adaptors.CustomAdaptor"></SfDataManager> <GridFilterSettings Type="Syncfusion.Blazor.Grids.FilterType.Excel"></GridFilterSettings> <GridPageSettings PageSize="50"></GridPageSettings> </SfGrid> } @code{ SfGrid<Order>? Grid { get; set; } public static List<Order> result = new List<Order>(); public bool IsVisible { get; set; } = false;
protected override async void OnInitialized() { var apiUrl = "https://localhost:7157/api/DataGrid"; result = await Http.GetFromJsonAsync<List<Order>>(apiUrl); //result = result.Take(50).ToList(); //if this line is ebabled then it will render only one page in DataGrid IsVisible = true; StateHasChanged();
}
// Implementing custom adaptor by extending the DataAdaptor class public class CustomAdaptor : DataAdaptor { // Performs data Read operation public override object Read(DataManagerRequest dm, string key = null) { IEnumerable<Order> DataSource = result;
int count = DataSource.Cast<Order>().Count(); // need to pass the entire DataSource count if (dm.Skip != 0) { //Paging DataSource = DataOperations.PerformSkip(DataSource, dm.Skip); } if (dm.Take != 0) { DataSource = DataOperations.PerformTake(DataSource, dm.Take); } return dm.RequiresCounts ? new DataResult() { Result = DataSource, Count = count } : (object)DataSource; }
} } |
Kindly get back to us if we misunderstood your query or if you have further queries.
Regards,
Monisha
hi, if you inspect the console out****
LoadData: Retrieved 50 contacts
LoadData: totalRecords=4320620
LoadData: PageCount=86413
LoadData: Grid refreshed******
total records number is returned
public async Task<(List<Contact> Contacts, int TotalCount)> GetContactsAsyncv2(int skip, int take) {
return (contacts, totalCount);
}
So why the grid is not showing the rest of the pages that must be fetched on demand?
we dont want to use an api but service classed. can you show us a very simple working example in order to achieve the requirements (Database-side pagination,filtering) to load data "on damand when changing page,
Hi Daniel,
Based on the reported problem, we would like to clarify that the TotalItemsCount property is not binding directly to the DataGrid, which is why you faced the issue with your previous approach using the DataSource property.
Instead of using the DataSource property, we suggest using the CustomAdaptor feature to achieve your requirements. We have already documented a similar SQL server data binding with the CustomAdaptor feature.please refer to the documentation below.
Reference: https://blazor.syncfusion.com/documentation/datagrid/data-binding#sql-server-data-bindingsql-client
Previously,
you used the actionBegin handler. Instead, with the CustomAdaptor, the
ReadAsync method should return the current page records and the total record
count. This way, the grid will render paging correctly, and the records for the
next page will be displayed using this approach. This is the default behavior.
Thanks for your understanding.
Regards,
Prathap Senthil
i am trying to implement it but it does not work. i am getting not data.
here is my code for beneficiaries page, and custom adapter and service:
====razor page====
@page "/beneficiaries"
@using Syncfusion.Blazor.Grids
@using Syncfusion.Blazor.Data
@using Rad.Models
@using Rad.Services
@inject BeneficiaryDataAdaptor BeneficiaryDataAdaptor
<h1>Beneficiary Management</h1>
<SfGrid TValue="Beneficiary" AllowPaging="true" AllowSorting="true" AllowFiltering="true">
<GridPageSettings PageSize="10"></GridPageSettings>
<GridFilterSettings Type="Syncfusion.Blazor.Grids.FilterType.Menu"></GridFilterSettings>
<GridEditSettings AllowAdding="true" AllowEditing="true" AllowDeleting="true"></GridEditSettings>
<SfDataManager Adaptor="Adaptors.CustomAdaptor">
<SfDataManager.AdaptorInstance>
@BeneficiaryDataAdaptor
</SfDataManager.AdaptorInstance>
</SfDataManager>
@* <SfDataManager AdaptorInstance="@BeneficiaryDataAdaptor">
</SfDataManager> *@
<GridColumns>
<GridColumn Field="@nameof(Beneficiary.Id)" IsPrimaryKey="true" IsIdentity="true" HeaderText="ID" TextAlign="TextAlign.Right" Width="100"></GridColumn>
<GridColumn Field="@nameof(Beneficiary.FirstName)" HeaderText="First Name" Width="150"></GridColumn>
<GridColumn Field="@nameof(Beneficiary.LastName)" HeaderText="Last Name" Width="150"></GridColumn>
<!-- Add more columns as needed based on your Beneficiary model -->
</GridColumns>
</SfGrid>
@code {
}
=======
//Services> BeneficiaryDataAdaptor.cs
using Syncfusion.Blazor.Data;
using Rad.Services;
using Rad.Models;
using Microsoft.Extensions.Logging;
using Syncfusion.Blazor;
public class BeneficiaryDataAdaptor : DataAdaptor
{
private readonly IBeneficiaryService _beneficiaryService;
private readonly ILogger<BeneficiaryDataAdaptor> _logger;
public BeneficiaryDataAdaptor(IBeneficiaryService beneficiaryService, ILogger<BeneficiaryDataAdaptor> logger)
{
_beneficiaryService = beneficiaryService;
_logger = logger;
}
public override async Task<object> ReadAsync(DataManagerRequest dataManagerRequest, string key = null)
{
_logger.LogInformation("ReadAsync method called");
try
{
var skip = dataManagerRequest.Skip;
var take = dataManagerRequest.Take > 0 ? dataManagerRequest.Take : 10;
var searchText = dataManagerRequest.Search?.FirstOrDefault()?.Key ?? string.Empty;
var orderBy = dataManagerRequest.Sorted?.FirstOrDefault()?.Name ?? "Id";
var isAscending = dataManagerRequest.Sorted?.FirstOrDefault()?.Direction == "Ascending";
_logger.LogInformation($"Fetching data with parameters: skip={skip}, take={take}, searchText='{searchText}', orderBy='{orderBy}', isAscending={isAscending}");
var result = await _beneficiaryService.GetPaginatedBeneficiariesAsync(skip, take, searchText, orderBy, isAscending);
_logger.LogInformation($"Fetched {result.Data.Count} records out of {result.Count}");
return new DataResult() { Result = result.Data, Count = result.Count };
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while fetching beneficiaries");
throw;
}
}
public override async Task<object> InsertAsync(DataManager dataManager, object data, string key)
{
if (data is Beneficiary beneficiary)
{
var createdBeneficiary = await _beneficiaryService.CreateBeneficiaryAsync(beneficiary);
return createdBeneficiary;
}
throw new InvalidCastException("Invalid data type for insertion.");
}
public override async Task<object> UpdateAsync(DataManager dataManager, object data, string keyField, string key)
{
if (data is Beneficiary beneficiary)
{
var updatedBeneficiary = await _beneficiaryService.UpdateBeneficiaryAsync(beneficiary);
return updatedBeneficiary;
}
throw new InvalidCastException("Invalid data type for update.");
}
public override async Task<object> RemoveAsync(DataManager dataManager, object data, string keyField, string key)
{
if (int.TryParse(key, out int id))
{
await _beneficiaryService.DeleteBeneficiaryAsync(id);
return data;
}
throw new ArgumentException("Invalid key for deletion.");
}
}
=======================
// IBeneficiaryService.cs
using Microsoft.EntityFrameworkCore;
using Rad.Data;
using Rad.Models;
using System.Linq.Expressions;
namespace Rad.Services
{
public interface IBeneficiaryService
{
IQueryable<Beneficiary> GetBeneficiariesQuery();
Task<(List<Beneficiary> Data, int Count)> GetPaginatedBeneficiariesAsync(int skip, int take, string searchText, string orderBy, bool isAscending);
Task<List<Beneficiary>> GetAllBeneficiariesAsync(); // New method to get all beneficiaries
Task<Beneficiary?> GetBeneficiaryByIdAsync(int beneficiaryId);
Task<Beneficiary> CreateBeneficiaryAsync(Beneficiary beneficiary);
Task<Beneficiary> UpdateBeneficiaryAsync(Beneficiary beneficiary);
Task DeleteBeneficiaryAsync(int beneficiaryId);
}
public class BeneficiaryService : IBeneficiaryService
{
private readonly ApplicationDbContext _context;
private readonly MyQueryBuilder _myQueryBuilder;
public BeneficiaryService(ApplicationDbContext context, ILogger<MyQueryBuilder> logger)
{
_context = context;
_myQueryBuilder = new MyQueryBuilder(logger);
}
public IQueryable<Beneficiary> GetBeneficiariesQuery()
{
return _context.Beneficiaries.AsNoTracking();
}
public async Task<(List<Beneficiary> Data, int Count)> GetPaginatedBeneficiariesAsync(
int skip, int take, string searchText, string orderBy, bool isAscending)
{
var query = GetBeneficiariesQuery();
if (!string.IsNullOrEmpty(searchText))
{
query = query.Where(b => b.FirstName.Contains(searchText));
}
if (!string.IsNullOrEmpty(orderBy))
{
var parameter = Expression.Parameter(typeof(Beneficiary), "b");
var property = Expression.Property(parameter, orderBy);
var lambda = Expression.Lambda<Func<Beneficiary, object>>(
Expression.Convert(Expression.Coalesce(property, Expression.Constant(string.Empty)), typeof(object)),
parameter
);
query = isAscending ? query.OrderBy(lambda) : query.OrderByDescending(lambda);
}
var count = await query.CountAsync();
var data = await query.Skip(skip).Take(take).ToListAsync();
// Debugging/Logging
Console.WriteLine($"Fetched {data.Count} records out of {count}.");
return (data, count);
}
public async Task<List<Beneficiary>> GetAllBeneficiariesAsync()
{
return await _context.Beneficiaries.ToListAsync();
}
public async Task<Beneficiary?> GetBeneficiaryByIdAsync(int BeneficiaryId)
{
// Implementation for fetching beneficiary by ID
return await _context.Beneficiaries.FindAsync(BeneficiaryId);
}
public async Task<Beneficiary> CreateBeneficiaryAsync(Beneficiary beneficiary)
{
_context.Beneficiaries.Add(beneficiary);
await _context.SaveChangesAsync();
return beneficiary;
}
public async Task<Beneficiary> UpdateBeneficiaryAsync(Beneficiary beneficiary)
{
_context.Entry(beneficiary).State = EntityState.Modified;
await _context.SaveChangesAsync();
return beneficiary;
}
public async Task DeleteBeneficiaryAsync(int beneficiaryId)
{
var beneficiary = await _context.Beneficiaries.FindAsync(beneficiaryId);
if (beneficiary != null)
{
_context.Beneficiaries.Remove(beneficiary);
await _context.SaveChangesAsync();
}
}
}
}
========Program.cs======
builder.Services.AddScoped<IBeneficiaryService, BeneficiaryService>();
builder.Services.AddScoped<BeneficiaryDataAdaptor>();
Based on the reported issue, it appears that the SfDataManager's AdaptorInstance has been incorrectly defined in your code, leading to the problem you're experiencing. We recommend defining the AdaptorInstance correctly. Please refer to the code snippet and sample provided below for guidance. If the issue persists, please share a reproducible sample with duplicate data or try to reproduce the reported issue using the attached sample.
|
<SfDataManager AdaptorInstance="@typeof(BeneficiaryDataAdaptor)" Adaptor="Adaptors.CustomAdaptor">
</SfDataManager> <SfDataManager Adaptor="Adaptors.CustomAdaptor">
<SfDataManager.AdaptorInstance>
@BeneficiaryDataAdaptor
</SfDataManager.AdaptorInstance>
</SfDataManager> |
Note: After
connecting the database, we need to copy the path of the database and change
the connection string with the copied path in appsettings.json file based on
the NORTHWND.MDF file
Additionally,
if you are looking for a custom adapter as a component, we have already
documented this topic. Kindly refer to the documentation below for your
reference.
https://blazor.syncfusion.com/documentation/datagrid/custom-binding#custom-adaptor-as-component