dotnet / dotnet/efcore

GetDatabaseValues[Async] does not return ComplexCollection properties

Open
#38,938 1 comment 1 reaction 1 assignee Claimed by @AndriySvyryd View on GitHub
area-complex-types customer-reported
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

### 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](https://github.com/thepirat000/Audit.NET/issues/798) for Audit.NET where it [was discovered/noted](https://github.com/thepirat000/Audit.NET/issues/798#issuecomment-5311404855) 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 https://github.com/dotnet/efcore/issues/38486 but again, I am not certain

### Your code

Project File

```xml


Exe
net10.0
enable
enable





```

Entities and Context

```csharp
public sealed class TestContext(DbContextOptions opts) : DbContext(opts)
{
public DbSet Things => Set();

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity(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 Items { get; set; } = null!;
}

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

Using the following Program:

Program

```csharp
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()
.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() : "";

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

### Verbose output

The attached program produces the following output:

```text
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:

===DEBUG===
DB _complexCollectionValues.Count: 1
DB _complexCollectionValues[0].Count=
```

That said, I would expect the following:

```text
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

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.