Skip to content

GetDatabaseValues[Async] does not return ComplexCollection properties #38938

Description

@pinkfloydx33

Bug description

GetDatabaseValues does not return the values of complex collection properties. Trying to access the value of the property via the indexer always returns null. Inspecting the returned ArrayPropertyValues object in the debugger, shows a _complexCollectionValues field with one, null sub-list inside of it.

This came up in the context of a feature request for Audit.NET where it was discovered/noted that GetDatabaseValues does not return the values of complex collections.

Honestly I am not sure if this is a bug or entirely expected/unimplemented. I spent a while searching around the issues but did not find anything that seemed to match. It might be related to #38486 but again, I am not certain

Your code

Project File
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net10.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Npgsql" Version="10.0.3" />
        <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.3" />
        <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.11" />
    </ItemGroup>

</Project>
Entities and Context
public sealed class TestContext(DbContextOptions<TestContext> opts) : DbContext(opts)
{
    public DbSet<Test> Things => Set<Test>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Test>(e =>
        {
            e.ToTable("things");
            e.HasKey(p => p.Id);
            e.ComplexCollection(p => p.Items, p =>
            {
                p.ToJson();
                p.IsRequired();
                p.Property(c => c.Title).IsRequired();
            });
        });
    }
}

public sealed class Test
{
    public int Id { get; set; }
    public List<ComplexItem> Items { get; set; } = null!;
}

public class ComplexItem
{
    public string Title { get; set; } = null!;
}

Using the following Program:

Program
using System.Collections;
using System.Runtime.CompilerServices;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;

const string pgconn = "Host=localhost;Port=5432;Database=repro;Username=postgres;Password=postgres";

var opts = new DbContextOptionsBuilder<TestContext>()
    .UseNpgsql(pgconn, s => s.SetPostgresVersion(new Version(17, 0)))
    .Options;

await using (var ctx = new TestContext(opts))
{
    await ctx.Database.EnsureDeletedAsync();
    await ctx.Database.EnsureCreatedAsync();
    var newItem = new Test
    {
        Id = 1, Items = [ new ComplexItem { Title = "Title1" } ]
    };
    ctx.Things.Add(newItem);
    await ctx.SaveChangesAsync();

    var entry = ctx.Entry(newItem);
    var origVal = entry.OriginalValues[nameof(Test.Items)];
    var currVal = entry.CurrentValues[nameof(Test.Items)];
    
    Console.WriteLine("After Initial Save Change");
    Console.WriteLine("Original Items.Count: {0}", GetCount(origVal));
    Console.WriteLine("Current Items.Count: {0}", GetCount(currVal));
    Console.WriteLine();
}

await using (var ctx = new TestContext(opts))
{
    var thing = await ctx.Things.SingleAsync();

    thing.Items.Add(new ComplexItem { Title = "TestAdd" });

    var entry = ctx.Entry(thing);
    var origVal = entry.OriginalValues[nameof(Test.Items)];
    var currVal = entry.CurrentValues[nameof(Test.Items)];

    Console.WriteLine("After Local Modification");
    Console.WriteLine("Original Items.Count: {0}", GetCount(origVal));
    Console.WriteLine("Current Items.Count: {0}", GetCount(currVal));

    var dbValues = (await entry.GetDatabaseValuesAsync())!;
    var dbVal = dbValues[nameof(Test.Items)];

    Console.WriteLine("DB Items.Count: {0}", GetCount(dbVal));
    Console.WriteLine();

    Console.WriteLine("===DEBUG===");
    var complexCollectionValues = GetCollection((ArrayPropertyValues)dbValues);
    Console.WriteLine("DB _complexCollectionValues.Count: {0}", complexCollectionValues.Length);
    for (var i = 0; i < complexCollectionValues.Length; ++i)
        Console.WriteLine("DB _complexCollectionValues[{0}].Count={1}", i, GetCount(complexCollectionValues[i]));
}

static string GetCount(object? o) => o is IList l ? l.Count.ToString() : "<null>";

[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_complexCollectionValues")]
static extern ref List<ArrayPropertyValues?>?[] GetCollection(ArrayPropertyValues obj);

Verbose output

The attached program produces the following output:

After Initial Save Change
Original Items.Count: 1
Current Items.Count: 1

After Local Modification
Original Items.Count: 1
Current Items.Count: 2
DB Items.Count: <null>

===DEBUG===
DB _complexCollectionValues.Count: 1
DB _complexCollectionValues[0].Count=<null>

That said, I would expect the following:

After Initial Save Change
Original Items.Count: 1
Current Items.Count: 1

After Local Modification
Original Items.Count: 1
Current Items.Count: 2
DB Items.Count: 1 <----- non-null!

===DEBUG===
DB _complexCollectionValues.Count: 1
DB _complexCollectionValues[0].Count=1  <---- non-null

EF Core version

10.0.11

Database provider

Npgsql.EntityFrameworkCore.PostgreSQL (but I don't think it matters)

Target framework

.NET 10

Operating system

Windows 11

IDE

Rider, VS 2026

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions