How to filter a collection property using odata


Hello,

I've a Grid where I'm using an ODataV4Adaptor for the data-manager, I'm using remote data binding and in my Odata controller I have something like:

[EnableQuery]
[HttpGet]
[ODataRoute]
public IQueryable<Action> Get()
{
var data = this._ctx.Action
.Include(st => st.Status)
.Include(evUsr => evUsr.ActionUsers).ThenInclude(usr => usr.User)
.AsQueryable();

return data;
}

And my grid has something similar to this:

<e-grid-column field="StartDt"></e-grid-column>
<e-grid-column field="Status.Name" template="#templateStatus"></e-grid-column>
<e-grid-column field="ActionUsers.User.FullName" template="#templateActionUsers"></e-grid-column>

As you can see, Im using template columns for the Status (complex type, 1-1 relation) and the ActionUsers (many to many relation). My object models re similar to these ones:

public class Action
{
public int Id { get; set; }
public DateTimeOffset? StartDt { get; set; }
public ActionStatus Status { get; set; }
public ICollection<ActionUsers> ActionUsers { get; set; }
}

public class ActionStatus
{
public int Id { get; set; } public string Name { get; set; }
}

public class ActionUsers
{
public int ActionId { get; set; } public int UserId { get; set; } public User User { get; set; }
}

public class User
{
public int Id { get; set; } public string FullName { get; set; }
}

If you see, the attribute "ActionUsers" of the "Action" object is a collection, and everything is working well excepty by that column. When I filter by that column, the $filter in the odata query should be like this:

$filter=ActionUsers/all(c:contains(tolower(c/User/FullName),%27input value here%27))

Is there a way to specify in the e-grid-column that this one is a collection? How can I achieve this?

The second column (Status.Name) is a complex type, but a single object, and it works well, but the third column (ActionUsers.User.FullName) is a complex type but also a collection.

Thanks!

9 Replies

AG Ajith Govarthan Syncfusion Team November 30, 2020 12:18 PM UTC

Hi Rony, 
 
Thanks for contacting Syncfusion support. 
 
Query: If you see, the attribute "ActionUsers" of the "Action" object is a collection, and everything is working well excepty by that column. When I filter by that column, the $filter in the odata query should be like this:$filter=ActionUsers/all(c:contains(tolower(c/User/FullName),%27input value here%27))Is there a way to specify in the e-grid-column that this one is a collection? How can I achieve this? 
 
Based on your query we suspect that you are facing issues while filtering the multi-level complex field column and you also mentioned that you have used the collection for the FullName field. So, before we procced to your query please share the below details validate further on your requirement. 
 
  1. Share the complete grid code example.
 
  1. Share type of the filter used in your grid application
 
  1. Share the screenshot or video demonstration of the issue.
 
  1. Share the datasource bind to the grid component.
 
  1. Share the Syncfusion package version.
 
Regards, 
Ajith G. 



RD Rony De Sousa November 30, 2020 03:31 PM UTC


Hi Ajith,

Correct, I am facing issues while filtering the multi-level complex field column (ActionUsers is a Collection of a Complect Type: User, which is a complex type too, and FullName is a string attribute of User).

  1. Share the complete grid code example.
@{
List<object> sortCols = new List<object>();
sortCols.Add(new { field = "StartDt", direction = "Descending" });
}
<ejs-grid id="MainGrid" actionBegin="onActionBeginMainGrid" actionComplete="onActionCompleteMainGrid" allowPaging="true" showColumnChooser="true" allowSorting="true" allowFiltering="true" allowExcelExport="true" allowPdfExport="true" allowTextWrap="true" toolbarClick="toolbarClick" toolbar="@(new List<string>() { "Add", "ExcelExport", "PdfExport", "Search", "ColumnChooser" })">
<e-grid-filterSettings type="Menu"></e-grid-filterSettings>
<e-grid-editSettings allowAdding="true" allowDeleting="false" allowEditing="false"></e-grid-editSettings>
<e-grid-pagesettings pageSize="50" pageCount="5"></e-grid-pagesettings>
<e-data-manager url="https://localhost:4455/myodata/Actions?$expand=ActionUsers($expand=User($select=Id,FullName)),Status" adaptor="ODataV4Adaptor" crossdomain="true"></e-data-manager>
<e-grid-sortsettings columns="sortCols"></e-grid-sortsettings>
<e-grid-columns>
<e-grid-column field="Id" isPrimaryKey="true" visible="false"></e-grid-column>
<e-grid-column field="StartDt" headerText="Start"></e-grid-column>
<e-grid-column headerText="Action Name" field="ActionName"></e-grid-column>
<e-grid-column headerText="Status" field="Status.Name" template="#templateStatus" filter="@(new { type="CheckBox" })"></e-grid-column>
<e-grid-column field="ActionUsers.User.FullName" template="#templateActionUsers"></e-grid-column>
</e-grid-columns>
</ejs-grid>

