Search code examples
c#asp.net-coreentity-framework-coreodata

OData throws The query specified in the URI is not valid


The client is 3rd party, so I don't have access to that source. However, I have a well explained PDF with some sample of requests the client uses. In example,

/Customers?$filter=ID+eq+guid'1D225D75-A587-4AE4-BA9A-2224B2484EA5' 

/Customers?$filter=Code+eq+12345 

This is my first time working with OData and Entity Framework and I am currently running into problems with the filtering.

When I do a simple GET Request without filtering, it returns the data correctly. However, when I do something like

http://localhost:52973/odata/customers?$filter=Code+eq+12069

It returns the error:

{"error":{"code":"","message":"The query specified in the URI is not valid. A binary operator with incompatible types was detected. Found operand types 'Edm.String' and 'Edm.Int32' for operator kind 'Equal'.","details":[],"innererror":{"message":"A binary operator with incompatible types was detected. Found operand types 'Edm.String' and 'Edm.Int32' for operator kind 'Equal'.","type":"Microsoft.OData.ODataException","stacktrace":"   at Microsoft.OData.UriParser.BinaryOperatorBinder.PromoteOperandTypes(BinaryOperatorKind binaryOperatorKind, SingleValueNode& left, SingleValueNode& right, TypeFacetsPromotionRules facetsPromotionRules)\r\n   at Microsoft.OData.UriParser.BinaryOperatorBinder.BindBinaryOperator(BinaryOperatorToken binaryOperatorToken)\r\n   at Microsoft.OData.UriParser.MetadataBinder.Bind(QueryToken token)\r\n   at Microsoft.OData.UriParser.FilterBinder.BindFilter(QueryToken filter)\r\n   at Microsoft.OData.UriParser.ODataQueryOptionParser.ParseFilterImplementation(String filter, ODataUriParserConfiguration configuration, ODataPathInfo odataPathInfo)\r\n   at Microsoft.OData.UriParser.ODataQueryOptionParser.ParseFilter()\r\n   at Microsoft.AspNet.OData.Query.FilterQueryOption.get_FilterClause()\r\n   at Microsoft.AspNet.OData.Query.Validators.FilterQueryValidator.Validate(FilterQueryOption filterQueryOption, ODataValidationSettings settings)\r\n   at Microsoft.AspNet.OData.Query.Validators.ODataQueryValidator.Validate(ODataQueryOptions options, ODataValidationSettings validationSettings)\r\n   at Microsoft.AspNet.OData.EnableQueryAttribute.ValidateQuery(HttpRequest request, ODataQueryOptions queryOptions)\r\n   at Microsoft.AspNet.OData.EnableQueryAttribute.<>c__DisplayClass1_0.<OnActionExecuted>b__3(ODataQueryContext queryContext)\r\n   at Microsoft.AspNet.OData.EnableQueryAttribute.ExecuteQuery(Object responseValue, IQueryable singleResultCollection, IWebApiActionDescriptor actionDescriptor, Func`2 modelFunction, IWebApiRequestMessage request, Func`2 createQueryOptionFunction)\r\n   at Microsoft.AspNet.OData.EnableQueryAttribute.OnActionExecuted(Object responseValue, IQueryable singleResultCollection, IWebApiActionDescriptor actionDescriptor, IWebApiRequestMessage request, Func`2 modelFunction, Func`2 createQueryOptionFunction, Action`1 createResponseAction, Action`3 createErrorAction)"}}}

So let's get started. In our Startup.cs:

   public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            ....
            app.UseMvc(b =>
            {
                b.Select().Expand().Filter().OrderBy().MaxTop(100).Count();
                b.MapODataServiceRoute("odata", "odata", GetEdmModel());
            });
        }

        private static IEdmModel GetEdmModel()
        {
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
            builder.EntitySet<Customer>("Customers").EntityType
                .Filter(nameof(Customer.Id),nameof(Customer.Code));
            return builder.GetEdmModel();
        }

And then our controller:

public class CustomersController : ODataController
    {
        private readonly IsahContext db; //IsahContext is our DbContext class ;) 

        public CustomersController()
        {
            this.db = new IsahContext();
        }
   [HttpGet]
        [EnableQuery]
        public IActionResult Get()
        {
            var AllCusts = GetAllCustomers();
            return Ok(AllCusts);
        }

        [HttpGet]
        [EnableQuery]
        public IActionResult Get(Guid Id, string Code)
        {
            var AllCusts = GetAllCustomers();
            if (!string.IsNullOrEmpty(Code))
            {
                return Ok(AllCusts.FirstOrDefault(c => c.Code == Code));
            }
            return Ok(AllCusts.FirstOrDefault(c => c.Id == Id));
        }
        public IList<Customer> GetAllCustomers()
        {
            IList<Customer> customers = null;
            customers = db.Customers.FromSql("SomeProcName").ToList();
            return customers;
        }

Solution

  • Remove the plus symbols (+) and wrap the value in single quotes as it should be a string equality check. That last uri should be:

    http://localhost:52973/odata/customers?$filter=Code eq '12069'
    

    If you want an encoded uri use %20 for spaces and %27 for single quote:

    http://localhost:52973/odata/customers?$filter=Code%20eq%20%2712069%27