Collection Properties

Collection properties render a child-entity collection as an inline data grid within a DataGraph page. CollectionPropertyEdit provides an editable grid with Add Row, Delete Row, and custom row actions. CollectionPropertyView renders the same grid in read-only form. Both are auto-selected by PropertyComponent when the property type is Collection; no manual component selection is needed.

Declaring a Collection in a Graph

To surface a collection in a DataGraph, include it using .Navigation() on the GraphBuilder:

private static GraphBuilder BuildGraph() => new GraphBuilder("Order")
    .Group("Summary", g => g
        .Property("OrderDate")
        .Property("Status"))
    .Navigation("LineItems", nav => nav
        .Property("ProductName")
        .Property("Quantity")
        .Property("UnitPrice"));

Then render the collection on the page:

<DataGraph Builder="@BuildGraph" DataSet="@_dataSet" EntityGuid="@_guid">
    <PropertyComponent Path="OrderDate" />
    <PropertyComponent Path="Status" />
    <PropertyComponent Path="LineItems" />
</DataGraph>

PropertyComponent resolves the Collection type and renders CollectionPropertyEdit (or CollectionPropertyView in View mode) automatically.

CollectionPropertyBase Parameters

Both CollectionPropertyEdit and CollectionPropertyView inherit from CollectionPropertyBase and share the following parameters. Pass them directly on CollectionPropertyEdit or CollectionPropertyView when you render the component explicitly inside a PropertyComponent.

Parameter Type Description
ChildContent RenderFragment? Declarative content for column templates and row actions. Accepts <ColumnTemplate> and <CollectionRowAction> elements only.
ColumnTemplates Dictionary<string, RenderFragment<PropertyComponentContext>>? Programmatic column template overrides keyed by property name. An alternative to the declarative ChildContent syntax.
PropertyGroup string? Name of a graph definition group whose properties drive the column set. When set, only the properties from that group scoped to the collection path are shown, in group order. Falls back to all navigated properties if the group is absent or has no matching properties.
MaxHeight string? CSS value for the maximum height of the scrolling container (e.g., "400px", "50vh"). When the row count exceeds this height, the container scrolls vertically.

Column Templates

Column templates let you replace the default PropertyComponent cell rendering with custom markup for any column in the grid.

Declarative Syntax

Place <ColumnTemplate> elements inside the component's ChildContent. Each element targets one property by name.

<PropertyComponent Path="LineItems">
    <CollectionPropertyEdit>
        <ColumnTemplate Property="Status" Label="Order Status">
            <MudChip Color="@GetStatusColor(context.Get<string>())"
                     T="string"
                     Size="Size.Small">
                @context.Get<string>()
            </MudChip>
        </ColumnTemplate>
    </CollectionPropertyEdit>
</PropertyComponent>

The context variable inside a ColumnTemplate is a PropertyComponentContext scoped to that cell's row DataSet. Use context.Get<T>() to read the value and context.Set(value) to write it.

ColumnTemplate accepts the following attributes:

Attribute Type Required Description
Property string Yes Property name within the collection (e.g., "Status", "UnitPrice"). Case-insensitive.
Label string? No Overrides the column header label derived from metadata.
Width string? No Fixed CSS width for the column (e.g., "120px"). Applied as width, min-width, and max-width so the column does not resize.
ChildContent RenderFragment<PropertyComponentContext>? No The cell template body. Receives the row's PropertyComponentContext.

These correspond directly to the fields of the ColumnTemplateDefinition record:

public sealed record ColumnTemplateDefinition
{
    public RenderFragment<PropertyComponentContext>? CellTemplate { get; init; }
    public string? Label { get; init; }
    public string? Width { get; init; }
}

Dictionary Syntax

When column templates are defined outside the component tree (for example, computed in code-behind), pass them through the ColumnTemplates parameter:

<PropertyComponent Path="LineItems">
    <CollectionPropertyEdit ColumnTemplates="@_columnTemplates" />
</PropertyComponent>
private Dictionary<string, RenderFragment<PropertyComponentContext>> _columnTemplates = new()
{
    ["Status"] = context => @<MudChip Color="@GetStatusColor(context.Get<string>())"
                                       T="string" Size="Size.Small">
                                @context.Get<string>()
                            </MudChip>
};

Keys are property names (case-insensitive). When both ColumnTemplates and declarative ChildContent definitions are provided for the same property, they are merged; the declarative Label and Width attributes take effect even when the template body comes from the dictionary.

Row Actions

Row actions appear in the per-row action menu (the ellipsis button in the rightmost column of CollectionPropertyEdit). A built-in Delete action is always present. Custom actions are added before Delete.

CollectionRowActionDefinition has the following fields:

public sealed record CollectionRowActionDefinition
{
    public required string Label { get; init; }
    public string? Icon { get; init; }
    public required Func<IDataSet, Task> OnClick { get; init; }
}
Field Type Description
Label string Menu item text shown to the user.
Icon string? Optional SVG or icon markup. Use Lucide.<IconName>.Markup for consistency with the built-in icons.
OnClick Func<IDataSet, Task> Async callback invoked when the action is clicked. Receives the row's IDataSet.

Declarative Row Actions

Add row actions inside ChildContent using <CollectionRowAction>:

<PropertyComponent Path="LineItems">
    <CollectionPropertyEdit>
        <CollectionRowAction Label="Open Product"
                             Icon="@Lucide.ExternalLink.Markup"
                             OnClick="@(item => NavigateToProduct(item))" />
    </CollectionPropertyEdit>
</PropertyComponent>
private Task NavigateToProduct(IDataSet item)
{
    var productGuid = item.GetGuid("ProductGuid");
    Navigation.NavigateTo($"/products/{productGuid}");
    return Task.CompletedTask;
}

