Initial Filter on Related Entity Property in OData not accepted

When trying to apply an initial filter to a related entity property using Radzen DataGrid with OData, the filter created in the url looks like

... and (it => it.OrderDetail.Product.ProductID == 11)

  1. When we attempt to set an initial filter on a related property (e.g. OrderDetail.Product.ProductID), the generated OData query appears to be malformed. It seems like the query builder is generating a lambda-style expression, which is not valid or accepted by standard OData servers.

  2. We are trying to create a minimal reproducible example using the Northwind demo included with Radzen, but the issue is that we are unable to set filters on related entity properties like Product.ProductID. This limitation is preventing us from replicating the exact problem using only the demo project.

Would it be possible to allow filters on related entity properties (e.g. Product.ProductID) within the Radzen demo or provide an example on how to properly configure this?

This would help us confirm whether the issue is with our usage or a bug in the query generation.

We post the example we're trying to create for demo page debug

  1. Go to Blazor DataGrid Component - OData Service | Free UI Components by Radzen
  2. In Edit source paste and run:
@using RadzenBlazorDemos.Data
@using RadzenBlazorDemos.Models.Northwind

<RadzenDataGrid @ref="grid"
                KeyProperty="OrderID"
                IsLoading="@isLoading"
                Count="@count"
                Data="@oderDetails"
                LoadData="@LoadData"
                FilterPopupRenderMode="PopupRenderMode.OnDemand"
                FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive"
                FilterMode="FilterMode.CheckBoxList"
                AllowSorting="true"
                AllowFiltering="true"
                AllowPaging="true"
                PageSize="4"
                PagerHorizontalAlign="HorizontalAlign.Center"
                ColumnWidth="200px">
    <Columns>
        <RadzenDataGridColumn Property="@nameof(OrderDetail.OrderID)"
                              Title="OrderID" />

        <RadzenDataGridColumn Property="OrderDetail.Product.ProductID"
                              Title="Product Id" />
    </Columns>
</RadzenDataGrid>

@code {
    bool isLoading;
    int count;
    ODataEnumerable<OrderDetail> oderDetails = default!;
    RadzenDataGrid<OrderDetail> grid;


    IEnumerable<int> filteredIds;

    NorthwindODataService service = new NorthwindODataService("https://services.radzen.com/odata/Northwind/");

    async Task LoadData(LoadDataArgs args)
    {
        isLoading = true;

        var result = await service.GetOrderDetails(
            filter: args.Filter, 
            top: args.Top, 
            skip: args.Skip, 
            orderby: args.OrderBy, 
            expand: "Product",
            count: true
        );


        oderDetails = result.Value.AsODataEnumerable();

        count = result.Count;

        isLoading = false;
    }

    protected override Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            // It's just an example, we could filter directly ProductID in OrderDetail, but we're
            // trying to go deeper in the relational entities tree.
            var column = grid.ColumnsCollection.Where(c => c.Property == "OrderDetail.Product.ProductID").FirstOrDefault();

            if (column != null)
            {
                // Setting an initial filter.
                // Expecting in datagrid appearing only rows for product with id 11.
                var initialFilterProductId = 11;
                column.SetFilterValue(initialFilterProductId);
                column.SetFilterOperator(FilterOperator.Equals);

                grid.Reload();
            }
        }

        return base.OnAfterRenderAsync(firstRender);
    }
}

Thank you.

To access sub properties in OData you should use / instead .. Check OData specification for reference.

Thank you enchev for your fast reply.

I've tried updating the filter property to use / instead of . as per the OData specification (e.g., Product/ProductID), but the generated filter still ends up as:

... and (it => it.OrderDetail/Product/ProductID == 11)

Not sure what is creating this lambda-style filter in OData filter query param, which causes issues with OData request.

Any guidance or example on configuring this properly would be greatly appreciated.

Check this example:

Yes, thank you. The example works for direct properties. However, the key difference is when trying to filter on a related entity property. In that case, the generated filter still uses the it => it... syntax, which causes the issue in the request.

If anyone finds the same issue, the fix is in this line:

private ODataEnumerable<OrderDetail> data = Enumerable.Empty<OrderDetail>().AsODataEnumerable();

No, the code works normally no matter if you are filtering by sub property or not:

@using RadzenBlazorDemos.Data
@using RadzenBlazorDemos.Models.Northwind

<RadzenDataGrid @ref="grid"
                IsLoading="@isLoading"
                Count="@count"
                Data="@data"
                LoadData="@LoadData"
                AllowSorting="true"
                AllowFiltering="true"
                AllowPaging="true">
    <Columns>
        <RadzenDataGridColumn Property="Product.ProductID" Title="ProductID" />
    </Columns>
</RadzenDataGrid>

@code {
    bool isLoading;
    int count;
    ODataEnumerable<OrderDetail> data = Enumerable.Empty<OrderDetail>().AsODataEnumerable();
    RadzenDataGrid<OrderDetail> grid;

    NorthwindODataService service = new NorthwindODataService("https://services.radzen.com/odata/Northwind/");

    async Task LoadData(LoadDataArgs args)
    {
        isLoading = true;

        var result = await service.GetOrderDetails(filter: args.Filter, top: args.Top, skip: args.Skip, orderby: args.OrderBy, count: true);

        data = result.Value.AsODataEnumerable();

        count = result.Count;

        isLoading = false;
    }

    protected override Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            var column = grid.ColumnsCollection.Where(c => c.Property == "Product.ProductID").FirstOrDefault();

            if (column != null)
            {
                column.SetFilterValue(1);
                grid.Reload();
            }
        }

        return base.OnAfterRenderAsync(firstRender);
    }
}

1 Like