Querybuilder with dynamic QueryBuilderColumns

I'm trying to use the QueryBuilder component with a dynamic set of columns that are queried from a database. The columns are unknown at compile time. IOTs easy enough to add the columns to the QueryBuilder but what I'm unsure about is what to set the TValue to as columns will be of differing data types? One thought is to use an ExpandoObject. Is ExpandoObject supported by the QueryBuilder?

<SfQueryBuilder TValue=???? @ref="Querybuilder">
                                    <QueryBuilderColumns>
                                        @foreach (DataGridColumn item in DataGridColumns)
                                        {
                                            <QueryBuilderColumn Field="@item.FieldName" Label="@item.HeaderString" [email protected]/>
                                        }
                                    </QueryBuilderColumns>
</SfQueryBuilder>

25 Replies

MA Michael Aston December 4, 2020 05:05 PM UTC

Ignore this. Figured it out now.


MK Mohan Kumar Ramasamy Syncfusion Team December 7, 2020 04:22 PM UTC

Hi Michael, 
 
We are happy to hear that you have found the solution by yourself. Please get back to us if you need further assistance. 
 
Regards, 
Mohan kumar R 



DF Drew Frisk June 7, 2021 02:08 PM UTC

Michael Aston I am trying to do this same thing -- using an ExpandoObject for dynamic columns in a query builder. What was the solution you found to make this work?


GK Gayathri KarunaiAnandam Syncfusion Team June 9, 2021 02:42 AM UTC

Hi Drew, 

We have checked your reported query. We have prepared a sample based on your requirement by using ExpandoObject. Please check the below code snippet and sample. 

<div class="col-lg-12 control-section"> 
    <SfQueryBuilder TValue="ExpandoObject" DataSource="@Orders"> 
        <QueryBuilderColumns> 
            <QueryBuilderColumn Field="OrderID" Label="OrderID" Type=Syncfusion.Blazor.QueryBuilder.ColumnType.Number></QueryBuilderColumn> 
            <QueryBuilderColumn Field="CustomerID" Label="CustomerID" Type=Syncfusion.Blazor.QueryBuilder.ColumnType.String></QueryBuilderColumn> 
            
        </QueryBuilderColumns> 
    </SfQueryBuilder> 
</div> 
 
@code{ 
    public List<ExpandoObject> Orders { get; set; } = new List<ExpandoObject>(); 
 
    protected override void OnInitialized() 
    { 
        Orders = Enumerable.Range(1, 75).Select((x) => 
        { 
            dynamic d = new ExpandoObject(); 
            d.OrderID = 1000 + x; 
            d.CustomerID = (new string[] { "ALFKI", "ANANTR", "ANTON", "BLONP", "BOLID" })[new Random().Next(5)]; 
            d.Freight = (new double[] { 2, 1, 4, 5, 3 })[new Random().Next(5)] * x; 
            d.OrderDate = (new DateTime[] { new DateTime(2010, 11, 5), new DateTime(2018, 10, 3), new DateTime(1995, 9, 9), new DateTime(2012, 8, 2), new DateTime(2015, 4, 11) })[new Random().Next(5)]; 
            d.ShipCountry = (new string[] { "USA", "UK" })[new Random().Next(2)]; 
            d.Verified = (new bool[] { true, false })[new Random().Next(2)]; 
 
            return d; 
        }).Cast<ExpandoObject>().ToList<ExpandoObject>(); 
 
    } 
} 


Please get back to us, if you need further assistance. 

Regards, 
Gayathri K 



HA HappyCamper July 28, 2021 01:45 AM UTC

Gayathri ,


Your sample does not address the question. The question was how to create dynamic columns... not hard code them.

The below would typically the way to do this now that the referenced columns propert is deprecated.


<SfQueryBuilder TValue=???? @ref="Querybuilder">
                                    <QueryBuilderColumns>
                                        @foreach (DataGridColumn item in DataGridColumns)
                                        {
                                            <QueryBuilderColumn Field="@item.FieldName" Label="@item.HeaderString" [email protected]/>
                                        }
                                    </QueryBuilderColumns>
</SfQueryBuilder>


However, using the foreach method does not allow the Operators parameter to be used. If you have a requirement to be able to specify custom operators per column (IE... potentially, every column has its own set of operators allowed).... how would you build columns dynamicly from a list.

