diff --git a/src/EFCore/Metadata/Builders/CollectionCollectionBuilder.cs b/src/EFCore/Metadata/Builders/CollectionCollectionBuilder.cs
index e8a11ceb312..f8fecfc10cc 100644
--- a/src/EFCore/Metadata/Builders/CollectionCollectionBuilder.cs
+++ b/src/EFCore/Metadata/Builders/CollectionCollectionBuilder.cs
@@ -83,6 +83,25 @@ public CollectionCollectionBuilder(
[EntityFrameworkInternal]
protected virtual InternalModelBuilder ModelBuilder => LeftEntityType.AsEntityType().Model.Builder;
+ ///
+ /// Configures the association entity type implementing the many-to-many relationship.
+ ///
+ /// The configuration of the association type.
+ /// The builder for the originating entity type so that multiple configuration calls can be chained.
+ public virtual EntityTypeBuilder UsingEntity(
+ [NotNull] Action configureAssociation)
+ {
+ Check.DebugAssert(LeftNavigation.AssociationEntityType != null, "LeftNavigation.AssociationEntityType is null");
+ Check.DebugAssert(RightNavigation.AssociationEntityType != null, "RightNavigation.AssociationEntityType is null");
+ Check.DebugAssert(LeftNavigation.AssociationEntityType == RightNavigation.AssociationEntityType,
+ "LeftNavigation.AssociationEntityType != RightNavigation.AssociationEntityType");
+
+ var associationEntityTypeBuilder = new EntityTypeBuilder(LeftNavigation.AssociationEntityType);
+ configureAssociation(associationEntityTypeBuilder);
+
+ return new EntityTypeBuilder(RightEntityType);
+ }
+
///
/// Configures the relationships to the entity types participating in the many-to-many relationship.
///
diff --git a/src/EFCore/Metadata/Builders/CollectionCollectionBuilder`.cs b/src/EFCore/Metadata/Builders/CollectionCollectionBuilder`.cs
index b785a0efe2a..a1ea6dca9ab 100644
--- a/src/EFCore/Metadata/Builders/CollectionCollectionBuilder`.cs
+++ b/src/EFCore/Metadata/Builders/CollectionCollectionBuilder`.cs
@@ -6,6 +6,7 @@
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Utilities;
namespace Microsoft.EntityFrameworkCore.Metadata.Builders
{
@@ -40,6 +41,25 @@ public CollectionCollectionBuilder(
{
}
+ ///
+ /// Configures the association entity type implementing the many-to-many relationship.
+ ///
+ /// The configuration of the association type.
+ /// The builder for the originating entity type so that multiple configuration calls can be chained.
+ public new virtual EntityTypeBuilder UsingEntity(
+ [NotNull] Action configureAssociation)
+ {
+ Check.DebugAssert(LeftNavigation.AssociationEntityType != null, "LeftNavigation.AssociationEntityType is null");
+ Check.DebugAssert(RightNavigation.AssociationEntityType != null, "RightNavigation.AssociationEntityType is null");
+ Check.DebugAssert(LeftNavigation.AssociationEntityType == RightNavigation.AssociationEntityType,
+ "LeftNavigation.AssociationEntityType != RightNavigation.AssociationEntityType");
+
+ var associationEntityTypeBuilder = new EntityTypeBuilder(LeftNavigation.AssociationEntityType);
+ configureAssociation(associationEntityTypeBuilder);
+
+ return new EntityTypeBuilder(RightEntityType);
+ }
+
///
/// Configures the relationships to the entity types participating in the many-to-many relationship.
///
diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs
index bc7c55ce24a..9595aef8b4c 100644
--- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs
+++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs
@@ -1238,6 +1238,138 @@ public virtual void Many_to_many_join_table_stored_in_snapshot()
});
}
+
+ [ConditionalFact]
+ public virtual void Can_override_table_name_for_many_to_many_join_table_stored_in_snapshot()
+ {
+ Test(
+ builder =>
+ {
+ var manyToMany = builder
+ .Entity()
+ .HasMany(l => l.Rights)
+ .WithMany(r => r.Lefts)
+ .UsingEntity(a => a.ToTable("MyJoinTable"));
+ },
+ AddBoilerPlate(
+ GetHeading()
+ + @"
+ modelBuilder.Entity(""ManyToManyLeftManyToManyRight"", b =>
+ {
+ b.Property(""ManyToManyLeft_Id"")
+ .HasColumnType(""int"");
+
+ b.Property(""ManyToManyRight_Id"")
+ .HasColumnType(""int"");
+
+ b.HasKey(""ManyToManyLeft_Id"", ""ManyToManyRight_Id"");
+
+ b.HasIndex(""ManyToManyRight_Id"");
+
+ b.ToTable(""MyJoinTable"");
+ });
+
+ modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+ManyToManyLeft"", b =>
+ {
+ b.Property(""Id"")
+ .ValueGeneratedOnAdd()
+ .HasColumnType(""int"")
+ .UseIdentityColumn();
+
+ b.Property(""Name"")
+ .HasColumnType(""nvarchar(max)"");
+
+ b.HasKey(""Id"");
+
+ b.ToTable(""ManyToManyLeft"");
+ });
+
+ modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+ManyToManyRight"", b =>
+ {
+ b.Property(""Id"")
+ .ValueGeneratedOnAdd()
+ .HasColumnType(""int"")
+ .UseIdentityColumn();
+
+ b.Property(""Description"")
+ .HasColumnType(""nvarchar(max)"");
+
+ b.HasKey(""Id"");
+
+ b.ToTable(""ManyToManyRight"");
+ });
+
+ modelBuilder.Entity(""ManyToManyLeftManyToManyRight"", b =>
+ {
+ b.HasOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+ManyToManyLeft"", null)
+ .WithMany()
+ .HasForeignKey(""ManyToManyLeft_Id"")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+ManyToManyRight"", null)
+ .WithMany()
+ .HasForeignKey(""ManyToManyRight_Id"")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });", usingSystem: true),
+ model =>
+ {
+ var associationEntity = model.FindEntityType("ManyToManyLeftManyToManyRight");
+ Assert.NotNull(associationEntity);
+ Assert.Equal("MyJoinTable", associationEntity.GetTableName());
+ Assert.Collection(associationEntity.GetDeclaredProperties(),
+ p =>
+ {
+ Assert.Equal("ManyToManyLeft_Id", p.Name);
+ Assert.True(p.IsShadowProperty());
+ },
+ p =>
+ {
+ Assert.Equal("ManyToManyRight_Id", p.Name);
+ Assert.True(p.IsShadowProperty());
+ });
+ Assert.Collection(associationEntity.FindDeclaredPrimaryKey().Properties,
+ p =>
+ {
+ Assert.Equal("ManyToManyLeft_Id", p.Name);
+ },
+ p =>
+ {
+ Assert.Equal("ManyToManyRight_Id", p.Name);
+ });
+ Assert.Collection(associationEntity.GetDeclaredForeignKeys(),
+ fk =>
+ {
+ Assert.Equal("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+ManyToManyLeft", fk.PrincipalEntityType.Name);
+ Assert.Collection(fk.PrincipalKey.Properties,
+ p =>
+ {
+ Assert.Equal("Id", p.Name);
+ });
+ Assert.Collection(fk.Properties,
+ p =>
+ {
+ Assert.Equal("ManyToManyLeft_Id", p.Name);
+ });
+ },
+ fk =>
+ {
+ Assert.Equal("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+ManyToManyRight", fk.PrincipalEntityType.Name);
+ Assert.Collection(fk.PrincipalKey.Properties,
+ p =>
+ {
+ Assert.Equal("Id", p.Name);
+ });
+ Assert.Collection(fk.Properties,
+ p =>
+ {
+ Assert.Equal("ManyToManyRight_Id", p.Name);
+ });
+ });
+ });
+ }
+
[ConditionalFact]
public virtual void TableName_preserved_when_generic()
{