首页 > 解决方案 > 摔跤模拟器实体框架/数据库设计

问题描述

我正在努力想出一个数据库设计。

场景:我正在创建一个非常基本的摔跤模拟器游戏。

我有以下模型类:

摔跤手

public class Wrestler
{
    public int WrestlerId { get; set; }
    public string Name { get; set; }
    public int Overall { get; set; }
    public string Finisher { get; set; }
    public virtual ICollection<Match> Matches { get; set; }
}

晋升

public class Promotion
{
    public int PromotionId { get; set; }
    public string Name { get; set; }
    public decimal Budget { get; set; }
    public string Size { get; set; }
}

节目

public class Show
{
    public int ShowId { get; set; }
    public string Name { get; set; }
    public int PromotionId {get; set;}
    public virtual Promotion Promotion { get; set; }
}

匹配

public class Match
{
    public int MatchId { get; set; }
    public string MatchType { get; set; }
    public int ShowId { get; set; }
    public virtual Show Show { get; set; }
    public virtual ICollection<Wrestler> Wrestlers { get; set; }
}

摔跤手比赛

public class WrestlerMatch
{
    public virtual int WrestlerId { get; set; }
    public virtual int MatchId { get; set; }
    public virtual Wrestler Wrestler { get; set; }
    public virtual Match Match { get; set; }
}

对于一场比赛,我创建了一个名为 的多对多表,WrestlerMatch其中列出了Id分配给他们参加比赛的 和WrestlerMatch

但是,我想知道如何绘制比赛的赢家和输家?

是否有另一个表我需要解决这个问题,例如:

(下面我的描述可能不正确)

标签: databaseentity-frameworkdatabase-designentity-framework-6erd

解决方案


一种选择是添加类似bool IsWinner { get; set; }WrestlerMatch 的内容。

这种结构适用于 1 对 1 和团队比赛,尽管在团队比赛中管理 IsWinner 会有点手动,其中 IsWinner 将设置在 2 个或更多条目上。

或者,您可以在比赛中引入诸如“边”或“角”之类的东西,以跟踪比赛中哪一方获胜,然后将一个或多个摔跤手与每一方相关联。

然后你会有:

比赛 -> 角球(与 IsWinner) -> CornerWrestlers -> 摔跤手

业务逻辑需要强制在一场比赛中可以有多少个角球,以及一个角球可以有多少个摔跤手(确保相同的数量,在一场比赛中摔跤手不加倍等)这将支持 1v1、2v2、 4v4、2v2v2、2v2v2v2等

一些关于 EF 和导航属性的快速提示有助于避免一些令人头疼的问题:

使用导航属性时,我建议不要在实体中声明 FK 字段,而是使用 Map(x => x.MapKey())(EF6) 或影子属性 (EF Core)。例如:

public class Show
{
    public int ShowId { get; set; }
    public string Name { get; set; }
    public virtual Promotion Promotion { get; set; }
}

