GetDatabaseValues[Async] does not return ComplexCollection properties
- 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
Assessment
This issue has not been assessed yet.