OData / OData/AspNetCoreOData

It is not possible to use byte (Edm.Byte), sbyte (Edm.SByte), and short (Edm.Int16) dynamic properties in $filter expression

Open
#1,340 1 comment 0 reactions 1 assignee View on GitHub

@gathogojr is already working on this.

Since Nov 5, 2024.

bug P2
Dominant language
C#
Stars
505
Forks
186
PR merge metrics
No merged PRs in 30d

Description

Assemblies affected

  • ASP.NET Core OData 9.x
  • ASP.NET Core OData 8.x
  • ASP.NET Core OData 7.x/ASP.NET OData 7.x

Describe the bug/Repro steps
Consider the following sample service that returns a collection of open entities

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData;
using Microsoft.AspNetCore.OData.Query;
using Microsoft.AspNetCore.OData.Routing.Controllers;
using Microsoft.OData.ModelBuilder;

var builder = WebApplication.CreateBuilder(args);

var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<BasicType>("BasicTypes");

builder.Services.AddControllers().AddOData(
    options => options.EnableQueryFeatures().AddRouteComponents(
        modelBuilder.GetEdmModel()));

var app = builder.Build();

app.UseRouting();
app.MapControllers();

app.Run();

public class BasicType
{
    public int Id { get; set; }
    public Dictionary<string, object> DynamicProperties { get; set; }
}

public class BasicTypesController : ODataController
{
    [EnableQuery]
    public ActionResult<IEnumerable<BasicType>> Get()
    {
        return new List<BasicType>()
        {
            new BasicType
            {
                Id = 1,
                DynamicProperties = new Dictionary<string, object>
                {
                    { "DynamicInt32Property", 5 },
                    { "DynamicByteProperty", (byte)7 },
                    { "DynamicSByteProperty", (sbyte)11 },
                    { "DynamicInt16Property", (short)13 }
                }
            }
        };
    }
}

Data Model
Shared in the code snippet in the section above

EDM (CSDL) Model

<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0">
  <edmx:DataServices>
    <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="Default">
      <EntityType Name="BasicType" OpenType="true">
        <Key>
          <PropertyRef Name="Id"/>
        </Key>
        <Property Name="Id" Type="Edm.Int32" Nullable="false"/>
      </EntityType>
      <EntityContainer Name="Container">
        <EntitySet Name="BasicTypes" EntityType="Default.BasicType"/>
      </EntityContainer>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>

