OData / WebApi

OData Web API: A server library built upon ODataLib and WebApi
https://docs.microsoft.com/odata
Other
854 stars 473 forks source link

OData Value Conversion to Structured value. #2467

Open MikeKenyon opened 3 years ago

MikeKenyon commented 3 years ago

Trying to parse an OData request against a model that has structs that are representing defined value types. There's an implicit conversion for the type to/from string, but it's failing as incompatible data types.

Assemblies affected

Microsoft.AspNetCore.OData, v7.5.7

Reproduce steps

Several properties of the domain model are structs that provide some validation and type safety over primitive types (strings, longs, etc.). We never get to the point of applying the translation through Automapper to executing the query because the base .NET OData libraries are flagging the type conversion between our structured type and a primitive constant on the URL (as part of it mapping the data route).

// Sample Model
public class Context : DbContext {
   public DbSet<Quote> Quotes {get; set;}
}
public class Quote {
    public OrganizationCode? Organization {get;set;}
    // ... other properties here.
}
public struct OrganizationCode { 
   /* bunch of validation, but implicit conversion from/to a string */
}

Trying to execute ... http://localhost/api/quote?$filter=Organization eq 'VALUE'

Expected result

It would be expected that VALUE be cast to the type of the left hand operator.

Actual result

we are hitting the following error:

{
"Message": "The query specified in the URI is not valid. A binary operator with incompatible types was detected. Found operand types 'System.Nullable_1OfOrganizationCode' and 'Edm.String' for operator kind 'Equal'.",
"ExceptionMessage": "A binary operator with incompatible types was detected. Found operand types 'System.Nullable_1OfOrganizationCode' and 'Edm.String' for operator kind 'Equal'.",
"ExceptionType": "Microsoft.OData.ODataException",
"StackTrace": "   at Microsoft.OData.UriParser.BinaryOperatorBinder.PromoteOperandTypes(BinaryOperatorKind binaryOperatorKind, SingleValueNode& left, SingleValueNode& right, TypeFacetsPromotionRules facetsPromotionRules)\r\n   at Microsoft.OData.UriParser.ODataUriResolver.PromoteBinaryOperandTypes(BinaryOperatorKind binaryOperatorKind, SingleValueNode& leftNode, SingleValueNode& rightNode, IEdmTypeReference& typeReference)\r\n   at Microsoft.OData.UriParser.BinaryOperatorBinder.BindBinaryOperator(BinaryOperatorToken binaryOperatorToken)\r\n   at Microsoft.OData.UriParser.MetadataBinder.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.FilterQueryOption.Validate(ODataValidationSettings validationSettings)\r\n   at Microsoft.AspNet.OData.Query.Validators.ODataQueryValidator.Validate(ODataQueryOptions options, ODataValidationSettings validationSettings)\r\n   at Microsoft.AspNet.OData.Query.ODataQueryOptions.Validate(ODataValidationSettings validationSettings)\r\n   at Microsoft.AspNet.OData.EnableQueryAttribute.ValidateQuery(HttpRequest request, ODataQueryOptions queryOptions)\r\n   at Microsoft.AspNet.OData.EnableQueryAttribute.OnActionExecuting(ActionExecutingContext context)"
}

Additional detail

I am trying to expose a domain model version of the a database table through a WebApi and provide OData query capability over it. We've got Automapper installed and it's handling the translation between our domain model and the actual database model under the hood.

I'd assume that there's some capability to provide type conversion between these types. Looking through the code base, it looks like there's the capability to create a EdmTypeDefinition which could handle the translation, but I haven't been able to get it register successfully. I tried creating an EdmModel directly and calling AddStructuredType() to register the type translation. I've attempted to create a DefaultODataDeserializerProvider to provide a custom deserializer, but adding this through the registration method like

            endpoints.MapODataRoute("api", "api", config => 
            config.AddService(Microsoft.OData.ServiceLifetime.Singleton,
                typeof(Microsoft.AspNet.OData.Formatter.Deserialization.ODataDeserializerProvider),
                sp => new DynamicPropertiesDeserializerProvider(sp))

has not proven successful in getting that method called. Does anyone know how to make this work? Gotta figure having a value type in an entity definition should not be that difficult. This works with several "in the box" structured value types .. DateTime, Guid, etc.

xuzhg commented 3 years ago

@MikeKenyon It seems you can use property implicitly conversion to meet your requirement.

Would you please try to method mentioned in this post: https://devblogs.microsoft.com/odata/how-to-consume-sql-spatial-data-with-web-api-v2-2-for-odata-v4/

Let me know the result.

KenitoInc commented 2 years ago

@MikeKenyon Did you manage to resolve your issue?