Again, not like your sample above, those columns are hardcoded. The intent is to build columns dynamicly from a list, as perexample above.





GK Gayathri KarunaiAnandam Syncfusion Team July 28, 2021 04:32 PM UTC

Hi HappyCamper, 

We have checked your reported query. We have prepared a sample in which columns are created dynamically based on your requirement. We can add custom Operators to Querybuilder. Please check the below code snippet. 

 
<SfQueryBuilder TValue="ExpandoObject">  
    <QueryBuilderColumns>  
    @{   
        foreach(var column in columns)  
        {  
            <QueryBuilderColumn Field="@column.Field" Label="@column.Label" Type="@column.Type" Operators="@column.Operators"></QueryBuilderColumn>  
        }  
    }  
    </QueryBuilderColumns>  
</SfQueryBuilder>  
  
@code { 
    private List<QueryBuilderColumn> columns = new();  
    private static string[] Values = new string[] { "Mr.", "Mrs." };  
 
     public List<OperatorsModel> CustomOperators = new List<OperatorsModel> { 
        new OperatorsModel { Text="Equal", Value="equal"}, 
        new OperatorsModel { Text="Not equal", Value="notequal"} 
    }; 
 
    protected override async Task OnInitializedAsync()  
  
    {  
        columns = new List<QueryBuilderColumn>()  
    {  
        new QueryBuilderColumn(){ Field="EmployeeID", Label="Employee ID", Type=ColumnType.Number, Operators=CustomOperators},  
        new QueryBuilderColumn(){ Field="FirstName", Label="First Name", Type=ColumnType.String, Operators=CustomOperators},  
        new QueryBuilderColumn(){ Field="TitleOfCourtesy", Label="Title of Courtesy", Type=ColumnType.Boolean, Values=Values },  
  
    };  
        await base.OnInitializedAsync();  
    } 
 
} 
 
 
For your convenience, we have prepared a sample based on this. Please check the sample. 


Please get back to us, if you need further assistance. 

Regards, 
Gayathri K 



CD Clarence Dollesin August 19, 2021 12:52 PM UTC

I want to add that while we are able to add columns dynamically, in the event that we want to replace the columns and call StateHasChanged(), the columns are not updated, it still has the previous columns.

To describe the scenario, we have loaded QueryBuilder and dynamically defined the columns like the example above. Upon triggering an event (button click for example), we have a new list of columns that we wanted to load in the QueryBuilder but upon trying so, it did not show us the new columns we needed.

Can you provide a sample that updates the dynamic columns by triggering StateHasChanged()?



GK Gayathri KarunaiAnandam Syncfusion Team August 20, 2021 07:48 AM UTC

Hi Clarence Dollesin, 

We have checked your reported query. We can Dynamically change the columns in Querybuilder. We have prepared a sample in which in button click I have rendered a new columns. Please check the below code snippet. 

<SfQueryBuilder TValue="ExpandoObject" @ref="qrbobj">   
    <QueryBuilderColumns>   
    @{    
        foreach(var column in columns)   
        {   
            <QueryBuilderColumn Field="@column.Field" Label="@column.Label" Type="@column.Type" Operators="@column.Operators"></QueryBuilderColumn>   
        }   
    }   
    </QueryBuilderColumns>   
</SfQueryBuilder>   
<SfButton OnClick="click">Column Change</SfButton> 
 
   
@code { 
    SfQueryBuilder<ExpandoObject> qrbobj; 
    private List<QueryBuilderColumn> columns = new();   
     private List<QueryBuilderColumn> DynamicColumns = new();   
    private static string[] Values = new string[] { "Mr.", "Mrs." };   
  
     public List<OperatorsModel> CustomOperators = new List<OperatorsModel> {  
        new OperatorsModel { Text="Equal", Value="equal"},  
        new OperatorsModel { Text="Not equal", Value="notequal"}  
    };  
  
    protected override async Task OnInitializedAsync()   
   
    {   
        columns = new List<QueryBuilderColumn>()   
    {   
        new QueryBuilderColumn(){ Field="EmployeeID", Label="Employee ID", Type=ColumnType.Number, Operators=CustomOperators},   
        new QueryBuilderColumn(){ Field="FirstName", Label="First Name", Type=ColumnType.String, Operators=CustomOperators},   
        new QueryBuilderColumn(){ Field="TitleOfCourtesy", Label="Title of Courtesy", Type=ColumnType.Boolean, Values=Values },   
   
    };   
     DynamicColumns = new List<QueryBuilderColumn>()   
    {   
        new QueryBuilderColumn(){ Field="EmployeeID1", Label="Employee ID1", Type=ColumnType.Number},   
        new QueryBuilderColumn(){ Field="FirstName1", Label="First Name1", Type=ColumnType.String},   
        new QueryBuilderColumn(){ Field="TitleOfCourtesy1", Label="Title of Courtesy1", Type=ColumnType.Boolean, Values=Values },   
   
    };   
        await base.OnInitializedAsync();   
    }  
    public void click() 
    { 
        qrbobj.Reset(); 
        columns = DynamicColumns; 
    } 
 
} 

For your convenience, we have prepared a sample based on this. Please check the below link. 


Please get back to us, if you need further assistance. 

Regards, 
Gayathri K 



CD Clarence Dollesin August 22, 2021 01:11 PM UTC

Hi  Gayathri K,


We have extended your sample code to give you the issue we are having.

Your given solution above works only if the number of columns are always the same (in the case above, 3 initially then 3 again when updated).

Please refer to the attached source code for our scenario. We are using the query builder inside a modal. We load the columns dynamically when we open the modal. Every time we open the modal, we already have the new set of column definitions we want to be loaded in the query builder. 

From the extended sample, you can replicate it by clicking on [Open1] button, check the dropdown (there are 3 expected values as defined from code). Then close the modal and click on [Open2] button, check the dropdown (there should only be 1 value from dropdown as defined from code), you will see that the previous values are not removed and still there from the previous column definition values.


Thank you.

Clarence


Attachment: Sample_d89ab75d.zip


GK Gayathri KarunaiAnandam Syncfusion Team August 24, 2021 03:41 AM UTC

Hi Clarence Dollesin, 

We have checked your reported query. We need to validate more on this. So, we will provide further details on August 24th, 2021. We appreciate your patience until then. 

Regards, 
Gayathri K 



GK Gayathri KarunaiAnandam Syncfusion Team August 24, 2021 07:49 AM UTC

Hi Clarence , 

We have validated your query. We can reproduce your reported issue in our end. So, we have logged this issue as a bug, and it will be available in our upcoming patch release which will be scheduled on September 8th  ,2021 and we appreciate your patience until then. 

You can also track the status of this bug by using our feedback portal link below.   
   

Please get back to us, if you need further assistance. 

Regards, 
Gayathri K 



CD Clarence Dollesin September 9, 2021 06:48 AM UTC

Hi,


Was the issue fixed now in current release?


Thank you.

Clarence



GK Gayathri KarunaiAnandam Syncfusion Team September 12, 2021 02:28 PM UTC

Hi Clarence, 

Sorry for the inconvenience caused. 

We are facing complexity and need to ensure more cases. So, we will include this fix in our Volume 3 main release  which will be scheduled on 24th September 2021 patch release. We appreciate your patience until then.  
  
Regards,  
Gayathri K 



CD Clarence Dollesin September 29, 2021 11:07 AM UTC

Hi,


Was this released?


Thank you.

Clarence



GK Gayathri KarunaiAnandam Syncfusion Team October 1, 2021 03:42 AM UTC

Hi Clarence , 
  
We are glad to announce that our Essential Studio 2021 Volume 3 release v19.3.0.43  is rolled out and is available for download under the following link. 
  


We thank you for your support and appreciate your patience in waiting for this release. Please get in touch with us if you would require any further assistance. 
  
Regards,           
Gayathri K 



SA Simon Arnold December 7, 2022 08:42 AM UTC

This is all good, but what I want to do is pull the list of fields and for the QueryBuilderColumns from a table, the problem I am currently having is that I cannot set the QueryBuilderColumn, as it tells me that the parameter Field, Label, Type should not be set outside of its component, is it possible to do this, or do I have to hard code the columns and types, if so this makes it difficult to make a re-usable component for my s


