This is a migrated thread and some comments may be shown as answers.

Grid filter on Guid: Provided expression should have string type

7 Answers 2263 Views
Grid
This is a migrated thread and some comments may be shown as answers.
Bob
Top achievements
Rank 1
Veteran
Bob asked on 07 Aug 2018, 09:18 AM

I have an angular grid which uses a GridDataResult posting back to an API controller in my .netcore project with a DataSourceRequest parameter.

The first column of my grid has a Guid, declared in a model both server and client side.

When I try filter on this column the server throws an exception:

"Provided expression should have string type".

I have searched both this forum and the MVC forum to try apply a server-side request filter to no avail.

How should this be handled?

7 Answers, 1 is accepted

Sort by
0
Bob
Top achievements
Rank 1
Veteran
answered on 08 Aug 2018, 10:21 AM

I'm going to try explain this further in the hope of getting an answer.  Here's my .NET class:

public class NcEntityDto : EntityDto<Guid>
{
    public string DisplayName { get; set; }
 
    public string ExtensionData { get; set; }
 
    public long? OrganizationUnitId { get; set; }
    public string OrganizationUnitDisplayName { get; set; }
 
    public virtual List<NcFormSubmissionDto> FormSubmissions { get; set; }
 
    public virtual int CarePlanCount { get; set; }
 
    public virtual List<NcCarePlanDto> CarePlans { get; set; }
 
    public virtual int WarningCount { get; set; }
 
    public virtual List<NcWarningDto> Warnings { get; set; }
}

Which is dynamically created in my angular project through swagger and adheres to the interface below:

export interface INcEntityDto {
    id: string | undefined;
    displayName: string | undefined;
    extensionData: string | undefined;
    organizationUnitId: number | undefined;
    organizationUnitDisplayName: string | undefined;
    formSubmissions: NcFormSubmissionDto[] | undefined;
    carePlanCount: number | undefined;
    carePlans: NcCarePlanDto[] | undefined;
    warningCount: number | undefined;
    warnings: NcWarningDto[] | undefined;
}

A call is made from my component to get data from the API:

getEntitiesForKendo() {
    this.gridLoading = true;
    this._kendoGridService.getEntities(this.state)
        .subscribe((data) => {
            this.gridData = data;
            this.gridLoading = false;
            });
}

And the grid is rendered:

                <kendo-grid #ncEntityGrid
                    *ngIf="!gridLoading"
                    [data]="gridData"
                    [pageSize]="state.take"
                    [skip]="state.skip"
                    [sort]="state.sort"
                    [filter]="state.filter"
                    [group]="state.group"
                    [scrollable]="'scrollable'"
                    [pageable]="true"
                    [sortable]="{
                        allowUnsort: true,
                        mode: 'multiple'
                    }"
                    [groupable]="true"
                    [filterable]="true"
                    [columnMenu]="true"
                    [resizable]="true"
                    (dataStateChange)="dataStateChange($event)">
                    <ng-template kendoGridToolbarTemplate media="md">
                        <button type="button" kendoGridExcelCommand icon="file-excel">{{ l('ExportToExcel')}}</button>
                        <!-- <button type="button" (click)="saveGridSettings(ncEntityGrid)">Save settings</button> -->
                    </ng-template>
                    <kendo-grid-column field="id" title="{{ l('Id')}}" width="120" [hidden]="true" media="md"></kendo-grid-column>
                    <kendo-grid-column field="displayName" title="{{ l('DisplayName')}}" [minResizableWidth]="140" width="140"></kendo-grid-column>
...
...
...[code removed for brevity]

 

When a string is entered into the filter for the Id column (which is a string representation of a Guid) the API controller throws the error:

"Provided expression should have string type".

public async Task<JsonResult> GetEntities([DataSourceRequest]DataSourceRequest request)
{
    var model = await _ncEntityAppService.GetAllForKendoGrid();
    //request.Filters.Add(new FilterDescriptor() { Member = "id", MemberType = typeof(Guid) });
    return Json(model.ToDataSourceResult(request));
}

Obviously the grid is passing a string and the application of the filters in the grid is expecting the Id to be of type Guid.

 

I don't know how much more expansive I can be.  One thing I can say is that when I previously worked with Telerik questions in the support forum were answered promptly but I can see some questions here have been hanging around for days.  What is the SLA for getting answers during my trial period?

