Skip to content

JADNC does not support read-only navigation properties with backing fields. #1092

@ThomasBarnekow

Description

@ThomasBarnekow

DESCRIPTION

Using backing fields, EF Core supports read-only navigation properties (e.g., Children) as follows (using a variation of the TestResource class already used in issue #1090 to demonstrate the constructor binding issue):

public class TestResource : Identifiable
{
    // Added backing field for navigation property.
    private readonly HashSet<TestResource> _children = new();
    
    // Same as before.
    [Attr]
    public string Name { get; set; }
    
    // Same as before.
    public int? ParentId { get; set; }
    
    // Same as before.
    [HasOne]
    public TestResource Parent { get; set; }
    
    // Changed type, made read-only and return _children.
    [HasMany]
    public IReadOnlyCollection<TestResource> Children
    {
        get => _children;
    
        // Setter required by JADNC (but not EF Core per se). Uncomment to fix the issue.
        // private init => _children = (HashSet<TestResource>) value;
    }
       
    // Added method to add items to _children backing field.
    public void Add(TestResource item)
    {
        _children.Add(item);
        item.Parent = this;
        item.ParentId = Id;
    }
}

Using EF core directly, it is possible to use the Children navigation property to retrieve the children of a "Root" resource, for example (see also the unit test in #1090):

public class RelationalDbIntegrationTests
{
    [Fact]
    public async Task TestRelationalDatabaseAsync()
    {
        await using (var context = new RelationalDbContext())
        {
            await context.Database.EnsureDeletedAsync().ConfigureAwait(false);
            await context.Database.MigrateAsync().ConfigureAwait(false);

            // This is the change required for the new API
            var root = new TestResource { Name = "Root" };
            root.Add(new TestResource { Name = "First" });
            root.Add(new TestResource { Name = "Second" });    
            context.Add(root);

            await context.SaveChangesAsync().ConfigureAwait(false);
        }

        await using (var context = new RelationalDbContext())
        {
            // Establish that we can read the resource using EF core.
            TestResource resource = await context.TestResources
                .Include(testResource => testResource.Children)
                .SingleAsync(testResource => testResource.Name == "Root");

            Assert.NotEmpty(resource.Children);
        }
    }
}

However, when using JADNC, the following request will not include any children:

  • https://localhost:5001/TestResources?include=children

When uncommenting private init => _children = (HashSet<TestResource>) value; as indicated above, everything works as desired (children are returned).

STEPS TO REPRODUCE

  1. Change the TestResource class as indicated above (without uncommenting the setter/initializer).
  2. Run the unit test described above to confirm children are indeed returned by EF Core.
  3. Issue the above request (https://localhost:5001/TestResources?include=children) and note that no children are included.
  4. Uncomment the setter/initializer. Run the unit test to confirm children are still returned by EF Core. Issue the above request again and note that children are now returned as expected.

EXPECTED BEHAVIOR

JADNC supports read-only navigation properties with backing fields.

ACTUAL BEHAVIOR

JADNC supports requires at least a private initializer or setter.

VERSIONS USED

  • JsonApiDotNetCore version: 4.2.0
  • ASP.NET Core version: 5.0.10
  • Entity Framework Core version: 5.0.10
  • Database provider: SQL Server Express LocalDb

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