diff --git a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml
index eeddcfa75e..147ece7c0b 100644
--- a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml
+++ b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml
@@ -1954,7 +1954,7 @@
Gets or sets a value indicating whether column resize handles should extend the full height of the grid.
- When true, columns can be resized by dragging from any row. When false, columns can only be resized
+ When true, columns can be resized by dragging from any row. When false, columns can only be resized
by dragging from the column header. Default is true.
@@ -2140,6 +2140,13 @@
Gets or sets a value indicating whether the grids' first cell should be focused.
+
+
+ Gets or sets a value indicating whether the grid's dataset is not expected to change during its lifetime.
+ When set to true, reduces automatic refresh checks for better performance with static datasets.
+ Default is false to maintain backward compatibility.
+
+
Constructs an instance of .
@@ -2283,12 +2290,6 @@
-
-
- Computes a hash code for the given items.
- To limit the effect on performance, only the given maximum number (default 250) of items will be considered.
-
-
Gets or sets the reference to the item that holds this cell's values.
diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs
index 1cd7123d28..d6a8c91176 100644
--- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs
+++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs
@@ -117,7 +117,7 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve
///
/// Gets or sets a value indicating whether column resize handles should extend the full height of the grid.
- /// When true, columns can be resized by dragging from any row. When false, columns can only be resized
+ /// When true, columns can be resized by dragging from any row. When false, columns can only be resized
/// by dragging from the column header. Default is true.
///
[Parameter]
@@ -335,6 +335,14 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve
[Parameter]
public bool AutoFocus { get; set; } = false;
+ ///
+ /// Gets or sets a value indicating whether the grid's dataset is not expected to change during its lifetime.
+ /// When set to true, reduces automatic refresh checks for better performance with static datasets.
+ /// Default is false to maintain backward compatibility.
+ ///
+ [Parameter]
+ public bool IsFixed { get; set; }
+
// Returns Loading if set (controlled). If not controlled,
// we assume the grid is loading until the next data load completes
internal bool EffectiveLoadingValue => Loading ?? ItemsProvider is not null;
@@ -382,7 +390,7 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve
// things have changed, and to discard earlier load attempts that were superseded.
private PaginationState? _lastRefreshedPaginationState;
private IQueryable? _lastAssignedItems;
- private int _lastAssignedItemsHashCode;
+
private GridItemsProvider? _lastAssignedItemsProvider;
private CancellationTokenSource? _pendingDataLoadCancellationTokenSource;
@@ -443,18 +451,14 @@ protected override Task OnParametersSetAsync()
throw new InvalidOperationException($"FluentDataGrid cannot use both {nameof(Virtualize)} and {nameof(MultiLine)} at the same time.");
}
- var currentItemsHash = FluentDataGrid.ComputeItemsHash(Items);
- var itemsChanged = currentItemsHash != _lastAssignedItemsHashCode;
-
// Perform a re-query only if the data source or something else has changed
- var dataSourceHasChanged = itemsChanged || !Equals(ItemsProvider, _lastAssignedItemsProvider);
+ var dataSourceHasChanged = !Equals(ItemsProvider, _lastAssignedItemsProvider) || !ReferenceEquals(Items, _lastAssignedItems);
if (dataSourceHasChanged)
{
_scope?.Dispose();
_scope = ScopeFactory.CreateAsyncScope();
_lastAssignedItemsProvider = ItemsProvider;
_lastAssignedItems = Items;
- _lastAssignedItemsHashCode = currentItemsHash;
_asyncQueryExecutor = AsyncQueryExecutorSupplier.GetAsyncQueryExecutor(_scope.Value.ServiceProvider, Items);
}
@@ -761,6 +765,25 @@ private async Task RefreshDataCoreAsync()
if (RefreshItems is not null)
{
+ if (IsFixed)
+ {
+ if (_forceRefreshData || _lastRequest == null)
+ {
+ _forceRefreshData = false;
+ _lastRequest = request;
+ await RefreshItems.Invoke(request);
+ }
+ }
+ else
+ {
+ if (_forceRefreshData || _lastRequest == null || !_lastRequest.Value.IsSameRequest(request))
+ {
+ _forceRefreshData = false;
+ _lastRequest = request;
+ await RefreshItems.Invoke(request);
+ }
+ }
+
if (_forceRefreshData || _lastRequest == null || !_lastRequest.Value.IsSameRequest(request))
{
_forceRefreshData = false;
@@ -1115,31 +1138,5 @@ public async Task ResetColumnWidthsAsync()
await Module.InvokeVoidAsync("resetColumnWidths", _gridReference);
}
}
-
- ///
- /// Computes a hash code for the given items.
- /// To limit the effect on performance, only the given maximum number (default 250) of items will be considered.
- ///
- private static int ComputeItemsHash(IEnumerable? items, int maxItems = 250)
- {
- if (items == null)
- {
- return 0;
- }
- unchecked
- {
- var hash = 19;
- var count = 0;
- foreach (var item in items)
- {
- if (++count > maxItems)
- {
- break;
- }
- hash = (hash * 31) + (item?.GetHashCode() ?? 0);
- }
- return hash;
- }
- }
}
diff --git a/tests/Core/DataGrid/FluentDataGridIsFixedTests.razor b/tests/Core/DataGrid/FluentDataGridIsFixedTests.razor
new file mode 100644
index 0000000000..e29315c110
--- /dev/null
+++ b/tests/Core/DataGrid/FluentDataGridIsFixedTests.razor
@@ -0,0 +1,168 @@
+@using Xunit
+@inherits TestContext
+
+@code {
+ public FluentDataGridIsFixedTests()
+ {
+ var dataGridModule = JSInterop.SetupModule("./_content/Microsoft.FluentUI.AspNetCore.Components/Components/DataGrid/FluentDataGrid.razor.js");
+ dataGridModule.SetupModule("init", _ => true);
+
+ // Register services
+ Services.AddSingleton(LibraryConfiguration.ForUnitTests);
+ Services.AddScoped(factory => new KeyCodeService());
+ }
+
+ [Fact]
+ public void FluentDataGrid_IsFixed_Default_Value_Is_False()
+ {
+ // Arrange && Act
+ var cut = Render>(
+ @
+
+
+
+ );
+
+ // Assert
+ var dataGrid = cut.Instance;
+ Assert.False(dataGrid.IsFixed);
+ }
+
+ [Fact]
+ public void FluentDataGrid_IsFixed_Can_Be_Set_To_True()
+ {
+ // Arrange && Act
+ var cut = Render>(
+ @
+
+
+
+ );
+
+ // Assert
+ var dataGrid = cut.Instance;
+ Assert.True(dataGrid.IsFixed);
+ }
+
+ [Fact]
+ public async Task FluentDataGrid_IsFixed_True_Allows_Data_Changes_Without_Automatic_Refresh()
+ {
+ // Arrange
+ var items = GetCustomers().AsQueryable();
+
+ var cut = Render>(
+ @
+
+
+
+ );
+
+ var dataGrid = cut.Instance;
+
+ // Act - Update items (simulating data change)
+ var newItems = GetCustomers().Concat(new[] { new Customer(4, "New Customer") }).AsQueryable();
+ cut.SetParametersAndRender(parameters => parameters
+ .Add(p => p.Items, newItems));
+
+ // Assert - With IsFixed=true, the grid should still work correctly
+ Assert.True(dataGrid.IsFixed);
+ }
+
+ [Fact]
+ public async Task FluentDataGrid_IsFixed_True_Still_Allows_Pagination()
+ {
+ // Arrange
+ var pagination = new PaginationState { ItemsPerPage = 2 };
+
+ var cut = Render>(
+ @
+
+
+
+ );
+
+ // Act - Change pagination
+ await cut.InvokeAsync(() => pagination.SetCurrentPageIndexAsync(1));
+
+ // Assert - Should still work with IsFixed=true
+ Assert.Equal(1, pagination.CurrentPageIndex);
+ }
+
+ [Fact]
+ public async Task FluentDataGrid_IsFixed_False_Allows_Normal_Refresh_Behavior()
+ {
+ // Arrange
+ var refreshCallCount = 0;
+ async ValueTask> GetItems(GridItemsProviderRequest request)
+ {
+ refreshCallCount++;
+ await Task.Delay(1); // Simulate async work
+ return GridItemsProviderResult.From(
+ GetCustomers().ToArray(),
+ GetCustomers().Count());
+ }
+
+ var cut = Render>(
+ @
+
+
+
+ );
+
+ // Wait for initial load
+ await Task.Delay(100);
+ var dataGrid = cut.Instance;
+
+ // Act - Explicitly refresh
+ await cut.InvokeAsync(() => dataGrid.RefreshDataAsync(force: true));
+ await Task.Delay(100);
+
+ // Assert - With IsFixed=false, explicit refresh should still work
+ Assert.True(refreshCallCount >= 2,
+ $"Expected at least 2 refresh calls (initial + explicit). Got {refreshCallCount} calls.");
+ }
+
+ [Fact]
+ public async Task FluentDataGrid_IsFixed_True_Still_Allows_Explicit_Refresh()
+ {
+ // Arrange
+ var refreshCallCount = 0;
+ async ValueTask> GetItems(GridItemsProviderRequest request)
+ {
+ refreshCallCount++;
+ await Task.Delay(1); // Simulate async work
+ return GridItemsProviderResult.From(
+ GetCustomers().ToArray(),
+ GetCustomers().Count());
+ }
+
+ var cut = Render>(
+ @
+
+
+
+ );
+
+ // Wait for initial load
+ await Task.Delay(100);
+ var dataGrid = cut.Instance;
+
+ // Act - Explicitly refresh even with IsFixed=true
+ await cut.InvokeAsync(() => dataGrid.RefreshDataAsync(force: true));
+ await Task.Delay(100);
+
+ // Assert - Explicit refresh should still work with IsFixed=true
+ Assert.True(refreshCallCount >= 2,
+ $"Expected at least 2 refresh calls (initial + explicit). Got {refreshCallCount} calls.");
+ }
+
+ // Sample data...
+ private IEnumerable GetCustomers()
+ {
+ yield return new Customer(1, "Denis Voituron");
+ yield return new Customer(2, "Vincent Baaij");
+ yield return new Customer(3, "Bill Gates");
+ }
+
+ private record Customer(int Id, string Name);
+}