<script id="templateActionUsers" type="text/x-template">
console.log('this code works without issues')
    ${for(item of ActionUsers)}
    <a rel='nofollow' href="/Details/${item.User.Id}" target="_blank">${item.User.FullName}</a>
    <br>
    ${/for}
</script>

function onActionBeginMainGrid(args) {
        if (args.requestType === "filterbeforeopen") {
            switch (args.columnName) {
                case "Status.Name": {
                    args.filterModel.options.dataSource =
                        [ { "Status": { "Name": "In Progress" } },
                        { "Status": { "Name": "Created" } },
                        { "Status": { "Name": "Completed" } }];
                    break;
                }
            }

        }
    }

function onActionCompleteMainGrid(args) {
if (args.requestType == "filterafteropen") {
if (args.columnName == "ActionName" ||
args.columnName == "ActionUsers.User.FullName" ) {
args.filterModel.dlgDiv.querySelector('input').ej2_instances[0].value = 'contains';
args.filterModel.dlgObj.element.children[0].firstElementChild.children[0].style.display = 'none';
}
}
}
 
  1. Share type of the filter used in your grid application
Im using: <e-grid-filterSettings type="Menu"></e-grid-filterSettings>, but for some columns I have: filter="@(new { type="CheckBox" })"
 
  1. Share the screenshot or video demonstration of the issue.

When I click on the column ActionUsers.User.FullName I see this error:


 

  1. Share the datasource bind to the grid component.
Im using a remote data source:
<e-data-manager url="https://localhost:4455/myodata/Actions?$expand=ActionUsers($expand=User($select=Id,FullName)),Status" adaptor="ODataV4Adaptor" crossdomain="true"></e-data-manager>

Im my previous post, you can see the odata endpoint and the data model of the returned objects.
 
  1. Share the Syncfusion package version.
Simple.OData.V4.Client 5.12.0
Syncfusion.EJ2.AspNet.Core 18.1.0.55

Thanks in advance!


MS Manivel Sellamuthu Syncfusion Team December 1, 2020 02:35 PM UTC

Hi Rony, 

Thanks for your update. 

From the shared screenshot we are able to identify the reported issue. 

We suggest you set the type for the columns to resolve the issue. If the type is not defined for the column, then it will be determined from the first record of the dataSource. Incase if the first record of the dataSource is null/blank value for a column then it is necessary to define the type for that column. We have already discussed about this in our documentation section. Please refer the below documentation link for more information. 
 

Please let us know, if you need further assistance. 

Regards, 
Manivel 



RD Rony De Sousa December 1, 2020 05:50 PM UTC


Hi Manivel, thanks for the update!

I added the column type and it fixed the "Can not read property 0 of undefined", but it doesn't fixed my original problem: Hot to filter a multi-level complex field column using Odata and remote data source.

Now Im able to see the query sent to the server, which is not correct:

https://localhost:4455/myodata/Actions?$top=20&$filter=contains(tolower(EventUserAmbassador/User/FullName),%27vel%27)

But it should be:

https://localhost:4455/myodata/Actions?$top=20&$filter=ActionUsers/all(c:contains(tolower(c/User/FullName),%27vele%27))

Please let me know if I can provide any additional clarification on what the requirement is, and how can I proceed to achieve this.

Thanks in advance!




MS Manivel Sellamuthu Syncfusion Team December 2, 2020 12:48 PM UTC

Hi Rony, 
 
We are glad that the  “Can not read property 0 of undefined” error resolved by the provided update. 
 
Based on your query we suspect that you wish to customize the query which is being sent to the server. You can achieve this requirement by using the Grid’s custom adaptor method. This is demonstrated in below code snippet where a custom adaptor is extended from the built-in OData adaptor and also its built-in request and response processing methods have been overridden in the sample-level, 
 
<ejs-grid id="CustomAdaptor" created="created" allowPaging="true"> 
    <e-grid-pagesettings pageSize="8"></e-grid-pagesettings> 
</ejs-grid> 
 
<script> 
    function created(args) { 
        class SerialNoAdaptor extends ej.data.ODataAdaptor { 
            // Request processing method 
            processQuery() { 
                // calling base class processQuery function 
                var original = super.processQuery.apply(this, arguments); 
                // Here you can customize the request as per your requirement and send it 
                return original; 
            } 
            // Response processing method 
            processResponse() { 
                // calling base class processResponse function 
                var original = super.processResponse.apply(this, arguments); 
                return { result: original.result, count: original.count }; 
            } 
        } 
        var grid = document.querySelector('#CustomAdaptor').ej2_instances[0]; 
        grid.dataSource = new ej.data.DataManager({ 
            adaptor: new SerialNoAdaptor() 
        }); 
    } 