@if (QBFields != null) {
    <SfQueryBuilder @ref="AppQBuilder" TValue="ExpandoObject">
        <QueryBuilderColumns>
            @foreach (var line in QBFields) {
                <QueryBuilderColumn Field="@line.Field" Label="@line.Label" Type="@line.Type" />
            }
        </QueryBuilderColumns>
    </SfQueryBuilder>
}
@code {
    private SfQueryBuilder<ExpandoObject> AppQBuilder;
    public List<QueryBuilderColumn> QBFields { get; set; } = new();


 protected override async Task OnInitializedAsync() {
        await GetFieldInfo();
    }


    private async Task GetFieldInfo() {
        try {
            var apiResponse = await httpClient.GetFromJsonAsync<ApiResponseDto<List<ColumnTypeInfo>>>(BaseURLs.GET_APPLICANT_FIELDS);
            if (apiResponse.IsSuccessStatusCode) {
                var fields = apiResponse.Result;
                var newFields = new List<QueryBuilderColumn>();
                foreach(var f in fields) {
                    newFields.Add(new QueryBuilderColumn() {
                            Field = f.Name, // These give me the warnings, but cause a problem
                            Label = f.Label,
                            Type = GetType(f.Type)
                        });
                }
                QBFields = newFields;
            } else {
                await swal.FireAsync("Info", apiResponse.Message, SweetAlertIcon.Info);
            }
        } catch (Exception ex) {
            await swal.FireAsync("Error", ex.Message, SweetAlertIcon.Error);
        }
    }


    private Syncfusion.Blazor.QueryBuilder.ColumnType GetType(string type) {
        if (type.Contains("number")) return Syncfusion.Blazor.QueryBuilder.ColumnType.Number;
        if (type.Contains("date")) return Syncfusion.Blazor.QueryBuilder.ColumnType.Date;
        if (type.Contains("string")) return Syncfusion.Blazor.QueryBuilder.ColumnType.String;
        if (type.Contains("bool")) return Syncfusion.Blazor.QueryBuilder.ColumnType.Boolean;
        return Syncfusion.Blazor.QueryBuilder.ColumnType.Object;
    }
}


YA YuvanShankar Arunagiri Syncfusion Team December 14, 2022 06:27 AM UTC

Hi Simon,


After fetching the data from the table, we can set the query builder columns as shown in the below code snippet.

@page "/customquerybuilder"

 

@using Newtonsoft.Json

@using Syncfusion.Blazor.QueryBuilder

@using QbServerAPI.Data

@using QbServerAPI.Shared

@using System.Dynamic

 

@if (dataLoaded)

{

    <div id="container">

        <SfQueryBuilder TValue="ExpandoObject">

            <QueryBuilderColumns>              

                @{

                foreach (CustomColumn col in columns)

                    {

                        ColumnType type = ColumnType.String;

                        switch (col.Type)

                        {

                            case "Boolean":

                                type = ColumnType.Boolean;

                                break;

                            case "Number":

                                type = ColumnType.Number;

                                break;

                            case "Date":

                                type = ColumnType.Date;

                                break;

                        }

                        <QueryBuilderColumn Field="@col.Id" Label="@col.Label" Type="@type"></QueryBuilderColumn>

                    }

                }

            </QueryBuilderColumns>

        </SfQueryBuilder>

    </div>

}

 

@code

{

    public List<CustomColumn> columns { get; set; } = new List<CustomColumn>();

    public bool dataLoaded = false;

 

    protected override async Task OnInitializedAsync()

    {

        using (var httpClient = new HttpClient())

        {

            using (var response = await httpClient.GetAsync("https://localhost:7016/api/querybuilder/"))

            {

                string apiResponse = await response.Content.ReadAsStringAsync();

                columns =  JsonConvert.DeserializeObject<List<CustomColumn>>(apiResponse);

                dataLoaded = true;

                StateHasChanged();

            }

        }

    }

}

 

 


Please get back to us if you need any further assistance on this. 


Regards,

YuvanShankar A



JM Joseph Murray June 22, 2023 08:18 PM UTC

Hi Support,


I'm also using a DynamicDictionary to accomplish a very similar thing as the posters above (dynamically building the QueryBuilderColumns).  The main difference is that I have a custom component with both a SfGrid and SfQueryBuilder but I'm using a parameter for the Table name I'm querying against.  


