dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.65k stars 3.15k forks source link

Adding a join entity instance doesn't populate collections on the participating entities #23659

Closed andleer closed 3 years ago

andleer commented 3 years ago

I don't know if this is a bug or am I missing something?

Two simple entities that are connected with a simple mapping table that contains a single payload item. Both (non-mapping) entities contain a collection of the opposing entity and a collection of the common mapping entity. If I add one of the non-mapping entities to the other's collection and attach the "base" entity to the context, the context mapping dbset is correctly populated but I am unable to default the mapping entity's payload in code or via fluent config.

I have tried populating the mapping entity along with the payload value and adding that to the context dbset and saving changes on the entity but the simple many to many collections on the non-mapping entities are not populated.

Seems like a catch 22.

  1. How do I set a payload value? Can it be set dynamically at runtime?
  2. If I need to build by graph via the mapping entity, why aren't the non-mapping collections updated?

Repo with tests: https://github.com/andleer/BinaryOcean.EFCore5

EF Core version: 5.0.1 Database provider: Microsoft.EntityFrameworkCore.InMemory (5.0.1) Target framework: .NET 5.0 Operating system: Win10 IDE: Microsoft Visual Studio Community 2019 Version 16.8.3

roji commented 3 years ago

I've already investigated this to some extent, will soon paste a simplified repro on what is likely a bug.

roji commented 3 years ago

The issue here seems to be that inserting a M2M join entity (PlayerGame) referencing new instances (Playere, Game) does not cause the navigation properties on the instances to be populated. In contrast, when inserting a Player with a Game in its Games list, the Game's Players list is populated.

@andleer as a workaround, you can reload the Player/Game from the database (see the use ChangerTracker.Clear below).

Minimal repro ```c# var player = new Player { Name = "Andrew", }; var game = new Game { Name = "Rocket League", }; var playerGame = new PlayerGame { Player = player, Game = game }; ctx.PlayerGames.Add(playerGame); await ctx.SaveChangesAsync(); Console.WriteLine(player.Games is null); // True ctx.ChangeTracker.Clear(); player = await ctx.Players.Include(p => p.Games).SingleAsync(); Console.WriteLine(player.Games is null); // False public class BlogContext : DbContext { public DbSet Players { get; set; } public DbSet Games { get; set; } public DbSet PlayerGames { get; set; } static ILoggerFactory ContextLoggerFactory => LoggerFactory.Create(b => b.AddConsole().AddFilter("", LogLevel.Information)); protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder .UseSqlServer(@"...") .EnableSensitiveDataLogging() .UseLoggerFactory(ContextLoggerFactory); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity() .HasMany(e => e.Games) .WithMany(e => e.Players) .UsingEntity ( e => e.HasOne(k => k.Game).WithMany(k => k.PlayerGames), e => e.HasOne(k => k.Player).WithMany(k => k.PlayerGames) ); } } public class Player { public int PlayerId { get; set; } public string Name { get; set; } public virtual ICollection Games { get; set; } public virtual ICollection PlayerGames { get; set; } } public class Game { public int GameId { get; set; } public string Name { get; set; } public virtual ICollection Players { get; set; } public virtual ICollection PlayerGames { get; set; } } public class PlayerGame { public int PlayerId { get; set; } public int GameId { get; set; } public virtual Player Player { get; set; } public virtual Game Game { get; set; } } ```
andleer commented 3 years ago

I agree with your assessment. Sorry I didn't spell that out before but it is pretty simple and I already have been using that as a workaround.

But are you confirming it is a bug or possible future functionality? We're a little into the world of esoteric at this point so it doesn't matter much either way. I am good and appreciate the feedback.

roji commented 3 years ago

@andleer I think so, but let's let @AndriySvyryd confirm.