OData / OData/AspNetCoreOData

$select-ing non-key properties inside $expand of contained entity with odata.metadata=full fails despite being autoselected

Open
#1,265 2 comments 0 reactions 1 assignee View on GitHub

@ElizabethOkerio is already working on this.

Since Jul 2, 2024.

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

Description

Assemblies affected
ASP.NET Core OData 8.2.4

Describe the bug
$expanding a contained navigation and $selecting other properties except the keys throws:

Microsoft.OData.ODataException: The entity instance value of type '...' doesn't have a value for property '...'. To compute an entity's metadata, its key and concurrency-token property values must be provided.

Reproduce steps
A GET request to:

https://.../ContactForms?$expand=Emails($select=Locale)

with header:

Accept application/json;odata.metadata=full

Throws:

Microsoft.OData.ODataException: The entity instance value of type 'Models.Email.ContactFormEmail' doesn't have a value for property 'Id'. To compute an entity's metadata, its key and concurrency-token property values must be provided.
12:22:13:466	   at Microsoft.OData.Evaluation.ODataResourceMetadataContext.TryGetPrimitiveOrEnumPropertyValue(ODataResourceBase resource, String propertyName, String entityTypeName, Boolean isRequired, Object& value)
12:22:13:466	   at Microsoft.OData.Evaluation.ODataResourceMetadataContext.GetPropertyValues(IEnumerable`1 properties, ODataResourceBase resource, IEdmEntityType actualEntityType, Boolean isRequired)+MoveNext()
12:22:13:466	   at System.Collections.Generic.LargeArrayBuilder`1.AddRange(IEnumerable`1 items)
12:22:13:466	   at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source)
12:22:13:466	   at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
12:22:13:466	   at Microsoft.OData.Evaluation.ODataResourceMetadataContext.ODataResourceMetadataContextWithModel.get_KeyProperties()
12:22:13:466	   at Microsoft.OData.Evaluation.ODataConventionalIdMetadataBuilder.ComputeIdForContainment()
12:22:13:466	   at Microsoft.OData.Evaluation.ODataConventionalIdMetadataBuilder.ComputeAndCacheId()
12:22:13:466	   at Microsoft.OData.Evaluation.ODataConventionalIdMetadataBuilder.get_ComputedId()
12:22:13:466	   at Microsoft.OData.Evaluation.ODataConventionalIdMetadataBuilder.GetId()
12:22:13:466	   at Microsoft.OData.Evaluation.ODataConventionalEntityMetadataBuilder.TryGetIdForSerialization(Uri& id)
12:22:13:466	   at Microsoft.OData.JsonLight.ODataJsonLightResourceSerializer.WriteResourceStartMetadataPropertiesAsync(IODataJsonLightWriterResourceState resourceState)
12:22:13:466	   at Microsoft.OData.JsonLight.ODataJsonLightWriter.StartResourceAsync(ODataResource resource)
12:22:13:466	   at Microsoft.OData.ODataWriterCore.<>c.<<WriteStartResourceImplementationAsync>b__196_0>d.MoveNext()
12:22:13:466	--- End of stack trace from previous location ---
12:22:13:466	   at Microsoft.OData.ODataWriterCore.InterceptExceptionAsync[TArg0](Func`3 action, TArg0 arg0)
12:22:13:466	   at Microsoft.OData.ODataWriterCore.WriteStartResourceImplementationAsync(ODataResource resource)
12:22:13:466	   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteResourceAsync(Object graph, ODataWriter writer, ODataSerializerContext writeContext, IEdmTypeReference expectedType)
12:22:13:466	   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteObjectInlineAsync(Object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext)
12:22:13:466	   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteResourceSetItemAsync(Object item, IEdmStructuredTypeReference elementType, Boolean isUntypedCollection, IEdmTypeReference resourceSetType, ODataWriter writer, IODataEdmTypeSerializer resourceSerializer, ODataSerializerContext writeContext)
12:22:13:466	   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteResourceSetAsync(IEnumerable enumerable, IEdmTypeReference resourceSetType, ODataWriter writer, ODataSerializerContext writeContext)
12:22:13:466	   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteObjectInlineAsync(Object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext)
12:22:13:466	   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteComplexAndExpandedNavigationPropertyAsync(IEdmProperty edmProperty, SelectItem selectItem, ResourceContext resourceContext, ODataWriter writer)
12:22:13:466	   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteExpandedNavigationPropertiesAsync(SelectExpandNode selectExpandNode, ResourceContext resourceContext, ODataWriter writer)
12:22:13:466	   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteResourceAsync(Object graph, ODataWriter writer, ODataSerializerContext writeContext, IEdmTypeReference expectedType)
12:22:13:466	   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteObjectInlineAsync(Object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext)
12:22:13:466	   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteResourceSetItemAsync(Object item, IEdmStructuredTypeReference elementType, Boolean isUntypedCollection, IEdmTypeReference resourceSetType, ODataWriter writer, IODataEdmTypeSerializer resourceSerializer, ODataSerializerContext writeContext)
12:22:13:466	   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteResourceSetAsync(IEnumerable enumerable, IEdmTypeReference resourceSetType, ODataWriter writer, ODataSerializerContext writeContext)
12:22:13:466	   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteObjectInlineAsync(Object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext)
12:22:13:466	   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteObjectAsync(Object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)
12:22:13:466	   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)
12:22:13:466	   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|30_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
12:22:13:466	   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
12:22:13:466	   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
12:22:13:466	   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
12:22:13:466	--- End of stack trace from previous location ---
12:22:13:466	   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
12:22:13:466	   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
12:22:13:466	   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
12:22:13:466	   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
12:22:13:466	   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
12:22:13:466	   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
12:22:13:466	   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
12:22:13:466	   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
12:22:13:466	--- End of stack trace from previous location ---
12:22:13:466	   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
12:22:13:466	--- End of stack trace from previous location ---
12:22:13:466	   at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
12:22:13:466	   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
12:22:13:466	   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
12:22:13:466	   at Microsoft.AspNetCore.OData.Routing.ODataRouteDebugMiddleware.Invoke(HttpContext context)
12:22:13:466	   at Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context)
12:22:13:466	   at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)

Data Model

public class Language{
    public string LanguageCode { get; set; }
}

public class ContactFormEmail {
    public int Id { get; set; }

    ...

    public Language? Locale { get; set; }

    // Reverse navigation
    public ContactForm Form { get; set; }
}

public class ContactForm {
    public int Id { get; set; }

    ...

    public ICollection<ContactFormEmail> Emails { get; set; }
}

EDM (CSDL) Model

...
<EntityType Name="ContactForm">
    <Key>
        <PropertyRef Name="Id" />
    </Key>
    <Property Name="Id" Type="Edm.Int32" Nullable="false" />
    ...
    <NavigationProperty Name="Emails" Type="Collection(Models.Email.ContactFormEmail)" ContainsTarget="true" />
</EntityType>
<EntityType Name="ContactFormEmail">
    <Key>
        <PropertyRef Name="Id" />
    </Key>
    <Property Name="Id" Type="Edm.Int32" Nullable="false" />
    ...
    <Property Name="Locale" Type="Models.Language" />
    <NavigationProperty Name="Form" Type="Models.Email.ContactForm" Nullable="false" />
</EntityType>
<ComplexType Name="Language">
    <Property Name="LanguageCode" Type="Edm.String" Nullable="false" />
</ComplexType>
...
<EntitySet Name="ContactForms" EntityType="Models.Email.ContactForm">
    <NavigationPropertyBinding Path="Emails/Form" Target="ContactForms" />
    ...
</EntitySet>
...

Additional context
From the resulting query it looks like the key properties are autoselected as they should but they are not being used.

DbSet<ContactForm>()
    .AsSplitQuery()
    .AsNoTrackingWithIdentityResolution()
    .Select($it => new SelectAllAndExpand<ContactForm>{ 
        Model = TypedLinqParameterContainer<IEdmModel>.TypedProperty, 
        Instance = $it, 
        UseInstanceForProperties = True, 
        Container = new NamedProperty<IEnumerable<SelectSome<ContactFormEmail>>>{ 
            Name = "Emails", 
            Value = $it.Emails
                .Select($it => new SelectSome<ContactFormEmail>{ 
                    Model = TypedLinqParameterContainer<IEdmModel>.TypedProperty, 
                    Container = new SingleExpandedPropertyWithNext0<SelectAll<Language>>{ 
                        Name = "Locale", 
                        Value = new SelectAll<Language>{ 
                            Model = TypedLinqParameterContainer<IEdmModel>.TypedProperty, 
                            Instance = $it.Locale, 
                            UseInstanceForProperties = True 
                        }
                        , 
                        Next0 = new AutoSelectedNamedProperty<int?>{ 
                            Name = "Id", 
                            Value = (int?)$it.Id 
                        }
                        , 
                        IsNull = $it.Locale == null 
                    }
                     
                }
                ) 
        }
         
    }
    )

If I explicitly select the key, the results are correctly returned

https://.../ContactForms?$expand=Emails($select=Id,Locale)

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.