Custom column template filtering by multiple fields that form it

Hi guys,
I have datagrid which contains columns that use Template in order to display mutlitple fields inside. One of the examples you can see on the screenshot below is for User column, where I display ID, full name and email.

What I'm trying to achieve is that filter works over all of those three fields, not only users name. I tried experimenting with FilterValueTemplate but I'm struggling to figure out how everything works and to basically have a custom method. It's fine if I have to create custom method logic, but I'm not sure how to define it.

I thought about using only simple filtering method, but it would be helpful for the user to have an option to exclude some results, like I did on the screenshot where I excluded system logs. So I'm trying to use this simple with menu filter.

Here is the screenshot of the UI

And here is my page code:

@page "/logs"
@attribute [Authorize]

@using Microsoft.AspNetCore.Authorization

@inject ApiService ApiService

<RadzenStack Orientation="Orientation.Horizontal" JustifyContent="JustifyContent.SpaceBetween" AlignItems="AlignItems.Center" class="rz-mb-5">
    <h3>Change Logs</h3>
</RadzenStack>

<RadzenCard Variant="Variant.Outlined">
    <RadzenStack Orientation="Orientation.Horizontal" JustifyContent="JustifyContent.SpaceBetween" AlignItems="AlignItems.Center" Gap="1.5rem;">
        <RadzenStack Orientation="Orientation.Horizontal" Style="flex: 1;">
            <RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.5rem;" Style="flex: 1; max-width: 400px;">
                <RadzenLabel Text="Gym:" Component="Gym"/>
                <RadzenDropDown Data="@gymsSearchFilterList" @bind-Value="@gymIdSearchFilter" TextProperty="Text" ValueProperty="Id" Name="Gym" Style="width: 100%;"
                                FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive" FilterOperator="StringFilterOperator.StartsWith" 
                                AllowFiltering="true" AllowClear="true" />
            </RadzenStack>
            <RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.5rem;">
                <RadzenLabel Text="Date from:" Component="DateFrom"/>
                <RadzenDatePicker @bind-Value="@dateFromSearchFilter" DateFormat="MM/dd/yyyy" Name="DateFrom"/>
            </RadzenStack>
            <RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.5rem;">
                <RadzenLabel Text="Date to:" Component="DateTo"/>
                <RadzenDatePicker @bind-Value="@dateToSearchFilter" DateFormat="MM/dd/yyyy" Name="DateTo"/>
            </RadzenStack>
        </RadzenStack>

        <RadzenButton Click="Search" Icon="search" IsBusy="loadingData" ButtonStyle="ButtonStyle.Primary" Text="Search"/>
    </RadzenStack>
</RadzenCard>

<RadzenDataGrid @ref="LogsGrid" IsLoading="loadingData" AllowFiltering="true" FilterMode="FilterMode.SimpleWithMenu"
                FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive" AllowPaging="true" PageSize="25"
                AllowSorting="true" AllowColumnResize="true" ShowPagingSummary="@showPagingSummary"
                PagingSummaryFormat="@pagingSummaryFormat" Data="@LogsList" TItem="EntityChangeLogDto"
                ExpandMode="DataGridExpandMode.Single">
    <Template Context="log">
        @if (log.Changes.Count > 0)
        {
            <RadzenDataGrid AllowFiltering="false" AllowPaging="false" AllowSorting="false" Data="log.Changes"
                            ColumnWidth="200px" Density="Density.Compact" AllowAlternatingRows="false" class="rz-m-5">
                <EmptyTemplate>
                    <p class="my-0 text-center rz-cell-data">No changes to display.</p>
                </EmptyTemplate>
                <Columns>
                    <RadzenDataGridColumn Property="PropertyName" Title="Property Name" />
                    <RadzenDataGridColumn Property="OldValue" Title="Old Value"/>
                    <RadzenDataGridColumn Property="NewValue" Title="New Value"/>
                </Columns>
            </RadzenDataGrid>
        }
    </Template>
    <Columns>
        <RadzenDataGridColumn Property="Action" Title="Action"/>
        <RadzenDataGridColumn Property="EntityName" Title="Entity Name"/>
        <RadzenDataGridColumn Property="EntityId" Title="Entity ID"/>
        <RadzenDataGridColumn Property="Timestamp" Title="Timestamp" FormatString="{0:dd. MMM yyyy. HH:mm:ss}" SortOrder="SortOrder.Descending"/>
        <RadzenDataGridColumn Property="UserId" Title="User">
            <Template Context="log">
                @if (log.UserId == null)
                {
                    <RadzenText TextStyle="TextStyle.Body1" class="rz-mb-0">@log.UserFullName</RadzenText>
                }
                else
                {
                    <RadzenStack Orientation="Orientation.Vertical" JustifyContent="JustifyContent.Center" Gap="0">
                        <RadzenText TextStyle="TextStyle.Body1" class="rz-mb-0">@($"{log.UserId} - {log.UserFullName}")</RadzenText>
                        <RadzenText TextStyle="TextStyle.Body2" class="rz-mb-0 rz-color-base-600">@($"Email: {log.UserEmail}")</RadzenText>
                    </RadzenStack>
                }
            </Template>
        </RadzenDataGridColumn>
        <RadzenDataGridColumn Property="GymName" Title="Gym">
            <Template Context="log">
                <RadzenText TextStyle="TextStyle.Body1" class="rz-mb-0">@($"{log.GymId} - {log.GymName}")</RadzenText>
            </Template>
        </RadzenDataGridColumn>
    </Columns>