0
Konstantin Dikov
Telerik team
answered on 10 Aug 2018, 06:31 AM
Hello Bob,

For handling the Guid expression you will have to manually traverse the "Filters" collection of the DataSourceRequest object, find the filter expression for the "id" field and change its MemberType to "Guid".

As for the response time, the forum threads are for the community and although that we are doing our best to handle all threads as soon as possible, the support tickets are handled with higher priority. As a trial user you could also open support tickets, but have in mind that trial support does not fall into the 24 hours response time policy and it could take up to 72 hours for our team to answer.


Regards,
Konstantin Dikov
Progress Telerik
Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
0
Bob
Top achievements
Rank 1
Veteran
answered on 11 Aug 2018, 04:34 PM

Thank you, Konstantin,

Here is my controller method:

public async Task<JsonResult> GetEntities([DataSourceRequest]DataSourceRequest request)
{
    if (request.Filters != null)
    {
        ModifyFilters(request.Filters);
    }
 
    var query = _ncEntityService.GetEntityIQueryableForKendoGrid();
    var model = query.ToDataSourceResult(request);
    foreach (var entity in ObjectMapper.Map<List<NcEntityDto>>(model.Data))
    {
        entity.OrganizationUnit.DisplayName = await _ncOrganizationUnitService.GetOrganisationUnitBreadcrumb(entity.OrganizationUnit.Id);
    }
    return Json(model);
}

 

Here is my method to change the type:

private void ModifyFilters(IEnumerable<IFilterDescriptor> filters)
{
    if (filters.Any())
    {
        foreach (var filter in filters)
        {
            var descriptor = filter as FilterDescriptor;
            if (descriptor != null && descriptor.Member == "id")
            {
                descriptor.MemberType = typeof(Guid);
            }
            else if (filter is CompositeFilterDescriptor)
            {
                ModifyFilters(((CompositeFilterDescriptor)filter).FilterDescriptors);
            }
        }
    }
}

 

And below is the stack trace of the error. The error is thrown during invocation of .toDataSourceResult() and is internal to kendo.  Is there anything obvious I am doing wrong?

ERROR 2018-08-11 17:10:50,121 [3    ] Mvc.ExceptionHandling.AbpExceptionFilter - Method 'System.String ToLower()' declared on type 'System.String' cannot be called with instance of type 'System.Guid'
System.ArgumentException: Method 'System.String ToLower()' declared on type 'System.String' cannot be called with instance of type 'System.Guid'
   at System.Linq.Expressions.Expression.ValidateCallInstanceType(Type instanceType, MethodInfo method)
   at System.Linq.Expressions.Expression.ValidateStaticOrInstanceMethod(Expression instance, MethodInfo method)
   at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable`1 arguments)
   at Kendo.Mvc.Infrastructure.Implementation.Expressions.FilterOperatorExtensions.GenerateToLowerCall(Expression stringExpression, Boolean liftMemberAccess)
   at Kendo.Mvc.Infrastructure.Implementation.Expressions.FilterOperatorExtensions.GenerateCaseInsensitiveStringMethodCall(MethodInfo methodInfo, Expression left, Expression right, Boolean liftMemberAccess)
   at Kendo.Mvc.Infrastructure.Implementation.Expressions.FilterOperatorExtensions.CreateExpression(FilterOperator filterOperator, Expression left, Expression right, Boolean liftMemberAccess)
   at Kendo.Mvc.Infrastructure.Implementation.Expressions.FilterDescriptorExpressionBuilder.CreateBodyExpression()
   at Kendo.Mvc.FilterDescriptor.CreateFilterExpression(ParameterExpression parameterExpression)
   at Kendo.Mvc.FilterDescriptorBase.CreateFilterExpression(Expression instance)
   at Kendo.Mvc.Infrastructure.Implementation.Expressions.FilterDescriptorCollectionExpressionBuilder.CreateBodyExpression()
   at Kendo.Mvc.Infrastructure.Implementation.Expressions.FilterExpressionBuilder.CreateFilterExpression()
   at Kendo.Mvc.Extensions.QueryableExtensions.Where(IQueryable source, IEnumerable`1 filterDescriptors)
   at Kendo.Mvc.Extensions.QueryableExtensions.CreateDataSourceResult[TModel,TResult](IQueryable queryable, DataSourceRequest request, ModelStateDictionary modelState, Func`2 selector)
   at Kendo.Mvc.Extensions.QueryableExtensions.ToDataSourceResult(IQueryable queryable, DataSourceRequest request)
   at Nuagecare.Web.Host.Controllers.KendoController.<GetEntities>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at lambda_method(Closure , Object )
   at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeActionMethodAsync>d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeNextActionFilterAsync>d__10.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeInnerFilterAsync>d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeNextExceptionFilterAsync>d__23.MoveNext()

 

0
Alex Hajigeorgieva
Telerik team
answered on 14 Aug 2018, 06:03 AM
Hi, Bob,

Thank you for the provided code snippets.

I will just update this thread with the answer that resolved the issue from a private thread.

------------

I inspected the ModifyFilters function and the only thing that it was missing is actually casting the string filter value to a Guid so that the ToDataSourceResult() extension method can work with the appropriate filter value:
private void ModifyFilters(IList<IFilterDescriptor> filters)
{
    if (filters.Any())
    {
        foreach (var filter in filters)
        {
            var descriptor = filter as FilterDescriptor;
            if (descriptor != null && descriptor.Member == "id")
            {
                descriptor.MemberType = typeof(Guid);
                descriptor.Value = Guid.Parse(descriptor.Value.ToString());
            }
            else if (filter is CompositeFilterDescriptor)
            {
                ModifyFilters(((CompositeFilterDescriptor)filter).FilterDescriptors);
            }
        }
    }
}

Kind Regards,
Alex Hajigeorgieva
Progress Telerik
Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
0
Bob
Top achievements
Rank 1
Veteran
answered on 14 Aug 2018, 08:39 AM
Hi Alex,
I will include here the code from our private message.  For those coming across this problem Alex pointed out that's it's not possible to do anything other than an "eq" operator on a Guid.  Therefore I implemented the following filter in my grid:
<kendo-grid-column field="id" title="{{ l('Id')}}" width="120" [hidden]="true" media="md" [sortable]="false">
  <ng-template kendoGridFilterMenuTemplate let-filter let-column="column" let-filterService="filterService">
    <kendo-grid-string-filter-menu [extra]="false" [column]="column" [filter]="filter" [filterService]="filterService" operator="eq" eq="eq">
      <kendo-filter-eq-operator></kendo-filter-eq-operator>
    </kendo-grid-string-filter-menu>
  </ng-template>
</kendo-grid-column>

 

And handled it in my controller with the following code:

private void ModifyFilters(IList<IFilterDescriptor> filters)
{
    if (filters.Any())
    {
        foreach (var filter in filters)
        {
            var descriptor = filter as FilterDescriptor;
            if (descriptor != null && descriptor.Member == "id")
            {
                descriptor.Value = ToGuid(descriptor.Value.ToString());
                descriptor.MemberType = typeof(Guid);
            }
            else if (filter is CompositeFilterDescriptor)
            {
                ModifyFilters(((CompositeFilterDescriptor)filter).FilterDescriptors);
            }
        }
    }
}
 
public static Guid ToGuid(string descriptorValueToString)
{
    Guid newGuid;
 
    if (Guid.TryParse(descriptorValueToString, out newGuid))
    {
        return Guid.Parse(descriptorValueToString);
    }
    return Guid.Empty;
}

Hope this helps anyone with the same problem, thanks to Alex.

0
Alex Hajigeorgieva
Telerik team
answered on 15 Aug 2018, 03:34 PM
Hi, Bob,

Thank you very much for updating the forum thread.

It appears that it is possible to compare guids similar to numbers according to this Microsoft article:

https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql/comparing-guid-and-uniqueidentifier-values

In case you wish to explore these filters or need additional assistance, please let us know.

Regards,
Alex Hajigeorgieva
Progress Telerik
Get quickly and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
0
Daniel Blendea
Top achievements
Rank 1
answered on 23 Aug 2019, 12:19 PM

I have the same problem with fields that are int but are serialized as double. I implemented the same workaround, although I think this is a poor solution.

Maybe tomorrow I will have to add another "if" in the extension method, to modify another FilterDescriptor?

Tags
Grid
Asked by
Bob
Top achievements
Rank 1
Veteran
Answers by
Bob
Top achievements
Rank 1
Veteran
Konstantin Dikov
Telerik team
Alex Hajigeorgieva
Telerik team
Daniel Blendea
Top achievements
Rank 1
Share this question
or