In this situation I cannot use OnInitializedAsync() to reset the columns as it has already been run, but rather in the OnParametersSetAsync().  Is that too late to set the columns?  When I perform that task there I cannot get the query builder columns to update after the first time it's set.  


I've tried resetting the QueryBuilder and resetting the Rules but nothing works.  

I know we're pushing the boundaries for this component, and possibly Blazor itself.  If it is impossible, don't be shy to tell me to use something else.

Razor class:
```

<div id="target" class="col-lg-12 control-section">
@if (dataLoaded)
{
    <div class="row">
        <div class="col">
            <SfQueryBuilder @ref="QueryBuilder" TValue="DynamicDictionary" >
                <SfDataManager @ref="sfDataManager" AdaptorInstance="@typeof(KdbTableAdapter)" Adaptor="Adaptors.CustomAdaptor" />
                @* <QueryBuilderRule Condition="and" Rules="@importRules"></QueryBuilderRule> *@
                <QueryBuilderEvents TValue="DynamicDictionary" RuleChanged="UpdateRule"></QueryBuilderEvents>
                <QueryBuilderColumns >
                    @if (TableMetadata.Length() > 0)
                    {
                        foreach (var columnMetadata in TableMetadata)
                        {
                            var columnType = Syncfusion.Blazor.QueryBuilder.ColumnType.String;
                            switch (columnMetadata.Value)
                            {
                                case "System.Double" :
                                case "System.Int32" :
                                case "System.Int16" :
                                case "System.Int64" :
                                    columnType = Syncfusion.Blazor.QueryBuilder.ColumnType.Number;
                                    break;
                                case "System.DateTime" :
                                    columnType = Syncfusion.Blazor.QueryBuilder.ColumnType.Date;
                                    break;
                                case "System.String" :
                                    columnType = Syncfusion.Blazor.QueryBuilder.ColumnType.String;
                                    break;
                                default:
                                    break;
                            }
                        <QueryBuilderColumn Field="@columnMetadata.Key" Label="@columnMetadata.Key" Type="@columnType" ></QueryBuilderColumn>
                        }
                    }
                </QueryBuilderColumns>
            </SfQueryBuilder>
        </div>
    </div>
}
    <div class="row">
        <div class="col">
            <span class="e-control e-float-line e-test-class">@Label</span>  
             <SfGrid @ref="SfKdbTableGrid" ID="Grid" TValue="DynamicDictionary" Query=@GridQuery AllowPaging="true" AllowResizing="true"
                AllowSorting="true" ShowColumnChooser="true" Toolbar="@(new List<string>() { "CsvExport", "ColumnChooser" })" Columns="@AllColumns" >
                <GridFilterSettings Type=Syncfusion.Blazor.Grids.FilterType.Excel></GridFilterSettings>
                <GridEvents TValue="DynamicDictionary" OnToolbarClick="ToolbarClick" OnActionBegin="ActionHandler" />
                <SfDataManager @ref="sfDataManager" AdaptorInstance="@typeof(KdbTableAdapter)" Adaptor="Adaptors.CustomAdaptor" />
                <GridPageSettings PageSize="@PageSize" PageSizes="true" />
            </SfGrid>
        </div>
    </div>
</div>


Code behind:
```


    protected async Task ToolbarClick(Syncfusion.Blazor.Navigations.ClickEventArgs args)
    {
        if (args.Item.Id == "Grid_csvexport")
        {
            await SaveCsv();
            return;
        }

        if (args.Item.Id == "Grid_columnchooser")
        {
            // defaultGrid.ShowColumnChooser();
            return;
        }
    }
    async Task SaveCsv()
    {
       
        // var columns = await SfKdbTableGrid.GetVisibleColumnsAsync();
        // var columnNames = columns.Select(c => $"`{c.Field}").Aggregate((a, b) => $"{a} {b}");
        string randomfilename = Path.GetRandomFileName();
        var path = KdbService.SaveTempCsvFile(randomfilename, TableName);
        var fileData = await File.ReadAllBytesAsync(path);
        var outputFileName = TableName.Split(".", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).Last();
        await JSRuntime.InvokeVoidAsync("BlazorDownloadFile", outputFileName, "text/csv", fileData);
        KdbService.Delete(randomfilename);
    }

    void ActionHandler(ActionEventArgs<DynamicDictionary> args)
    {
        if (args.RequestType.Equals(Syncfusion.Blazor.Grids.Action.Sorting) || args.RequestType.Equals(Syncfusion.Blazor.Grids.Action.FilterSearchBegin))
        {
            if (QueryBuilder.GetValidRules().Rules.Count > 0)
            {
                var whereFilter = QueryBuilder.GetPredicate();
                GridQuery =  new Query().From(TableName).Where(whereFilter);
                return;
            }
            GridQuery =  new Query().From(TableName);
        }
    }
    private void UpdateRule(Syncfusion.Blazor.QueryBuilder.RuleChangeEventArgs args)
    {  
        if (QueryBuilder.GetValidRules().Rules.Count > 0)
        {
            var whereFilter = QueryBuilder.GetPredicate();
            GridQuery =  new Query().From(TableName).Where(whereFilter);
            return;
        }
       
        GridQuery =  new Query().From(TableName);

    }

}

