Skip to content

NullableAttributeEdmPropertyConvention incorrectly marks Nullable<T> value type properties as non-nullable #61

Description

@sergiojrdotnet

Description

ODataConventionModelBuilder incorrectly marks Nullable<T> value type properties (e.g. DateOnly?, int?, TimeOnly?) as Nullable="false" in the EDM model. This is a regression introduced by the NRT (Non-nullable Reference Types) convention support added in #16 / PR #17.

Root Cause

The NullableAttributeEdmPropertyConvention misinterprets the C# compiler's [Nullable] attribute for value types:

  1. Initial default is correctStructuralPropertyConfiguration constructor calls EdmLibHelpers.IsNullable(), which correctly returns true for Nullable<T> value types.

  2. Convention overrides itNullableAttributeEdmPropertyConvention.Apply() reads the [Nullable] attribute emitted by the C# compiler. For Nullable<DateOnly> in a #nullable enable context, the compiler emits NullableAttribute with flag 1 (non-nullable), because value type nullability is expressed through Nullable<T> in the CLR type system, not through NRT annotations. The convention interprets flag 1 as "make it non-nullable," overriding the correct default.

Reference types (e.g. string?) are not affected because their [Nullable] flags correctly indicate nullability (2 = nullable).

Reproduce

#nullable enable

public sealed record PersonResponse
{
    public required Guid Id { get; init; }
    public required string FirstName { get; init; }
    public DateOnly? DateOfBirth { get; init; }  // <-- incorrectly becomes Nullable="false"
    public string? Email { get; init; }           // <-- correctly remains nullable
}

var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<PersonResponse>("persons");
var model = modelBuilder.GetEdmModel();

Expected metadata

<Property Name="DateOfBirth" Type="Edm.Date" />
<!-- or explicitly: Nullable="true" -->

Actual metadata

<Property Name="DateOfBirth" Type="Edm.Date" Nullable="false" />

Impact

When ASP.NET Core OData registers input formatters, the non-nullable EDM property causes the OData deserializer to reject null or missing values for DateOnly? properties in POST/PUT/PATCH requests, even though the CLR type explicitly allows null.

Workaround

Use OnModelCreating to globally fix all Nullable<T> value type properties after conventions run:

var modelBuilder = new ODataConventionModelBuilder
{
    OnModelCreating = builder =>
    {
        foreach (var type in builder.StructuralTypes)
        {
            foreach (var property in type.Properties)
            {
                if (property is PrimitivePropertyConfiguration primitive
                    && Nullable.GetUnderlyingType(property.PropertyInfo.PropertyType) is not null)
                {
                    primitive.IsNullable();
                }
            }
        }
    },
};

Suggested Fix

In NullableAttributeEdmPropertyConvention.Apply(), skip properties whose PropertyInfo.PropertyType is a Nullable<T> value type, since their nullability is already correctly determined by EdmLibHelpers.IsNullable() from the CLR type system.

Environment

  • Microsoft.OData.ModelBuilder 2.0.0
  • Microsoft.AspNetCore.OData 9.4.1
  • .NET 10 (also reproducible on .NET 8/9)
  • #nullable enable context

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions