摔跤模拟器实体框架/数据库设计
Posted
技术标签:
【中文标题】摔跤模拟器实体框架/数据库设计【英文标题】:Wrestling simulator Entity Framework / database design 【发布时间】:2020-11-16 03:43:40 【问题描述】:我正在努力想出一个数据库设计。
场景:我正在创建一个非常基本的摔跤模拟器游戏。
我有以下模型类:
摔跤手
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
的多对多表,其中列出了Wrestler
中的Id
和分配给他们参加比赛的Match
。
但是,我想知道如何确定比赛的赢家和输家?
是否需要另外一张表来解决这个问题,例如:
(下面我的描述可能不正确)
【问题讨论】:
【参考方案1】:一种选择是将bool IsWinner get; set;
之类的内容添加到 WrestlerMatch。
这种结构适用于 1 对 1 和团队比赛,但在团队比赛中管理 IsWinner 会有点手动,其中 IsWinner 将设置在 2 个或更多条目上。
或者,您可以在比赛中引入“边”或“角”之类的内容,以跟踪比赛中哪一方获胜,然后将一名或多名摔跤手与每一方相关联。
那么你会有:
匹配 -> 角球(使用 IsWinner) -> 角斗士 -> 摔跤手
业务逻辑需要强制在一场比赛中可以有多少个角斗士,以及一个角斗士可以有多少个摔跤手(确保比赛中的摔跤手数量相同,不加倍等)这将支持 1v1 、2v2、4v4、2v2v2、2v2v2v2等
一些关于 EF 和导航属性的快速提示有助于避免一些令人头疼的问题:
使用导航属性时,我建议不要在实体中声明 FK 字段,而是使用 Map(x => x.MapKey())
(EF6) 或 Shadow Properties (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
之前,PromotionId 不会自动更新。假设 PromotionId 始终有效并使用 show.PromotionId 与 show.Promotion.PromotionId 相比,这可能会为任何代码留下漏洞。
EF 完全支持双向导航属性(Wrestler 具有 Matches,而 Match 具有 Wrestlers),但通常更容易管理单向引用,并将双向引用保存在您真正需要的位置。您始终可以从诸如 Match 之类的***实体中查询/过滤数据,并在该比赛的上下文中深入了解 Wrestlers,而无需 Wrestler 上的“Matches”。
例如,如果我有一个 Wrestler,一个包含 Corners 的 Match 和 Wrestler 分配给一个 Corner,我的 DbContext 可能有 Wrestlers 以便我可以管理我的 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 列以链接到 Match。
代码逻辑仍然需要防止多个角最终设置为 IsWinner = True 的任何可能性。 IMO 帮助避免此类问题的最佳实践是采用 DDD 方法对实体执行操作,而不是直接在实体中访问设置器的代码。如果实体具有受保护/内部设置器,而是使用操作方法来更新状态(即在匹配级别具有AssignWinner(cornerId)
方法,则该方法成为设置 IsWinner 的唯一位置,并且可以验证 Corner 是匹配的一部分并且所有其他角 IsWinner 都是错误的。只是为了避免数据状态问题/w EF 或其他 ORM 而需要考虑的事情。
编辑 #2:没有双向参考的比赛、角球、摔跤手(以及影子 CornerWrestler 加入表)
实体:
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" 中的 "Mapper")
【讨论】:
首先感谢您的回复,非常感谢。如果我使用双向方法,对于 Corners 的上下文,Corner 的属性是否只是 int CornerId 和 bool isWinner? 在实体中,是的。在表中也会有一个 MatchId。您还可以添加诸如“名称”之类的内容,该名称可以取自摔跤手名称、团队名称或“红色”角与“蓝色”角等。 我添加了一个使用单向引用的匹配到角的可能映射示例。 抱歉刚刚看到您的回复,考虑到这个imgur.com/a/cWaSIoI,我可以使用 Werstler 导航属性来访问该名称吗? 这种结构就是您以前的结构,将 WrestlerMatch 重命名为 Corner,并且要么将您限制为 1v1 比赛,要么必须管理标记多个获胜者。 (双打等)角球的想法是在比赛和角球之间形成一对多,然后在角球和摔跤手之间形成多对多。 (WrestlerCorner 表)角球跟踪胜利。当映射简单的多对多关系时,一个角可以有一个摔跤手的集合。 (其中 EF 使用 WrestlerCorner 表,无需创建 WrestlerCorner 实体)以上是关于摔跤模拟器实体框架/数据库设计的主要内容,如果未能解决你的问题,请参考以下文章
如果我稍后应用 ado 实体框架,推荐的数据访问层设计模式是啥?