</RadzenDataGrid>

@code {
    bool loadingData = false;
    bool showPagingSummary = true;
    string pagingSummaryFormat = "Page {0} of {1} <b>(total {2} records)</b>";
    
    IList<EntityChangeLogDto> LogsList;
    RadzenDataGrid<EntityChangeLogDto> LogsGrid;
    
    // Search filters
    long? gymIdSearchFilter;
    List<ListItem> gymsSearchFilterList;
    DateOnly dateFromSearchFilter;
    DateOnly dateToSearchFilter;
    
    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        
        dateFromSearchFilter = DateOnly.FromDateTime(DateTime.Now).AddDays(-3);
        dateToSearchFilter = DateOnly.FromDateTime(DateTime.Now);
        gymsSearchFilterList = await ApiService.GetDataAsync<List<ListItem>>("gyms/list");
    }
    
    private async Task Search()
    {
        loadingData = true;
        LogsList = await ApiService.GetLogs(gymIdSearchFilter, dateFromSearchFilter, dateToSearchFilter);
        loadingData = false;
    }
    
}

Filtering is performed at data level not with templates. You can use for example LoadData binding where you can create your own filtering using Where().

I'm not sure if LoadData would work for my cas where I'm calling an existing API, not doing loading data directly through Blazor/EF.

The thing is, that I'm not actually computing any additional data, I'm just trying to filter over multiple data fields at the same time with that one filter input/column. UserId, FullName and Email are all data properties, I could show them as separate columns, but visually this was clients choice.

Just to expand a bit more.
It's like on your example here, where you have Employee column, but filtering works only over surname. If you type the name of the employee, it's not displaying any results.

One of the things I managed to do in the experiment with simple filtering (without option of the menu), is via this below. But still that does not fully satisfy my need, as it's fixed to Contains method.

    async Task OnNameFilterValueChange(RadzenDataGridColumn<GetAllUsersResponse> column)
    {
        if (!string.IsNullOrEmpty(nameFilterValue))
        {
            var parsedFilterValue = EscapeSpecialCharacters(nameFilterValue.ToLowerInvariant());
            nameFilterWhereExpression = $"FullName.ToLowerInvariant().Contains(\"{parsedFilterValue}\") or Email.ToLowerInvariant().Contains(\"{parsedFilterValue}\")";
        }
        else
        {
            nameFilterWhereExpression = "";
        }

        await column.SetCustomFilterExpressionAsync(nameFilterWhereExpression);
    }

It does not matter from where your data are loaded. If you are using for example service that supports filtering you can pass desired filter:

Or you can use IQueryable filtering similar to this demo:

Or you can simply extend your model with additional property that concatenates values from other properties and use the new property in a column.

Thank you for such a quick responses. I think I'll go with the option of adding new columns to my DTO will represent concatendated strings and filter by them, as that seems like the easiest option.

I wanted to learn what are the possibilites that I might missed from the documentation, as I thought there is an option for this case, especially when I saw the FilterValueTemplate.