-
Notifications
You must be signed in to change notification settings - Fork 182
Description
Assemblies affected
ASP.NET Core OData 8.2.2
.NET 7
EF Core 7.0.10
Describe the bug
When expanding a navigation property that can be nullable, and the result set returns at least one record that doesn't have a value set for this optional navigation (null), the following error is returned: Nullable object must have a value.
This works in version 8.2.0, but is broken in both the version listed above and 8.2.1.
Reproduce steps
- Have a navigation property on your data model that is a related entity that is optional (nullable).
- Either configure the property on the EDM entity type to be automatically expanded, or just add the property to your expand query parameter.
- Ensure the result set returns at least one record that doesn't have a value set for this optional navigation.
Data Model
public class User
{
public User(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
// Foreign Keys
public Guid? VehicleId { get; set; }
// Navigations
public Vehicle? PrimaryVehicle { get; set; }
}EDM (CSDL) Model
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
<edmx:DataServices>
<Schema Namespace="ODataSample.Data" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityType Name="User">
<Key>
<PropertyRef Name="id" />
</Key>
<Property Name="id" Type="Edm.Guid" Nullable="false" />
<Property Name="firstName" Type="Edm.String" Nullable="false" />
<Property Name="lastName" Type="Edm.String" Nullable="false" />
<Property Name="age" Type="Edm.Int32" Nullable="false" />
<Property Name="vehicleId" Type="Edm.Guid" />
<NavigationProperty Name="primaryVehicle" Type="ODataSample.Data.Vehicle">
<ReferentialConstraint Property="vehicleId" ReferencedProperty="id" />
</NavigationProperty>
</EntityType>
<EntityType Name="Vehicle">
<Key>
<PropertyRef Name="id" />
</Key>
<Property Name="id" Type="Edm.Guid" Nullable="false" />
<Property Name="make" Type="Edm.String" Nullable="false" />
<Property Name="model" Type="Edm.String" Nullable="false" />
<Property Name="color" Type="Edm.String" Nullable="false" />
</EntityType>
</Schema>
<Schema Namespace="Default" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityContainer Name="Container">
<EntitySet Name="Users" EntityType="ODataSample.Data.User">
<NavigationPropertyBinding Path="primaryVehicle" Target="Vehicles" />
</EntitySet>
<EntitySet Name="Vehicles" EntityType="ODataSample.Data.Vehicle" />
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>public static IEdmModel GetEdmModelV1()
{
var builder = new ODataConventionModelBuilder();
builder.EnableLowerCamelCase();
// Users
var userSet = builder.EntitySet<User>("Users");
var userType = userSet.EntityType;
userType.HasKey(t => t.Id);
userType.Property(t => t.FirstName);
userType.Property(t => t.LastName);
userType.Property(t => t.Age);
userType.Property(t => t.VehicleId);
userType.HasOptional(t => t.PrimaryVehicle);
// Vehicles
var vehicleSet = builder.EntitySet<Vehicle>("Vehicles");
var vehicleType = vehicleSet.EntityType;
vehicleType.HasKey(t => t.Id);
vehicleType.Property(t => t.Make);
vehicleType.Property(t => t.Model);
vehicleType.Property(t => t.Color);
return builder.GetEdmModel();
}Request/Response
Request Uri:
Response:
Status Code: 500 - Nullable object must have a value.
Expected behavior
Expected behavior is to return a response of the queried records that have the optional navigation set if they contain a value and for records that don't have a value set to also be returned. This would make querying work with optional navigations work like it did in version 8.2.0.
Additional context
System.InvalidOperationException: Nullable object must have a value.
at System.Nullable1.get_Value() at lambda_method18(Closure, QueryContext, DbDataReader, ResultContext, SplitQueryResultCoordinator) at Microsoft.EntityFrameworkCore.Query.Internal.SplitQueryingEnumerable1.AsyncEnumerator.MoveNextAsync()
at System.Text.Json.Serialization.Converters.IAsyncEnumerableOfTConverter2.OnWriteResume(Utf8JsonWriter writer, TAsyncEnumerable value, JsonSerializerOptions options, WriteStack& state) at System.Text.Json.Serialization.JsonCollectionConverter2.OnTryWrite(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.JsonConverter1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state) at System.Text.Json.Serialization.JsonConverter1.WriteCore(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.JsonConverter`1.WriteCoreAsObject(Utf8JsonWriter writer, Object value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.JsonSerializer.WriteCore[TValue](Utf8JsonWriter writer, TValue& value, JsonTypeInfo jsonTypeInfo, WriteStack& state)
at System.Text.Json.JsonSerializer.WriteStreamAsync[TValue](Stream utf8Json, TValue value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken)
at System.Text.Json.JsonSerializer.WriteStreamAsync[TValue](Stream utf8Json, TValue value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken)
at System.Text.Json.JsonSerializer.WriteStreamAsync[TValue](Stream utf8Json, TValue value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|30_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|28_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)