Skip to content

Deleting an item from a ComplexCollection, that contains an array results in Error #37585

@bblankenship78

Description

@bblankenship78

Bug description

This may be resolved with the same fix as #37377; however, posting to be sure.

When deleting an item from a ComplexCollection, that Contains an array, an error is thrown.

Using the code below, calling SaveChanges(), results in an exception. What is unusual, is the save still goes through and the database result is correct.

Your code

#:package Microsoft.EntityFrameworkCore@10.0.2
#:package Npgsql.EntityFrameworkCore.PostgreSQL@10.0.0
#:package Testcontainers@4.10.0

using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Containers;
using EfCore10Regression.Tests;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Npgsql;
using static EfCore10Regression.Tests.Models;

var fixture = new PostgresContainerFixture();
await fixture.InitializeAsync();
Console.WriteLine($"Postgres started. ConnectionString={fixture.ConnectionString}");
Console.WriteLine($"Logging SQL to: {fixture.LogFilePath}");

await using (var ctx = fixture.CreateContext())
{
    var modelBs = new List<ModelB>
    {
        new() {
            Id = 1,
            ModelCs = []
        }
    };

    ctx.Models.Add(new ModelA
    {
        Id = 0,
        ModelBs = modelBs
    });

    await ctx.SaveChangesAsync();
}

await using var context = fixture.CreateContext();

var rawData = await context.Models.SingleAsync();
Console.WriteLine($"Nbr items before {rawData.ModelBs.Count}");

rawData.ModelBs = [.. rawData.ModelBs.Where(x => x.Id != 1)];
Console.WriteLine($"Nbr items after {rawData.ModelBs.Count}");

await context.SaveChangesAsync();
Console.WriteLine("SUCCESS!!");
Console.ReadLine();

await fixture.DisposeAsync();

namespace EfCore10Regression.Tests
{
    public class RegressionDbContext(DbContextOptions<RegressionDbContext> options) : DbContext(options)
    {
        public DbSet<Models.ModelA> Models => Set<Models.ModelA>();

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Models.ModelA>(b => b.ComplexCollection(c => c.ModelBs, c =>
            {
                c.ToJson();
            }));
        }
    }

    public static class Models
    {
        public class ModelA
        {
            public int Id { get; set; }
            public List<ModelB> ModelBs { get; set; } = [];
        }

        public class ModelB
        {
            public int Id { get; set; }
            public List<ModelC> ModelCs { get; set; } = [];
        }

        public record ModelC
        {
            public Guid Id { get; set; }
        }
    }

    public class PostgresContainerFixture : IAsyncLifetime
    {
        private readonly IContainer _pgContainer;
        private StreamWriter? _logWriter;
        public string ConnectionString { get; private set; } = string.Empty;
        public string? LogFilePath { get; private set; }

        public PostgresContainerFixture()
        {
            _pgContainer = new ContainerBuilder("postgis/postgis:latest")
                .WithPortBinding(5432, assignRandomHostPort: true)
                .WithEnvironment("POSTGRES_PASSWORD", "postgres")
                .WithEnvironment("POSTGRES_USER", "postgres")
                .WithEnvironment("POSTGRES_DB", "testdb")
                .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(5432))
                .Build();
        }

        public async Task InitializeAsync()
        {
            await _pgContainer.StartAsync();
            var host = _pgContainer.Hostname;
            var port = _pgContainer.GetMappedPublicPort(5432);
            ConnectionString = new NpgsqlConnectionStringBuilder
            {
                Host = host,
                Port = port,
                Username = "postgres",
                Password = "postgres",
                Database = "testdb"
            }.ToString();

            LogFilePath = Path.Combine(Path.GetTempPath(), $"efcore-sql-{Guid.NewGuid():N}.log");
            _logWriter = new StreamWriter(File.Open(LogFilePath, FileMode.Create, FileAccess.Write, FileShare.Read))
            {
                AutoFlush = true
            };
        }

        public async Task DisposeAsync()
        {
            await _pgContainer.StopAsync();
            await _pgContainer.DisposeAsync();
            _logWriter?.Dispose();
        }

        public RegressionDbContext CreateContext()
        {
            var optionsBuilder = new DbContextOptionsBuilder<RegressionDbContext>()
                .UseNpgsql(ConnectionString, a => a.UseNetTopologySuite())
                .EnableSensitiveDataLogging()
                .EnableDetailedErrors();

            if (_logWriter is not null)
            {
                optionsBuilder.LogTo(_logWriter.WriteLine, LogLevel.Information);
            }

            var ctx = new RegressionDbContext(optionsBuilder.Options);
            ctx.Database.EnsureCreated();
            return ctx;
        }
    }

    public interface IAsyncLifetime
    {
        Task InitializeAsync();
        Task DisposeAsync();
    }
}

Stack traces

Unhandled exception. System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')
   at lambda_method39(Closure, ModelA, IReadOnlyList`1)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntryBase.ReadPropertyValue(IPropertyBase propertyBase)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalComplexEntry.ReadPropertyValue(IPropertyBase propertyBase)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntryBase.get_Item(IPropertyBase propertyBase)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntryBase.InternalComplexCollectionEntry.SetState(EntityState oldState, EntityState newState, Boolean acceptChanges, Boolean modifyProperties)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntryBase.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges, Boolean modifyProperties)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalComplexEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges, Boolean modifyProperties)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntryBase.SetEntityState(EntityState entityState, Boolean acceptChanges, Boolean modifyProperties, Nullable`1 forceStateWhenUnknownKey, Nullable`1 fallbackState)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntryBase.AcceptChanges()
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalComplexEntry.AcceptChanges()
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntryBase.InternalComplexCollectionEntry.AcceptChanges()
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntryBase.AcceptChanges()
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.AcceptChanges()
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.AcceptAllChanges(IReadOnlyList`1 changedEntries)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Program.<Main>$(String[] args) in C:\Users\brad\Downloads\EfCore10Regression.Tests_TB\EfCore10Regression.Tests\QueryRegressionTests.cs:line 45
   at Program.<Main>$(String[] args) in C:\Users\brad\Downloads\EfCore10Regression.Tests_TB\EfCore10Regression.Tests\QueryRegressionTests.cs:line 49
   at Program.<Main>(String[] args)

Verbose output


EF Core version

10.0.2

Database provider

Npgsql.EntityFrameworkCore.PostgreSQL

Target framework

.NET 10

Operating system

No response

IDE

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No fields configured for Bug.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions