OData / OData/ModelBuilder

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

Open Beginner friendly
#61 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C#
Stars
22
Forks
24
PR merge metrics
No merged PRs in 30d

Description

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

Contributor guide

Open the contributing guide

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.

Research direction

Start at NullableAttributeEdmPropertyConvention.Apply() and inspect how it handles PropertyInfo.PropertyType for Nullable value types. Reproduce the DateOnly? case with ODataConventionModelBuilder, then verify the generated EDM keeps the property nullable and that nullable reference type handling remains unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
api
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.