实体框架核心多个多对多与链接表(连接实体)

Jor*_*s L 3 c# many-to-many join entity-framework-core

如何使用连接实体(链接表)在 EF Core 5.x 中的实体之间添加多个多对多关系?

我理解的是Card和Game之间的两种多对多的关系。一种关系不是问题,但两种(或更多)关系我无法正确配置。

目前对我有用的是,我创建了两个不同的类 DeckGameCard 和 TableGameCard(连接实体),这迫使 EF 创建两个表。除了名称之外,这些类都是相同的。数据库中是否可以只有一个类(连接实体)和两个链接表?

我想要的是这样的:

public class Game
{
    [Key]
    public int Id { get; set; }
    
    public ICollection<GameCard> Deck { get; set; }
    
    public ICollection<GameCard> OnTable { get; set; }
    ...
Run Code Online (Sandbox Code Playgroud)

但目前这(下面的代码)是我的解决方案(不是最佳的,因为 DeckGameCard 和 TableGameCard 中的代码重复)。

public class DeckGameCard
{
    public int GameId { get; set; }
    public Game Game { get; set; }
    
    public int Order { get; set; }

    public int CardId { get; set; }
    public Card Card { get; set; }
}

public class TableGameCard
{
    public int GameId { get; set; }
    public Game Game { get; set; }
    
    public int Order { get; set; }

    public int CardId { get; set; }
    public Card Card { get; set; }
}


public class Game
{
    [Key]
    public int Id { get; set; }
    
    public ICollection<DeckGameCard> Deck { get; set; }
    
    public ICollection<TableGameCard> OnTable { get; set; }
    
    [Required]
    public int CardIndex { get; set; }
    
    [Required]
    public int PlayerId { get; set; }
    [Required]
    public Player Player { get; set; }
}

public class Card : IEntity
{
    [Key]
    public int Id { get; set; }

    [Required]
    public Shape Shape { get; set; }
    [Required]
    public Fill Fill { get; set; }
    [Required]
    public Color Color { get; set; }
    [Required]
    public int NrOfShapes { get; set; }
    
    public ICollection<DeckGameCard> Deck { get; set; }
    
    public ICollection<TableGameCard> OnTable { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<DeckGameCard>()
        .HasKey(x => new {x.GameId, x.CardId});

    modelBuilder.Entity<DeckGameCard>()
        .HasOne(gc => gc.Card)
        .WithMany(b => b.Deck)
        .HasForeignKey(gc => gc.CardId);

    modelBuilder.Entity<DeckGameCard>()
        .HasOne(gc => gc.Game)
        .WithMany(g => g.Deck)
        .HasForeignKey(gc => gc.GameId);


    modelBuilder.Entity<TableGameCard>()
        .HasKey(x => new {x.GameId, x.CardId});

    modelBuilder.Entity<TableGameCard>()
        .HasOne(gc => gc.Card)
        .WithMany(b => b.OnTable)
        .HasForeignKey(bc => bc.CardId);

    modelBuilder.Entity<TableGameCard>()
        .HasOne(gc => gc.Game)
        .WithMany(g => g.OnTable)
        .HasForeignKey(gc => gc.GameId);
}
Run Code Online (Sandbox Code Playgroud)

Iva*_*oev 5

是的,可以使用 EF Core 5.0 引入的共享类型实体类型,但不确定它是否值得,因为对象的类型不再唯一标识实体类型,因此大多数(如果不是全部)通用和非通用实体服务(方法) ofDbContext不起作用,你必须使用相应的DbSet<T>方法,通过重载来获取它Set<T>(name)。并且没有等效的Entry方法,因此您可能会在断开连接的情况下进行更改跟踪时遇到问题。

话虽如此,以下是如何在模型级别完成此操作。

鉴于模型类似于:


public class Game
{
    public int Id { get; set; }
    // ...
    public ICollection<GameCard> Deck { get; set; }
    public ICollection<GameCard> OnTable { get; set; }
}

public class Card
{
    public int Id { get; set; }
    // ...
    public ICollection<GameCard> Deck { get; set; }
    public ICollection<GameCard> OnTable { get; set; }
}

public class GameCard
{
    public int GameId { get; set; }
    public Game Game { get; set; }
    public int Order { get; set; }
    public int CardId { get; set; }
    public Card Card { get; set; }
}

Run Code Online (Sandbox Code Playgroud)

它可以配置如下:


modelBuilder.SharedTypeEntity<GameCard>("DeckGameCard", builder =>
{
    builder.ToTable("DeckGameCard");
    builder.HasKey(e => new { e.GameId, e.CardId });
    builder.HasOne(e => e.Card).WithMany(e => e.Deck);
    builder.HasOne(e => e.Game).WithMany(e => e.Deck);
});

modelBuilder.SharedTypeEntity<GameCard>("TableGameCard", builder =>
{
    builder.ToTable("TableGameCard");
    builder.HasKey(e => new { e.GameId, e.CardId });
    builder.HasOne(e => e.Card).WithMany(e => e.OnTable);
    builder.HasOne(e => e.Game).WithMany(e => e.OnTable);
});

Run Code Online (Sandbox Code Playgroud)

或者由于唯一的区别是实体(表)名称和相应的集合导航属性,因此可以将配置分解为类似于以下的方法


static void GameCardEntity(
    ModelBuilder modelBuilder, string name,
    Expression<Func<Card, IEnumerable<GameCard>>> cardCollection,
    Expression<Func<Game, IEnumerable<GameCard>>> gameCollection,
    string tableName = null
)
{
    var builder = modelBuilder.SharedTypeEntity<GameCard>(name);
    builder.ToTable(tableName ?? name);
    builder.HasKey(e => new { e.GameId, e.CardId });
    builder.HasOne(e => e.Card).WithMany(cardCollection);
    builder.HasOne(e => e.Game).WithMany(gameCollection);
}
Run Code Online (Sandbox Code Playgroud)

所以配置变得简单

GameCardEntity(modelBuilder, "DeckGameCard", c => c.Deck, g => g.Deck);
GameCardEntity(modelBuilder, "TableGameCard", c => c.OnTable, g => g.OnTable);
Run Code Online (Sandbox Code Playgroud)

这应该回答你的具体问题。但同样,请确保您了解它的潜在问题。与“直接”解决方案相比,使用基(而不是实体)实现相同的可重用性,而没有上述缺点,例如

public class Game
{
    public int Id { get; set; }
    // ...
    public ICollection<DeskGameCard> Deck { get; set; }
    public ICollection<TableGameCard> OnTable { get; set; }
}

public class Card
{
    public int Id { get; set; }
    // ...
    public ICollection<DeskGameCard> Deck { get; set; }
    public ICollection<TableGameCard> OnTable { get; set; }
}

public abstract class GameCard
{
    public int GameId { get; set; }
    public Game Game { get; set; }
    public int Order { get; set; }
    public int CardId { get; set; }
    public Card Card { get; set; }
}
public class DeskGameCard : GameCard { }
public class TableGameCard : GameCard { }

Run Code Online (Sandbox Code Playgroud)

复合 PK 只需要流畅的配置

modelBuilder.Entity<DeskGameCard>().HasKey(e => new { e.GameId, e.CardId });
modelBuilder.Entity<TableGameCard>().HasKey(e => new { e.GameId, e.CardId });
Run Code Online (Sandbox Code Playgroud)