</script> 
 
 
Please let us know if you need further assistance on this. 
 
Regards, 
Manivel 



RD Rony De Sousa December 2, 2020 07:54 PM UTC


Hi Manivel,

I followed your approach and the documentation, I ran into issues while implementing a custom adaptor and remote odata source. However, I manage to tweak the code to make it work, and currently I have this:

function onCreatedMainGrid(args) {
// class SerialNoAdaptor extends ej.data.ODataAdaptor {
class SerialNoAdaptor extends ej.data.ODataV4Adaptor {
// Request processing method
processQuery() {
// calling base class processQuery function
var original = super.processQuery.apply(this, arguments);
// Here you can customize the request as per your requirement and send it
return original;
}
// Response processing method
processResponse() {
// calling base class processResponse function
var original = super.processResponse.apply(this, arguments);
const resultObj = original.result ? { result: original.result, count: original.count } : original;
return resultObj;
}
}
var grid = document.querySelector('#MainGrid').ej2_instances[0];
grid.dataSource = new ej.data.DataManager({
url: 'MyOdataServiceUrl',
adaptor: new SerialNoAdaptor(),
crossDomain: true
});
}

1. ODataV4Adaptor instead of ODataAdaptor
2. const resultObj = original.result ? { result: original.result, count: original.count } : original; instead of just { result: original.result, count: original.count }

Above two points are needed, otherwise it doesn't work with odata and remote data sources, the second point is needed for filter menu columns, otherwise it fails.

I couldn't finish the entire code to make it work, but I guess that as you suggested, I should be able to change the query before it goes to the server. Ideally, this should automatically being handled for complex nested objects.

I realized now of this issue:
When I filter by x column of the grid it works, but if try to add another filter using a custom datasource right after I do click on the filter icon, it shows: "No matches found":



I have an onActionBegin function where I set the custom data source, like this:

function onActionBegin(args) {
if (args.requestType === "filterbeforeopen" && args.columnName == "Status.Name") {
args.filterModel.options.dataSource =
[ { "Status": { "Name": "Started" } },
{ "Status": { "Name": "Delayed" } },
{ "Status": { "Name": "Completed" } } ];
}

}
}

Any idea why is this happening? If I filter on that column without having any other active filter on any other column, it works.

Thanks!


MS Manivel Sellamuthu Syncfusion Team December 4, 2020 02:43 AM UTC

Hi Rony, 

Thanks for your update. 

Query: I couldn't finish the entire code to make it work, but I guess that as you suggested, I should be able to change the query before it goes to the server. Ideally, this should automatically being handled for complex nested objects. 
By default our DataManager sends query as per the ODataV4 standard protocols only. 

Query: When I filter by x column of the grid it works, but if try to add another filter using a custom datasource right after I do click on the filter icon, it shows: "No matches found": 
To validate further on the reported issue, we suggest you to share the network tab screenshots while opening the filter. 

Regards, 
Manivel 



RD Rony De Sousa December 11, 2020 08:10 PM UTC


Hi Manivel,

Thanks for your response and apologies for my delayed response.

"By default our DataManager sends query as per the ODataV4 standard protocols only."
No, that is not happening for columns mapped to a collection attribute. https://www.odata.org/blog/support-for-any-and-all/

The generated query has this format:

https://localhost:4455/myodata/Actions?$top=20&$filter=contains(tolower(EventUserAmbassador/User/FullName),%27vel%27)
which is not correct for collections, it must be like this:
https://localhost:4455/myodata/Actions?$top=20&$filter=ActionUsers/all(c:contains(tolower(c/User/FullName),%27vele%27))

You can see an example of the issue here: https://www.syncfusion.com/forums/135342/filtering-array-type-columns

"To validate further on the reported issue, we suggest you to share the network tab screenshots while opening the filter."
No request is being sent to the server, apparently it is an issue when we use custom data source for a few columns without all the columns or without the expected syncfusion format.
You can find an example of what Im referring to in this link: https://www.syncfusion.com/forums/142044/providing-server-side-filter-list-when-using-url-data-adaptor.

For now, Im using a Custom Adaptor in order to send custom queries when the user clicks on a column of type collection to "bypass" this issue. It would be really good if in the future syncfusion adds support to filter over collection columns.

Thanks for your help!




MS Manivel Sellamuthu Syncfusion Team December 14, 2020 09:51 AM UTC

Hi Rony, 

Thanks for your update. 

We are glad that the provided suggestion helped. 

We will consider to improve this in our ODataV4 Adaptor. It will be included in any of our upcoming release. 

Regards, 
Manivel 


Loader.
Up arrow icon