Request/Response

  1. The following requests containing a dynamic property of Edm.Int32 type returns the expected result:

    • http://localhost:5043/BasicTypes?$filter=DynamicInt32Property eq 5

    Response:

    {
      "@odata.context":"http://localhost:5043/$metadata#BasicTypes",
      "value":[
        {
          "Id":1,
          "DynamicInt32Property":5,
          "DynamicByteProperty@odata.type":"#Byte",
          "DynamicByteProperty":7,
          "DynamicSByteProperty@odata.type":"#SByte",
          "DynamicSByteProperty":11,
          "DynamicInt16Property@odata.type":"#Int16",
          "DynamicInt16Property":13
        }
      ]
    }
    
  2. The following requests containing dynamic properties of types Edm.Byte, Edm.SByte, and Edm.Int16 cause exceptions to be thrown:

    • http://localhost:5043/BasicTypes?$filter=DynamicByteProperty eq 7
    • http://localhost:5043/BasicTypes?$filter=DynamicSByteProperty eq 11
    • http://localhost:5043/BasicTypes?$filter=DynamicInt16Property eq 13

    Exception:

    System.InvalidCastException: Unable to cast object of type 'System.Byte' to type 'System.Nullable1[System.Int32]'. at lambda_method22(Closure, BasicType) at System.Linq.Enumerable.WhereListIterator1.MoveNext()
    at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteResourceSetAsync(IEnumerable enumerable, IEdmTypeReference resourceSetType, ODataWriter writer, ODataSerializerContext writeContext)
    at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteObjectInlineAsync(Object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext)
    at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteObjectAsync(Object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)
    at Microsoft.AspNetCore.OData.Formatter.ODataOutputFormatterHelper.WriteToStreamAsync(Type type, Object value, IEdmModel model, ODataVersion version, Uri baseAddress, MediaTypeHeaderValue contentType, HttpRequest request, IHeaderDictionary requestHeaders, IODataSerializerProvider serializerProvider)
    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|22_0(ResourceInvoker invoker, IActionResult result)
    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|30_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
    --- End of stack trace from previous location ---
    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker)
    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker)
    at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
    at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
    at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
    at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
    at Microsoft.WebTools.BrowserLink.Net.BrowserLinkMiddleware.InvokeAsync(HttpContext context)
    at Microsoft.AspNetCore.Watch.BrowserRefresh.BrowserRefreshMiddleware.InvokeAsync(HttpContext context)
    at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)

  3. The following requests containing dynamic properties of types Edm.Byte, Edm.SByte, and Edm.Int16 where a cast is applied to the left operand return empty results:

    • http://localhost:5043/BasicTypes?$filter=cast(DynamicByteProperty,Edm.Int32) eq 7
    • http://localhost:5043/BasicTypes?$filter=cast(DynamicSByteProperty,Edm.Int32) eq 11
    • http://localhost:5043/BasicTypes?$filter=cast(DynamicInt16Property,Edm.Int32) eq 13

    Response:

    {
      "@odata.context":"http://localhost:5043/$metadata#BasicTypes",
      "value":[]
    }
    
  4. The following requests containing dynamic properties of types Edm.Byte, Edm.SByte, and Edm.Int16 where a cast is applied to the right operand cause exceptions to be thrown:

    • http://localhost:5043/BasicTypes?$filter=DynamicByteProperty eq cast(7,Edm.Byte)
    • http://localhost:5043/BasicTypes?$filter=DynamicSByteProperty eq cast(11,Edm.SByte)
    • http://localhost:5043/BasicTypes?$filter=DynamicInt16Property eq cast(13,Int16)

    Exception:

    System.InvalidCastException: Unable to cast object of type 'System.Byte' to type 'System.Nullable1[System.Int32]'. at lambda_method28(Closure, BasicType) at System.Linq.Enumerable.WhereListIterator1.MoveNext()
    at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteResourceSetAsync(IEnumerable enumerable, IEdmTypeReference resourceSetType, ODataWriter writer, ODataSerializerContext writeContext)
    at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteObjectInlineAsync(Object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext)
    at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteObjectAsync(Object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)
    at Microsoft.AspNetCore.OData.Formatter.ODataOutputFormatterHelper.WriteToStreamAsync(Type type, Object value, IEdmModel model, ODataVersion version, Uri baseAddress, MediaTypeHeaderValue contentType, HttpRequest request, IHeaderDictionary requestHeaders, IODataSerializerProvider serializerProvider)
    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|22_0(ResourceInvoker invoker, IActionResult result)
    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|30_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
    --- End of stack trace from previous location ---
    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker)
    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker)
    at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
    at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
    at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
    at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
    at Microsoft.WebTools.BrowserLink.Net.BrowserLinkMiddleware.InvokeAsync(HttpContext context)
    at Microsoft.AspNetCore.Watch.BrowserRefresh.BrowserRefreshMiddleware.InvokeAsync(HttpContext context)
    at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)

Expected behavior
I expect to be able to successfully apply filter expressions referencing dynamic properties of type Edm.Byte, Edm.SByte, and Edm.Int16.

Additional context
This issue affect decimal dynamic properties too. However, for decimal dynamic properties, apply the m/M suffix to the value (right operand) seems to work as a workaround:

  • http://localhost:5043/BasicTypes?$filter=DynamicDecimalProperty eq 7654321m

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.