Skip to content

Issue when trying to update some entities that are in a many to many relationships with a favourite child #37310

@akilin

Description

@akilin

Bug description

Hi, I have encountered what I think is a bug when trying to update some entities that are in a many to many relationships with a favourite child.

I have 3 entities: User, Group, GroupMember.
Group also has a GroupOwner field referencing GroupMember.

I am trying to perform an update that will change list of members & change the ownership of the group.
Both operations work fine when done in isolation. But attempting to perform them in one go results in an error, and a weird one at that.

The code below throws:

System.InvalidOperationException: 'The property 'Group.Id' is defined as read-only after it has been saved,
but its value has been modified or marked as modified.'

even tho Group.Id was never touched.

group.Members = [new() { UserId = 1, GroupId = 1 }];
group.GroupOwnerId = 1;
ctx.SaveChanges();

repo with reproduction code:
https://github.com/akilin/demos/tree/master/ef-many-to-many-updates

steps to reproduce locally:

git clone https://github.com/akilin/demos
cd demos
cd .\ef-many-to-many-updates\
docker-compose up -d
dotnet run

Your code

Full code in a single file can be seen here (this is a simplified reproduction of what we are observing in our real app):
https://github.com/akilin/demos/blob/master/ef-many-to-many-updates/Program.cs

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;

namespace ef_many_to_many_updates
{
    static class Program
    {
        static void Main(string[] args)
        {
            // reset db
            using var ctx = new AppContext();
            ctx.Database.EnsureDeleted();
            ctx.Database.EnsureCreated();

            // arrange
            ctx.Groups.Add(new Group { Id = 1 });
            ctx.Users.Add(new User { Id = 1 });
            ctx.GroupMembers.Add(new GroupMember { UserId = 1, GroupId = 1 });
            ctx.SaveChanges();

            ctx.ChangeTracker.Clear();

            // act
            var group = ctx.Groups.Include(x => x.Members).Single();
            //either of the 2 lines below work fine on their own. but combined - they cause the issue
            group.Members = [new() { UserId = 1, GroupId = 1 }];
            group.GroupOwnerId = 1;
            ctx.SaveChanges();
        }
    }

    public class User
    {
        public int Id { get; set; }
        public ICollection<GroupMember> Groups { get; set; } = null!;
    }

    public class Group
    {
        public int Id { get; set; }
        public int? GroupOwnerId { get; set; }
        public GroupMember? GroupOwner { get; set; }
        public ICollection<GroupMember> Members { get; set; } = null!;
    }

    public class GroupMember
    {
        public int GroupId { get; set; }
        public Group Group { get; set; } = null!;

        public int UserId { get; set; }
        public User User { get; set; } = null!;
    }

    public class AppContext : DbContext
    {
        public DbSet<User> Users { get; set; }
        public DbSet<Group> Groups { get; set; }
        public DbSet<GroupMember> GroupMembers { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            => optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging()
            .EnableDetailedErrors()
            .UseNpgsql("Host=localhost;Database=test;Username=guest;Password=pwd");

        protected override void OnModelCreating(ModelBuilder mb)
        {
            mb.Entity<Group>()
                .HasOne(x => x.GroupOwner)
                .WithMany()
                .HasForeignKey(x => new { x.Id, x.GroupOwnerId });

            mb.Entity<GroupMember>().HasKey(x => new { x.GroupId, x.UserId });

            mb.Entity<GroupMember>()
                .HasOne(x => x.User)
                .WithMany(x => x.Groups)
                .HasForeignKey(x => new { x.UserId });

            mb.Entity<GroupMember>()
                .HasOne(x => x.Group)
                .WithMany(x => x.Members)
                .HasForeignKey(x => new { x.GroupId });
        }
    }
}

Stack traces

CoreEventId.SaveChangesFailed[10000] (Microsoft.EntityFrameworkCore.Update) 
      An exception occurred in the database while saving changes for context type 'ef_many_to_many_updates.AppContext'.
      System.InvalidOperationException: The property 'Group.Id' is defined as read-only after it has been saved, but its value has been modified or marked as modified.
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntryBase.PrepareToSave()       
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.GetEntriesToSave(Boolean cascadeChanges)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(StateManager stateManager, Boolean acceptAllChangesOnSuccess)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.<>c.<SaveChanges>b__114_0(DbContext _, ValueTuple`2 t)
         at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
         at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)        
Unhandled exception. System.InvalidOperationException: The property 'Group.Id' is defined as read-only after it has been saved, but its value has been modified or marked as modified.
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntryBase.PrepareToSave()
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.GetEntriesToSave(Boolean cascadeChanges)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(StateManager stateManager, Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.<>c.<SaveChanges>b__114_0(DbContext _, ValueTuple`2 t)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges()
   at ef_many_to_many_updates.Program.Main(String[] args) in C:\git\demos\ef-many-to-many-updates\Program.cs:line 31

EF Core version

10.0.0

Database provider

Npgsql.EntityFrameworkCore.PostgreSQL

Target framework

dotnet10

Operating system

win11

IDE

visual studio 2026

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions