OdataToEntity: `$select` on nullable `$expand` property throws ODataException

When I execute the following query (where an Account has a nullable/optional DepartmentId / Department:

  • http://localhost:5000/odata/Accounts?$top=50&$expand=Department($select=Name)

I get the following exception.

Microsoft.OData.ODataException: The property 'Name[Nullable=False]' of type 'Edm.String' has a null value, which is not allowed.
   at Microsoft.OData.WriterValidationUtils.ValidateNullPropertyValue(IEdmTypeReference expectedPropertyTypeReference, String propertyName, IEdmModel model)
   at Microsoft.OData.WriterValidator.ValidateNullPropertyValue(IEdmTypeReference expectedPropertyTypeReference, String propertyName, Boolean isTopLevel, IEdmModel model)
   at Microsoft.OData.JsonLight.ODataJsonLightPropertySerializer.WriteNullProperty(ODataProperty property)
   at Microsoft.OData.JsonLight.ODataJsonLightPropertySerializer.WriteProperties(IEdmStructuredType owningType, IEnumerable`1 properties, Boolean isComplexValue, IDuplicatePropertyNameChecker duplicatePropertyNameChecker)
   at Microsoft.OData.JsonLight.ODataJsonLightWriter.StartResource(ODataResource resource)
   at Microsoft.OData.ODataWriterCore.InterceptException(Action action)
   at OdataToEntity.AspNetCore.ODataResult`1.WriteEntry(ODataWriter writer, Object entity, EntityPropertiesInfo& entityPropertiesInfo)
   at OdataToEntity.AspNetCore.ODataResult`1.WriteNavigationProperty(ODataWriter writer, Object value, PropertyInfo navigationProperty)
   at OdataToEntity.AspNetCore.ODataResult`1.WriteEntry(ODataWriter writer, Object entity, EntityPropertiesInfo& entityPropertiesInfo)
   at OdataToEntity.AspNetCore.ODataResult`1.SerializeAsync(ODataWriter writer)
   at OdataToEntity.AspNetCore.ODataResult`1.ExecuteResultAsync(ActionContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultAsync(IActionResult result)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResultFilterAsync[TFilter,TFilterAsync]()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResultExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultFilters()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
   at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)
      Request finished in 259.252ms 200 application/json;odata.metadata=minimal;odata.streaming=true;charset=utf-8

If I adjust the query with either of the following, the query executes successfully

  • Do not apply the $select on the $expand
    • http://localhost:5000/odata/Accounts?$top=50&$expand=Department
  • Filter null Department entities
    • http://localhost:5000/odata/Accounts?$top=50&$expand=Department($select=Name)&$filter=DepartmentId ne null

This led me to believe the combination of $expanding a nullable property and $select was the culprit.

I was able to setup a local database and successfully run your entire test suite, and I attempted to write a failing test to confirm the issue using the existing models

[Theory]
[InlineData(0, false)]
[InlineData(1, false)]
[InlineData(0, true)]
[InlineData(1, true)]
public async Task ExpandNullableNestedSelect(int pageSize, bool navigationNextLink)
{
    var parameters = new QueryParameters<Order>()
    {
        RequestUri = "Orders?$expand=AltCustomers($select=Name)&$orderby=Id",
        Expression = t => t.Include(o => o.AltCustomer).OrderBy(o => o.Id),
        NavigationNextLink = navigationNextLink,
        PageSize = pageSize
    };
    await Fixture.Execute(parameters).ConfigureAwait(false);
}

but I am unable to execute the test due to a cleanup failure I can’t figure out.

image

I think there is a similar issue with ICollection properties, but I haven’t spent as much time trying to dig in and confirm yet.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 30 (30 by maintainers)

Commits related to this issue

Most upvoted comments

For example, in OData/WebApi I can take in the queryOptions and applying them manually in the controller, instead of just applying a [Queryable] attribute. This returns a IQueryable<T> instance which I can continue to add .Where(), .Select(), joins, etc. I use this pattern for 2 cases currently. 1.) Add additional filters to the query based on the user (HttpContext.User.Identity) to restrict the type of results they can return (without using say row level filtering at the database level) 2.) Use the query to produce a different result, such as a data export (build an excel file, csv, etc).

Add support filter and select result