Entity Framework Core Multiple 多对多链接 table(加入实体)

Entity Framework Core Multiple Many-to-many with linking table (join entity)

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

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

目前对我有用的是我创建了两个不同的 classes DeckGameCard 和 TableGameCard(连接实体),这会强制 EF 创建两个表。 class 除了名称不同外都是一样的。是否可以在数据库中只有一张class(join实体)和两张链接表?

我要的是这个:

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

但目前这个(下面的代码)是我的解决方案(不是最优的,因为 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);
}

是的,可以通过使用引入的 EF Core 5.0 Shared-type entity types,但不确定它是否值得,因为对象的类型不再唯一标识实体类型,所以大多数(如果不是全部)通用和非通用DbContext 的实体服务(方法)将不起作用,您必须使用相应的 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; }
}

可配置如下:


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);
});

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

的方法

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);
}

所以配置变得简单

GameCardEntity(modelBuilder, "DeckGameCard", c => c.Deck, g => g.Deck);
GameCardEntity(modelBuilder, "TableGameCard", c => c.OnTable, g => g.OnTable);

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

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 { }

复合PK只需要流畅的配置

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