public class ShowConfiguration : EntityTypeConfiguration<Show>
{
    public ShowConfiguration()
    {
        ToTable("Shows");
        HasKey(x => x.ShowId)
            .Property(x => x.ShowId)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

       HasRequired(x => x.Promotion)
           .WithMany()
           .Map(x => x.MapKey("PromotionId");
    }
}

同时拥有 Promotion 和 PromotionId 可能出现的问题是假设两者将始终保持同步。要更改节目的促销,您可以替换促销参考和/或更新 PromotionId。SaveChanges正确的方法是更新导航属性,但是在调用after 之前,PromotionId 不会自动更新。假设 PromotionId 始终有效并使用 show.PromotionId 与 show.Promotion.PromotionId,这可能会为任何代码留下漏洞。

EF 完全支持双向导航属性(Wrestler 具有 Matches,Match 具有 Wrestlers),但通常更容易管理单向引用,并在您真正需要的地方保存双向引用。您始终可以从顶级实体(如 Match)查询/过滤数据,并在该比赛的上下文中深入了解 Wrestlers,而无需在 Wrestler 上进行“比赛”。

例如,如果我有一个 Wrestler,一个包含 Corners 的 Match 并且 Wrestler 分配给一个 Corner,我的 DbContext 可能有 Wrestlers 以便我可以管理我的 Wrestler 池,但是在查看比赛或查看我的 Wrestler 的比赛表现时,摔跤手不需要角球等。我可以通过比赛访问该信息:

var wrestlerWinCount = context.Matches
    .Where(m => m.Corners
       .Where(c=> c.IsWinner)
       .Any(c => c.Wrestlers.Any(w => w.WrestlerId == wrestlerId)))
    .Count();

双向引用将允许:

var wrestlerWinCount = context.Wrestlers
    .Where(w => w.WrestlerId == wrestlerId)
    .SelectMany(w => w.Corners)
    .Where(c => c.IsWinner)
    .Count();

处理双向引用的问题是,在编辑双向关系时,您需要更新双方。例如,对于将“丑陋的 Iggy”替换为“粗犷的 Randy”的比赛,您需要从 Wrestler Iggy 中删除“Corner”并将其添加到 Randy,然后从 Corner Wrestlers 集合中删除 Iggy,并添加 Randy。忘记更新双向关系的一侧可能最终导致更新错误或意外的数据状态。尽可能多地依赖 1 方向参考通常更简单。

编辑:使用单向参考将匹配映射到角,从匹配到角:

public MatchConfiguration()
{
    ToTable("Matches");
    HasKey(x => x.MatchId)
        .Property(x => x.MatchId)
        .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

    HasMany(x => x.Corners)
       .WithRequired()
       .Map(x => x.MapKey("MatchId"));
}

一场比赛有几个角,代码逻辑需要强制执行有效的最小值和最大值。WithRequired()确保 Corner 需要 MatchId,但不引用 Match 实体。Map(x => x.MapKey("MatchId"))告诉映射在 Corners 表中查找 MatchId 列以链接到匹配项。

代码逻辑仍然需要防止多个角最终设置为 IsWinner = True 的任何可能性。IMO 帮助避免此类问题的最佳实践是采用 DDD 方法对实体执行操作,而不是直接在实体中访问设置器的代码。如果实体具有受保护/内部设置器,而是使用方法来更新状态(即在 Match 级别有一个AssignWinner(cornerId)方法,该方法将成为设置 IsWinner 的唯一位置,并且可以验证 Corner 是匹配的一部分并且所有其他角落 IsWinner 是错误的。只是为了避免数据状态问题/w EF 或其他 ORM 需要考虑的事情。

编辑#2:没有双向参考的比赛、角球、摔跤手(以及影子角摔跤手加入表)

实体:

public class Match
{
   public int MatchId { get; set; }
   // other match related fields.

   public virtual ICollection<Corner> Corners { get; set; }
}

public class Corner
{
    public int CornerId { get; set; }
    public bool IsWinner { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Wrestler> Wrestlers { get; set; }
}

public class Wrestler
{
    public int WrestlerId { get; set; }
    public string Name { get; set; }
    // other wrestler specific fields...
}

对于配置,让 EF 知道这些是如何相关的:

public MatchConfiguration()
{
    ToTable("Matches");
    HasKey(x => x.MatchId)
        .Property(x => x.MatchId)
        .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

    HasMany(x => x.Corners)
       .WithRequired()
       .Map(x => x.MapKey("MatchId"));
}

public CornerConfiguration()
{
    ToTable("Corners");
    HasKey(x => x.CornerId)
        .Property(x => x.CornerId)
        .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

    HasMany(x => x.Wrestlers)
       .WithMany()
       .Map(x => 
       {
           x.MapLeftKey("CornerId");
           x.MapRightKey("WrestlerId");
           x.ToTable("CornerWrestlers");
       });
}

public WrestlerConfiguration()
{
    ToTable("Wrestlers");
    HasKey(x => x.WrestlerId)
        .Property(x => x.WrestlerId)
        .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

}

请注意,没有双向引用,实体中没有 FK 属性,也没有 CornerWrestler 表的 CornerWrestler 实体。从一个角落你正在处理一系列摔跤手。EF 在后台管理多对多表。当 CornerWrestler 表只包含 CornerId 和 WrestlerId 作为复合 PK 时,这是可能的。这类似于 WrestlerMatch 连接表的工作方式,除了需要映射 WrestlerMatch 实体并在集合(而不是 Wrestlers)中使用它,如果您想支持跟踪该表/实体中的 IsWinner。如果该表仅包含参考 FK,则 EF 可以映射连接表。(AFAIK 这仅在 EF6 中受支持,EF Core 仍未实现此功能,需要加入实体。

让所有摔跤手参加比赛:

var wrestlers = match.Corners.SelectMany(c => c.Wrestlers);

如果您绘制出 CornerWrestler 实体,那么从比赛中访问摔跤手会更加迂回......

var wrestlers = match.Corners
    .SelectMany(c => c.CornerWrestlers.Select(cw => cw.Wrestler));

即,您总是必须通过 CornerWrestler(或 WrestlerMatch)导航才能找到摔跤手。

无论如何,它可能与您开始使用的设置有些不同,但请通读 EF 的一对多与多对多关系配置以及不同的配置选项。它可以让您以更直观的方式安排事物,并让 EF 弄清楚数据结构在幕后是如何工作的,而不是依赖于模仿关系数据结构的实体结构。(利用 OR“M”中的“映射器”)


推荐阅读