OData / WebApi

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

Aggregate statements containing alias with same name as model property throws ODataException #911

Open athoscouto opened 7 years ago

athoscouto commented 7 years ago

Every aggregate query that uses as an alias the name of a property of the model being aggregated throws an ODataException if the types of the aggregated value and of the property don't match.

Assemblies affected

Which assemblies and versions are known to be affected e.g. OData .Net lib 6.15-beta.

Reproduce steps

I used the Northwind Database to reproduce the bug. A simple OData web app containing it can be found here. To create the error you just need to query the OData endpoint with an URL like:

{OData endpoint}/Orders/?$apply=aggregate(OrderID with countdistinct as Customer)

Expected result

{
  "@odata.context": "http://localhost:8609/$metadata#Orders(CustomerX)",
  "value": [
    {
      "@odata.id": null,
      "Customer": 840
    }
  ]
}

Actual result

{
  "error": {
    "code": "",
    "message": "An error has occurred.",
    "innererror": {
      "message": "The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; odata.metadata=minimal'.",
      "type": "System.InvalidOperationException",
      "stacktrace": "",
      "internalexception": {
        "message": "A primitive value was specified; however, a value of the non-primitive type 'Northwind.Web.Models.Customer' was expected.",
        "type": "Microsoft.OData.ODataException",
        "stacktrace": "   at Microsoft.OData.ValidationUtils.ValidateIsExpectedPrimitiveType(Object value, IEdmPrimitiveTypeReference valuePrimitiveTypeReference, IEdmTypeReference expectedTypeReference) in C:\\Users\\t-atcout\\git\\odata.net\\src\\Microsoft.OData.Core\\ValidationUtils.cs:line 316\r\n   at Microsoft.OData.WriterValidator.ValidateIsExpectedPrimitiveType(Object value, IEdmPrimitiveTypeReference valuePrimitiveTypeReference, IEdmTypeReference expectedTypeReference) in C:\\Users\\t-atcout\\git\\odata.net\\src\\Microsoft.OData.Core\\WriterValidator.cs:line 98\r\n   at Microsoft.OData.JsonLight.ODataJsonLightValueSerializer.WritePrimitiveValue(Object value, IEdmTypeReference actualTypeReference, IEdmTypeReference expectedTypeReference) in C:\\Users\\t-atcout\\git\\odata.net\\src\\Microsoft.OData.Core\\JsonLight\\ODataJsonLightValueSerializer.cs:line 231\r\n   at Microsoft.OData.JsonLight.ODataJsonLightPropertySerializer.WritePrimitiveProperty(ODataPrimitiveValue primitiveValue, Boolean isOpenPropertyType) in C:\\Users\\t-atcout\\git\\odata.net\\src\\Microsoft.OData.Core\\JsonLight\\ODataJsonLightPropertySerializer.cs:line 456\r\n   at Microsoft.OData.JsonLight.ODataJsonLightPropertySerializer.WriteProperty(ODataProperty property, IEdmStructuredType owningType, Boolean isTopLevel, Boolean allowStreamProperty, IDuplicatePropertyNameChecker duplicatePropertyNameChecker) in C:\\Users\\t-atcout\\git\\odata.net\\src\\Microsoft.OData.Core\\JsonLight\\ODataJsonLightPropertySerializer.cs:line 231\r\n   at Microsoft.OData.JsonLight.ODataJsonLightPropertySerializer.WriteProperties(IEdmStructuredType owningType, IEnumerable`1 properties, Boolean isComplexValue, IDuplicatePropertyNameChecker duplicatePropertyNameChecker) in C:\\Users\\t-atcout\\git\\odata.net\\src\\Microsoft.OData.Core\\JsonLight\\ODataJsonLightPropertySerializer.cs:line 120\r\n   at Microsoft.OData.JsonLight.ODataJsonLightWriter.StartResource(ODataResource resource) in C:\\Users\\t-atcout\\git\\odata.net\\src\\Microsoft.OData.Core\\JsonLight\\ODataJsonLightWriter.cs:line 226\r\n   at Microsoft.OData.ODataWriterCore.<>c__DisplayClass79_0.<WriteStartResourceImplementation>b__0() in C:\\Users\\t-atcout\\git\\odata.net\\src\\Microsoft.OData.Core\\ODataWriterCore.cs:line 953\r\n   at Microsoft.OData.ODataWriterCore.InterceptException(Action action) in C:\\Users\\t-atcout\\git\\odata.net\\src\\Microsoft.OData.Core\\ODataWriterCore.cs:line 1294\r\n   at Microsoft.OData.ODataWriterCore.WriteStartResourceImplementation(ODataResource resource) in C:\\Users\\t-atcout\\git\\odata.net\\src\\Microsoft.OData.Core\\ODataWriterCore.cs:line 921\r\n   at Microsoft.OData.ODataWriterCore.WriteStart(ODataResource resource) in C:\\Users\\t-atcout\\git\\odata.net\\src\\Microsoft.OData.Core\\ODataWriterCore.cs:line 452\r\n   at System.Web.OData.Formatter.Serialization.ODataResourceSerializer.WriteDynamicTypeResource(Object graph, ODataWriter writer, IEdmTypeReference expectedType, ODataSerializerContext writeContext) in C:\\Users\\t-atcout\\git\\WebApi\\OData\\src\\System.Web.OData\\OData\\Formatter\\Serialization\\ODataResourceSerializer.cs:line 182\r\n   at System.Web.OData.Formatter.Serialization.ODataResourceSerializer.WriteResource(Object graph, ODataWriter writer, ODataSerializerContext writeContext, IEdmTypeReference expectedType) in C:\\Users\\t-atcout\\git\\WebApi\\OData\\src\\System.Web.OData\\OData\\Formatter\\Serialization\\ODataResourceSerializer.cs:line 221\r\n   at System.Web.OData.Formatter.Serialization.ODataResourceSerializer.WriteObjectInline(Object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext) in C:\\Users\\t-atcout\\git\\WebApi\\OData\\src\\System.Web.OData\\OData\\Formatter\\Serialization\\ODataResourceSerializer.cs:line 80\r\n   at System.Web.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteResourceSet(IEnumerable enumerable, IEdmTypeReference resourceSetType, ODataWriter writer, ODataSerializerContext writeContext) in C:\\Users\\t-atcout\\git\\WebApi\\OData\\src\\System.Web.OData\\OData\\Formatter\\Serialization\\ODataResourceSetSerializer.cs:line 145\r\n   at System.Web.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteObjectInline(Object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext) in C:\\Users\\t-atcout\\git\\WebApi\\OData\\src\\System.Web.OData\\OData\\Formatter\\Serialization\\ODataResourceSetSerializer.cs:line 87\r\n   at System.Web.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteObject(Object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) in C:\\Users\\t-atcout\\git\\WebApi\\OData\\src\\System.Web.OData\\OData\\Formatter\\Serialization\\ODataResourceSetSerializer.cs:line 56\r\n   at System.Web.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders) in C:\\Users\\t-atcout\\git\\WebApi\\OData\\src\\System.Web.OData\\OData\\Formatter\\ODataMediaTypeFormatter.cs:line 529\r\n   at System.Web.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken) in C:\\Users\\t-atcout\\git\\WebApi\\OData\\src\\System.Web.OData\\OData\\Formatter\\ODataMediaTypeFormatter.cs:line 433\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__1b.MoveNext()"
      }
    }
  }
}

Additional details

This error is not specific to the aggregation method used, or if the one value is primitive and the other is non-primitive. If the aggregate property and the model property have the same name (alias) and their type differs, we'll get errors like this. Took a quick look at the spec and didn't find any restriction on aliases for aggregation properties.

athoscouto commented 7 years ago

Apparently, this is because when aggregation is done the ODataWriterCore created has as the IEdmStructuredType resourceType the type that's being aggregated. This should not happen because the resulting type of the aggregation will not have the same structure that the aggregated model has. Keeping the resourceType of the serialization of aggregations as the model that's being aggregated impose a lot of checks that can lead to errors like this one.