在模型之间实现双向导航属性

Implementing two-directional navigation properties between models

尝试使用 Entity Framework (CRUD) 为我的播放器模型添加 Razor 页面时,出现以下异常

Unable to determine the relationship represented by navigation 'Player.Alliance' of type 'Alliance'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'
public class Player
{
    public Guid PlayerID { get; init; }

    // Navigation properties
    public virtual Alliance Alliance { get; init; }

    // Properties
    public int          GovernorIdentifier { get; set; }
    public string       Name               { get; set; }
    public int          Power              { get; set; }
    public int          Killpoints         { get; set; }
    public Civilization Civilization       { get; set; }
    public int          HighestPower       { get; set; }
    public int          Victory            { get; set; }
    public int          Defeat             { get; set; }
    public int          Dead               { get; set; }
    public int          ScoutTimes         { get; set; }
    public int          ResourcesGathered  { get; set; }
    public int          ResourceAssistance { get; set; }
    public int          AllianceHelpTimes  { get; set; }
}
public class Alliance
{
    // Primary key
    public Guid AllianceID { get; init; }

    // Navigation properties
    public Player              Leader  { get; init; }
    public ICollection<Player> Players { get; init; }

    // Properties
    public string Tag            { get; set; }
    public string Name           { get; set; }
    public int    Territories    { get; set; }
    public int    GiftLevel      { get; set; }
    public int    PlayerCapacity { get; set; }
}

当我从 Alliance 中删除 public Player Leader { get; init; } 时,或者当我从播放器中删除 public virtual Alliance Alliance { get; set; } 时,错误已解决。

如何实现玩家和联盟之间的双向一对一(如果这是错误的术语,请不要杀了我)关系?在我的特定用例中,如何确保 Alliance 既有玩家列表,又有指向作为联盟领导者的玩家的导航 属性,并且 Player 有指向联盟的导航 属性有一部分掉了吗?

当您依赖 EF 约定来映射关系时会发生这种情况。问题出在你的联盟 entity/table 上。你有:

public Player              Leader  { get; init; }
public ICollection<Player> Players { get; init; }

在您的玩家中,您将有一个 AllianceId,EF 将使用该 AllianceId 来解析与联盟关联的玩家,但是您的联盟 table 也需要一个 FK 来解析作为领导者的玩家记录.

EF 可以按照惯例计算出 many-to-one 键,但它是基于类型名称而不是 属性 名称来计算的。在您的联盟 table 中,您可能有类似 LeaderId 或更好的 LeaderPlayerId 之类的东西。 EF 不会自动解决这个问题,因此您需要明确映射它。

对于 EF Core,如果您不想在 Alliance 中将 FK 公开为 属性,则可以使用影子 属性。 (我建议不要将 FK 与导航属性一起公开,以避免两个事实来源)取决于您使用的映射方法,无论是使用 OnModelCreating /w modelBuilder 还是 IEntityTypeConfiguration,或者依赖于您可以设置的约定上传 FK 影子属性:

使用属性的约定:

[ForeignHey("LeaderPlayerId")]
public virtual Player Leader { get; set; }

对于 modelBuilder 或 EntityTypeConfiguration,您可以将 .HasForeignKey("LeaderPlayerId").HasOne(...).WithMany() 表达式一起使用。

这里的限制是,通过跟踪联盟上的 LeaderPlayerId,无法在数据层强制执行 Leader 的 AllianceId 实际上是该联盟。这只是返回 任何 玩家的任意 FK。

我们不能使用玩家的 AllianceId 为领导者建立类似 One-to-One 的关系,因为 FK 用于确定玩家属于哪个联盟。

如果您不想在联盟 table 中使用 LeaderPlayerId,则当前结构的另一种选择是在播放器上使用“IsLeader”标志。这将与联盟 ID 结合使用,以表明该玩家是其关联联盟的领导者。这意味着如果你有一个联盟只能有一个领导者的规则,那么你将需要在代码中执行该规则并使用数据健康检查来检测一个联盟是否以某种方式被分配了多个领导者。数据健康检查脚本也可用于使用 LeaderPlayerId 方法设置以解决“领导者不属于该联盟”的情况。