Multiple <CollectionRowAction> elements can be nested in the same ChildContent alongside <ColumnTemplate> elements. The order of declaration is the order they appear in the menu.

PropertyGroup-Driven Columns

When PropertyGroup is set, the resolver finds all groups with that name inside the graph definition (including nested groups) and collects their property paths. Only paths that begin with the collection's own path are included, and they are shown in group-declaration order.

<PropertyComponent Path="LineItems">
    <CollectionPropertyEdit PropertyGroup="LineItemSummary" />
</PropertyComponent>
private static GraphBuilder BuildGraph() => new GraphBuilder("Order")
    .Navigation("LineItems", nav => nav
        .Property("ProductName")
        .Property("Quantity")
        .Property("UnitPrice")
        .Property("Discount")
        .Property("Notes"))
    .Group("LineItemSummary", g => g
        .Property("LineItems.ProductName")
        .Property("LineItems.Quantity")
        .Property("LineItems.UnitPrice"));

With the above configuration, the collection grid shows only ProductName, Quantity, and UnitPrice in that order. If the group does not exist or contains no properties scoped to the collection path, all navigated properties are shown as a fallback.

This is the recommended way to control which columns appear in a collection grid and their display order, without writing column templates.

Add and Remove

CollectionPropertyEdit provides row-level add and remove operations backed by IDataSetCollection.

Adding a row — the ADD ROW button calls AddAsync() on the IDataSetCollection. This sends a POST request to the server to create a new child entity. Once the response is received, the new row appears at the bottom of the grid with an empty inline editor for each column. The ADD ROW button is disabled while a previous add request is in flight.

Removing a row — each row's action menu contains a Delete item. Clicking it calls RemoveAsync(guid) on the collection, which sends a DELETE request to the server. The row is removed from the grid once the server confirms deletion.

Both operations run through the normal IDataSetCollection pipeline, so server-side validation and events apply.

The ADD ROW button is disabled in the following situations:

  • A previous add or delete operation is still in flight (IsLoading is true).
  • The parent DataGraph has been placed in View mode.

Code Examples

1. Basic Collection (Auto-Columns)

No customization needed. PropertyComponent resolves the collection type and renders the grid with all navigated properties as columns.

<DataGraph Builder="@BuildGraph" DataSet="@_dataSet" EntityGuid="@_guid">
    <PropertyComponent Path="OrderDate" />
    <PropertyComponent Path="LineItems" />
</DataGraph>

2. Custom Column Template for a Status Property

Replace the default text cell with a colored chip.

<PropertyComponent Path="LineItems">
    <CollectionPropertyEdit>
        <ColumnTemplate Property="Status" Width="140px">
            <MudChip T="string"
                     Color="@GetStatusColor(context.Get<string>())"
                     Size="Size.Small"
                     Variant="Variant.Filled">
                @context.Get<string>()
            </MudChip>
        </ColumnTemplate>
    </CollectionPropertyEdit>
</PropertyComponent>
private Color GetStatusColor(string? status) => status switch
{
    "Fulfilled" => Color.Success,
    "Pending"   => Color.Warning,
    "Cancelled" => Color.Error,
    _           => Color.Default
};

3. Custom Row Action That Navigates to a Detail Page

<PropertyComponent Path="LineItems">
    <CollectionPropertyEdit>
        <CollectionRowAction Label="Open Line Item"
                             Icon="@Lucide.ExternalLink.Markup"
                             OnClick="@(item => OpenLineItem(item))" />
    </CollectionPropertyEdit>
</PropertyComponent>
@inject NavigationManager Navigation

private Task OpenLineItem(IDataSet item)
{
    var guid = item.GetGuid("Guid");
    Navigation.NavigateTo($"/line-items/{guid}");
    return Task.CompletedTask;
}

4. PropertyGroup-Driven Columns for a Narrow Column Set

Define the group in the graph and point the collection at it with PropertyGroup.

// Graph definition (factory for DataGraph Builder parameter)
private static GraphBuilder BuildGraph() => new GraphBuilder("Order")
    .Navigation("LineItems", nav => nav
        .Property("ProductName")
        .Property("Quantity")
        .Property("UnitPrice")
        .Property("Discount")
        .Property("FulfillmentNotes"))
    .Group("LineItemColumns", g => g
        .Property("LineItems.ProductName")
        .Property("LineItems.Quantity")
        .Property("LineItems.UnitPrice"));
<PropertyComponent Path="LineItems">
    <CollectionPropertyEdit PropertyGroup="LineItemColumns" />
</PropertyComponent>

Only ProductName, Quantity, and UnitPrice are shown as columns. Discount and FulfillmentNotes are excluded from the grid but remain part of the navigation and are accessible in row DataSets.


5. Collection with MaxHeight Scroll and Read-Only Mode

@* Edit mode with a scrolling container *@
<PropertyComponent Path="LineItems">
    <CollectionPropertyEdit MaxHeight="320px" />
</PropertyComponent>

@* View mode — CollectionPropertyView also supports MaxHeight *@
<DataGraph Builder="@BuildGraph" DataSet="@_dataSet" EntityGuid="@_guid" DisplayMode="DisplayMode.View">
    <PropertyComponent Path="LineItems">
        <CollectionPropertyView MaxHeight="50vh" />
    </PropertyComponent>
</DataGraph>

When MaxHeight is set, the grid container receives max-height: <value> as an inline style and the fluent-collection-constrained CSS class, enabling vertical overflow scrolling. The column headers remain sticky within the scroll area. Without MaxHeight, the container expands to fit all rows (fluent-collection-unconstrained).