Loading RadzenDataGrid Hierarchy dynamically doesn't work for record objects #1931

Closed
opened 2026-01-29 18:15:47 +00:00 by claunia · 1 comment
Owner

Originally created by @conor2 on GitHub (Dec 16, 2025).

Implementing a dynamically loaded hierarchy in the RadzenDataGrid doesn't work when the output models are record objects. What happens is that when you try to expand a section of the hierarchy that needs to be dynamically loaded, it will load the collection in the backend, and then partially collapse the hierarchy (i.e. the inner table is not visible, but the arrow still says the table is expanded).

We've changed our code to use class objects for the affected view models as a work around.

To Reproduce
Steps to reproduce the behavior:

  1. Clone my repository
  2. Run the project
  3. Try to expand any of the rows
  4. Notice that there are no errors

Expected behavior
Record objects work when building a data grid hierarchy that is eagerly loaded. So, I would expect record objects to work for the dynamic loading of hierarchies as well.

Screenshots

Image

Desktop:

  • OS: Windows 11 Enterprise 23H2
  • OS Build: 22631.6199
  • Browser Chrome 142.0.7444.177
  • Radzen Versions 8.3.0 and 8.4.0
Originally created by @conor2 on GitHub (Dec 16, 2025). Implementing a dynamically loaded hierarchy in the RadzenDataGrid doesn't work when the output models are record objects. What happens is that when you try to expand a section of the hierarchy that needs to be dynamically loaded, it will load the collection in the backend, and then partially collapse the hierarchy (i.e. the inner table is not visible, but the arrow still says the table is expanded). We've changed our code to use class objects for the affected view models as a work around. **To Reproduce** Steps to reproduce the behavior: 1. Clone [my repository](https://github.com/conor2/RadzenHierarchyBug/tree/main) 2. Run the project 3. Try to expand any of the rows 4. Notice that there are no errors **Expected behavior** Record objects work when building a data grid hierarchy that is eagerly loaded. So, I would expect record objects to work for the dynamic loading of hierarchies as well. **Screenshots** ![Image](https://github.com/user-attachments/assets/fb87f662-2543-4881-b797-20c94eb95d63) **Desktop:** - OS: Windows 11 Enterprise 23H2 - OS Build: 22631.6199 - Browser Chrome 142.0.7444.177 - Radzen Versions 8.3.0 and 8.4.0
Author
Owner

@enchev commented on GitHub (Dec 17, 2025):

Records are immuatable and your code will never work as with classes. Here is a fixed version:

@page "/"
@rendermode InteractiveServer

<PageTitle>Dynamic Hierarchies with Records and RadzenDataGrid</PageTitle>

<RadzenDataGrid IsLoading="_loading"
				Data="@_boxes"
				TItem="Box"
				AllowColumnResize="true"
				AllowPaging="true"
				AllowSorting="true"
				PageSizeOptions="_pageSizeOptions"
				PagerHorizontalAlign="HorizontalAlign.Center"
				ShowPagingSummary="true"
				ExpandMode="DataGridExpandMode.Single"
				RowExpand="OnRowExpand"
				RowRender="@OnRowRender">
	<Template Context="box">
		<RadzenDataGrid Data="@box.Items"
						TItem="Item"
						AllowPaging="true"
						PageSize="5"
						PagerHorizontalAlign="HorizontalAlign.Center"
						ShowPagingSummary="true">
			<Columns>
				<RadzenDataGridColumn Width="10em" Property="ProductNo" Title="Product No" />
				<RadzenDataGridColumn Width="20em" Property="Description" Title="Description" />
				<RadzenDataGridColumn Width="10em" Property="Price" Title="Price" />
				<RadzenDataGridColumn Width="10em" Property="Weight" Title="Weight" />
			</Columns>
		</RadzenDataGrid>
	</Template>
	<Columns>
		<RadzenDataGridColumn Property="Width" Title="Width" />
		<RadzenDataGridColumn Property="Height" Title="Height" />
		<RadzenDataGridColumn Property="Length" Title="Length" />
	</Columns>
</RadzenDataGrid>

<style>
	.rz-column-title-content {
		font-weight: bold;
		white-space: wrap !important;
	}

	button[type='submit'] {
		margin-top: 1em !important;
		margin-left: 1.5rem;
	}

	section {
		display: inline-block;
	}

	fieldset {
		margin-bottom: 1em !important;
	}

	th {
		vertical-align: bottom;
	}

	label, legend {
		font-weight: bold;
	}

	tr.bad-change td {
		background-color: yellow !important;
	}

	tr.bad-change:nth-child(even) td {
		background-color: #e5e619 !important;
	}

	ul {
		padding-left: 0;
		list-style: none;
	}
</style>

@code {
	record Box
	{
		public int Width { get; set; }
		public int Height { get; set; }
		public int Length { get; set; }
		public IList<Item>? Items { get; set; }

		public virtual bool Equals(Box? other)
		=> other is not null
		   && Width == other.Width
		   && Height == other.Height
		   && Length == other.Length;

		public override int GetHashCode()
			=> HashCode.Combine(Width, Height, Length);
	}

	record Item {
		public string ProductNo { get; set; }
		public string Description { get; set; }
		public decimal Price { get; set; }
		public decimal Weight { get; set; }
	}

	private readonly IEnumerable<int> _pageSizeOptions = [4, 8, 10];
	private IList<Box>? _boxes = new List<Box>();
	private bool _loading;

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

		_loading = true;
		try {
			await Task.Delay(TimeSpan.FromSeconds(1));
			_boxes = new List<Box> {
				new Box { Width = 10, Height = 20, Length = 30 },
				new Box { Width = 15, Height = 25, Length = 35 },
				new Box { Width = 20, Height = 30, Length = 40 }
			};
		} finally {
			_loading = false;
		}
	}

	private void OnRowRender(RowRenderEventArgs<Box> args) {
		args.Expandable = true;
	}

	private async Task OnRowExpand(Box box) {
		if (box.Items is { Count: > 0 })
			return;

		await Task.Delay(TimeSpan.FromSeconds(1));
		var items = new List<Item>();
		for (var i = 0; i < 100000; i++) {
			items.Add(new Item {
				ProductNo = $"P{i:D5}",
				Description = $"Product {i}",
				Price = (decimal)(i % 100) + 0.99m,
				Weight = (decimal)(i % 10) + 0.1m
			});
		}

		var record = _boxes!.FirstOrDefault(b =>
			b.Width == box.Width &&
			b.Height == box.Height &&
			b.Length == box.Length);
		record!.Items = items;
	}
}
@enchev commented on GitHub (Dec 17, 2025): Records are immuatable and your code will never work as with classes. Here is a fixed version: ``` @page "/" @rendermode InteractiveServer <PageTitle>Dynamic Hierarchies with Records and RadzenDataGrid</PageTitle> <RadzenDataGrid IsLoading="_loading" Data="@_boxes" TItem="Box" AllowColumnResize="true" AllowPaging="true" AllowSorting="true" PageSizeOptions="_pageSizeOptions" PagerHorizontalAlign="HorizontalAlign.Center" ShowPagingSummary="true" ExpandMode="DataGridExpandMode.Single" RowExpand="OnRowExpand" RowRender="@OnRowRender"> <Template Context="box"> <RadzenDataGrid Data="@box.Items" TItem="Item" AllowPaging="true" PageSize="5" PagerHorizontalAlign="HorizontalAlign.Center" ShowPagingSummary="true"> <Columns> <RadzenDataGridColumn Width="10em" Property="ProductNo" Title="Product No" /> <RadzenDataGridColumn Width="20em" Property="Description" Title="Description" /> <RadzenDataGridColumn Width="10em" Property="Price" Title="Price" /> <RadzenDataGridColumn Width="10em" Property="Weight" Title="Weight" /> </Columns> </RadzenDataGrid> </Template> <Columns> <RadzenDataGridColumn Property="Width" Title="Width" /> <RadzenDataGridColumn Property="Height" Title="Height" /> <RadzenDataGridColumn Property="Length" Title="Length" /> </Columns> </RadzenDataGrid> <style> .rz-column-title-content { font-weight: bold; white-space: wrap !important; } button[type='submit'] { margin-top: 1em !important; margin-left: 1.5rem; } section { display: inline-block; } fieldset { margin-bottom: 1em !important; } th { vertical-align: bottom; } label, legend { font-weight: bold; } tr.bad-change td { background-color: yellow !important; } tr.bad-change:nth-child(even) td { background-color: #e5e619 !important; } ul { padding-left: 0; list-style: none; } </style> @code { record Box { public int Width { get; set; } public int Height { get; set; } public int Length { get; set; } public IList<Item>? Items { get; set; } public virtual bool Equals(Box? other) => other is not null && Width == other.Width && Height == other.Height && Length == other.Length; public override int GetHashCode() => HashCode.Combine(Width, Height, Length); } record Item { public string ProductNo { get; set; } public string Description { get; set; } public decimal Price { get; set; } public decimal Weight { get; set; } } private readonly IEnumerable<int> _pageSizeOptions = [4, 8, 10]; private IList<Box>? _boxes = new List<Box>(); private bool _loading; protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); _loading = true; try { await Task.Delay(TimeSpan.FromSeconds(1)); _boxes = new List<Box> { new Box { Width = 10, Height = 20, Length = 30 }, new Box { Width = 15, Height = 25, Length = 35 }, new Box { Width = 20, Height = 30, Length = 40 } }; } finally { _loading = false; } } private void OnRowRender(RowRenderEventArgs<Box> args) { args.Expandable = true; } private async Task OnRowExpand(Box box) { if (box.Items is { Count: > 0 }) return; await Task.Delay(TimeSpan.FromSeconds(1)); var items = new List<Item>(); for (var i = 0; i < 100000; i++) { items.Add(new Item { ProductNo = $"P{i:D5}", Description = $"Product {i}", Price = (decimal)(i % 100) + 0.99m, Weight = (decimal)(i % 10) + 0.1m }); } var record = _boxes!.FirstOrDefault(b => b.Width == box.Width && b.Height == box.Height && b.Length == box.Length); record!.Items = items; } } ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/radzen-blazor#1931