```


Thanks,


Joe




YA YuvanShankar Arunagiri Syncfusion Team June 26, 2023 01:24 PM UTC

Hi Joseph,


We have prepared the sample based on your requirement (OnParameterAsync method with local data). Refer to the below attached sample. If you still facing the issue, please share the issue replicable issue or replicate the issue in our sample. Based on that, we will check and provide better solution quickly.


Regards,

YuvanShankar A


Attachment: ContextMenuSample_25cc7429.zip


JM Joseph Murray June 26, 2023 08:29 PM UTC

Hi Support,

Thanks for your reply.  I'm still experiencing the same issue.  Let me clarify what is happening.  I have a `SfTreeView` displaying a list of tables.  When I select a tablename in the tree, it updates the 'tablename' parameter in the component that contains the `SfQueryBuilder` and `SfGrid`.  

Once that changes I reload the columns that are available to the QueryBuilder in that OnParametersSet method.  


The problem is that the QueryBuilder loads with a default unpopulated Rule that has the list of the columns set the first time it's loaded.   On subsequent table selection changes, that default rule never changes and for some reason I cannot delete it in code.  I can manually delete it on the screen, create a new condition, and that will have the updated columns to select from.  

So as a workaround, is there a way to remove the default condition on a table selection change?  Should I do some kind of Navigation from the treeview instead so it completely resets the page?  Do I need to look into using the Javascript version of this control instead? 

Thanks,


Joe




JM Joseph Murray replied to Joseph Murray June 26, 2023 09:06 PM UTC

I was able to get it working by tricking the UI to disable and reenable the panel with the SfGrid and SfQueryBuilder.  Thanks for your help with this item.



YA YuvanShankar Arunagiri Syncfusion Team June 28, 2023 02:21 PM UTC

You're welcome, Joseph. We are glad to hear that your issue has been resolved now. Please get back to us if you need any other assistance.



UN Unknown August 22, 2023 02:27 PM UTC

Hi,


the example where you change the column on click doesnt work with newer versions - could you provide an updated version ?

We strugle to find a way to update the columns after the initial render



KV Keerthikaran Venkatachalam Syncfusion Team August 23, 2023 04:10 PM UTC

Hi Christoph,


We have confirmed the issue as "Dynamically change the columns in Querybuilder was not working" We logged a defect report. We will include the fix for this issue in the upcoming weekly patch release scheduled for September 6, 2023


You will be informed regarding this once the fix is published.


Disclaimer: "Inclusion of this solution in the weekly release may change due to other factors, including but not limited to QA checks and work reprioritization."


Regards,

KeerthiKaran K V



KV Keerthikaran Venkatachalam Syncfusion Team October 17, 2023 04:42 PM UTC

Hi Christoph,


We are glad to announce that our weekly patch release (23.1.41) has been rolled out. We have included the fix for this “Dynamically change the columns in Querybuilder was not working” issue in this release. So, we suggest you upgrade our Syncfusion packages to our latest version to resolve this issue on your end (23.1.41).


Package link: https://www.nuget.org/packages/Syncfusion.Blazor.QueryBuilder


Get back to us if you need any further assistance on this.


Regards,

KeerthiKaran K V


Loader.
Up arrow icon