From 4f9c5567ca87bcf447895d5904a7e73925a58846 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sun, 11 Aug 2024 01:24:38 +0700 Subject: [PATCH 1/5] Improve data refresh logic in FluentDataGrid This commit enhances the OnParametersSetAsync method in FluentDataGrid to better handle state changes: - Updated mustRefreshData calculation to consider pagination state changes - Moved _lastRefreshedPaginationStateHash update earlier in RefreshDataCoreAsync These changes ensure that: 1. The grid correctly detects when a refresh is needed due to pagination changes 2. The pagination state hash is captured at the start of the refresh process This improvement helps maintain data consistency, especially in scenarios with rapid state changes or when other events trigger state updates. --- src/Core/Components/DataGrid/FluentDataGrid.razor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index 90178a26dd..50a8331061 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -506,6 +506,7 @@ private async Task RefreshDataCoreAsync() var startIndex = Pagination is null ? 0 : (Pagination.CurrentPageIndex * Pagination.ItemsPerPage); GridItemsProviderRequest request = new( startIndex, Pagination?.ItemsPerPage, _sortByColumn, _sortByAscending, thisLoadCts.Token); + _lastRefreshedPaginationStateHash = Pagination?.GetHashCode(); var result = await ResolveItemsRequestAsync(request); if (!thisLoadCts.IsCancellationRequested) { @@ -515,7 +516,6 @@ private async Task RefreshDataCoreAsync() _pendingDataLoadCancellationTokenSource = null; } _internalGridContext.ResetRowIndexes(startIndex); - _lastRefreshedPaginationStateHash = Pagination?.GetHashCode(); } StateHasChanged(); From 19be03d42bca4e7bbbb4f0ffc3aa83f8d6456e27 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Mon, 12 Aug 2024 18:07:12 +0700 Subject: [PATCH 2/5] Fix Grid data source change detection to avoid boxing - Replace object casting and reference comparison with direct Equals method calls - Separately compare Items and ItemsProvider with their last assigned values - Eliminate false positive change detections caused by boxing - Improve performance by reducing unnecessary data refreshes --- src/Core/Components/DataGrid/FluentDataGrid.razor.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index 50a8331061..009cd1385b 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -253,7 +253,8 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve // as sort order, the pagination state, or the data source itself. These fields help us detect when // things have changed, and to discard earlier load attempts that were superseded. private int? _lastRefreshedPaginationStateHash; - private object? _lastAssignedItemsOrProvider; + private IQueryable? _lastAssignedItems; + private GridItemsProvider? _lastAssignedItemsProvider; private CancellationTokenSource? _pendingDataLoadCancellationTokenSource; // If the PaginationState mutates, it raises this event. We use it to trigger a re-render. @@ -303,11 +304,11 @@ protected override Task OnParametersSetAsync() } // Perform a re-query only if the data source or something else has changed - var _newItemsOrItemsProvider = Items ?? (object?)ItemsProvider; - var dataSourceHasChanged = _newItemsOrItemsProvider != _lastAssignedItemsOrProvider; + var dataSourceHasChanged = !Equals(Items, _lastAssignedItems) || !Equals(ItemsProvider, _lastAssignedItemsProvider); if (dataSourceHasChanged) { - _lastAssignedItemsOrProvider = _newItemsOrItemsProvider; + _lastAssignedItemsProvider = ItemsProvider; + _lastAssignedItems = Items; _asyncQueryExecutor = AsyncQueryExecutorSupplier.GetAsyncQueryExecutor(Services, Items); } From b3d2e478c8e61d8ddf41928a7406e0e820732e97 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Mon, 12 Aug 2024 20:22:03 +0700 Subject: [PATCH 3/5] Don't use GetHashCode for comparison. TotalCount should NOT be a part of the comparison. As this is an output not an input. --- src/Core/Components/DataGrid/FluentDataGrid.razor.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index 009cd1385b..d9bc8c2d0c 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -226,7 +226,7 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve // This happens on every render so that the column list can be updated dynamically private readonly InternalGridContext _internalGridContext; internal readonly List> _columns; - private bool _collectingColumns; // Columns might re-render themselves arbitrarily. We only want to capture them at a defined time. + private bool _collectingColumns;// Columns might re-render themselves arbitrarily. We only want to capture them at a defined time. // Tracking state for options and sorting private ColumnBase? _displayOptionsForColumn; @@ -252,7 +252,7 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve // We only re-query when the developer calls RefreshDataAsync, or if we know something's changed, such // as sort order, the pagination state, or the data source itself. These fields help us detect when // things have changed, and to discard earlier load attempts that were superseded. - private int? _lastRefreshedPaginationStateHash; + private PaginationState? _lastRefreshedPaginationState; private IQueryable? _lastAssignedItems; private GridItemsProvider? _lastAssignedItemsProvider; private CancellationTokenSource? _pendingDataLoadCancellationTokenSource; @@ -312,8 +312,12 @@ protected override Task OnParametersSetAsync() _asyncQueryExecutor = AsyncQueryExecutorSupplier.GetAsyncQueryExecutor(Services, Items); } - var mustRefreshData = dataSourceHasChanged - || (Pagination?.GetHashCode() != _lastRefreshedPaginationStateHash); + var paginationStateHasChanged = + Pagination?.ItemsPerPage != _lastRefreshedPaginationState?.ItemsPerPage + || Pagination?.CurrentPageIndex != _lastRefreshedPaginationState?.CurrentPageIndex; + + var mustRefreshData = dataSourceHasChanged || paginationStateHasChanged; + // We don't want to trigger the first data load until we've collected the initial set of columns, // because they might perform some action like setting the default sort order, so it would be wasteful From 1d94026a3118044a50e16f912c8e1b5cd907a5c4 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Mon, 12 Aug 2024 21:31:50 +0700 Subject: [PATCH 4/5] use _lastRefreshedPaginationState instead of _lastRefreshedPaginationStateHash --- src/Core/Components/DataGrid/FluentDataGrid.razor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index d9bc8c2d0c..0334988326 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -511,7 +511,7 @@ private async Task RefreshDataCoreAsync() var startIndex = Pagination is null ? 0 : (Pagination.CurrentPageIndex * Pagination.ItemsPerPage); GridItemsProviderRequest request = new( startIndex, Pagination?.ItemsPerPage, _sortByColumn, _sortByAscending, thisLoadCts.Token); - _lastRefreshedPaginationStateHash = Pagination?.GetHashCode(); + _lastRefreshedPaginationState = Pagination; var result = await ResolveItemsRequestAsync(request); if (!thisLoadCts.IsCancellationRequested) { @@ -529,7 +529,7 @@ private async Task RefreshDataCoreAsync() // Gets called both by RefreshDataCoreAsync and directly by the Virtualize child component during scrolling private async ValueTask> ProvideVirtualizedItemsAsync(ItemsProviderRequest request) { - _lastRefreshedPaginationStateHash = Pagination?.GetHashCode(); + _lastRefreshedPaginationState = Pagination; // Debounce the requests. This eliminates a lot of redundant queries at the cost of slight lag after interactions. // TODO: Consider making this configurable, or smarter (e.g., doesn't delay on first call in a batch, then the amount From 679c4e1f8ccbac7e7043f2f0712c8209275dc9c9 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Mon, 12 Aug 2024 21:38:21 +0700 Subject: [PATCH 5/5] Remove blank line --- src/Core/Components/DataGrid/FluentDataGrid.razor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index 0334988326..57adcbaec0 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -318,7 +318,6 @@ protected override Task OnParametersSetAsync() var mustRefreshData = dataSourceHasChanged || paginationStateHasChanged; - // We don't want to trigger the first data load until we've collected the initial set of columns, // because they might perform some action like setting the default sort order, so it would be wasteful // to have to re-query immediately