【问题标题】:How can I implement a 1..n bi-directional relationship in Entity Framework Code First如何在 Entity Framework Code First 中实现 1..n 双向关系
【发布时间】:2014-09-03 02:07:26
【问题描述】:

在试图弄清楚如何使用 Code First 在实体框架中实现双向 1..n 关系时,我感到非常困惑。例如,一个团队(由一个 Team 实体表示)有一个教练和一个经理(都由一个 Person 实体表示)。所以,我的团队模型可能如下:

public class Team
{
    public Team()
    {
        Manager = new Person();
        Coach = new Person();
    }

    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int TeamID { get; set; }
    public string Name { get; set; }

    [ForeignKey("Manager")]
    public int ManagerID { get; set; }
    public virtual Person Manager { get; set; }

    [ForeignKey("Coach")]
    public int CoachID { get; set; }
    public virtual Person Coach { get; set; }
}

我可以通过如下实现 Person Entity 来实现单向导航:

public class Person
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int PersonID { get; set; }
    public string Name { get; set; }
}

以及流畅的 API:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Team>()
        .HasRequired(t => t.Manager);

    modelBuilder.Entity<Team>()
        .HasRequired(t => t.Coach);

    base.OnModelCreating(modelBuilder);
}

但是,虽然这允许我从团队实体导航到相关的教练和经理(Person 的两个实例),但它不允许我直接从教练或经理导航到相关的团队。所以,为了实现双向导航,我修改了 Person 实体如下:

public class Person
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int PersonID { get; set; }
    public string Name { get; set; }

    [ForeignKey("Team")]
    public int TeamID { get; set; }
    public virtual Team Team { get; set; }
}

虽然构建正常,但当我尝试保存到数据库时出现以下运行时错误:

System.Data.Entity.Core.UpdateException
InnerException: Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values.

因此,为了指定实体之间的顺序,我尝试通过添加“WithRequiredPricipal”来修改 fluent API,如下所示:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Team>()
        .HasRequired(t => t.Manager)
        .WithRequiredPrincipal(t => t.Team);

    modelBuilder.Entity<Team>()
        .HasRequired(t => t.Coach)
        .WithRequiredPrincipal(t => t.Team);

    base.OnModelCreating(modelBuilder);
}

但是,当我尝试在包管理器控制台中执行“添加迁移”时,我收到以下错误:

System.InvalidOperationException: The navigation property 'Team' declared on type 'RelatedEntities.Models.Person' has been configured with conflicting foreign keys.

我试图实现的目标似乎是一个简单的要求,但我已经完成了大量搜索解决方案但尚未找到答案。我在流畅的 API 或注释中遗漏了什么吗?

(我不想实施解决方法,例如为教练和经理实施单独的模型,因为一个团队可能有许多其他角色(例如,助理教练、公共关系经理等)。我希望每个人都简单地是 Person 实体的一个实例。)

【问题讨论】:

  • 1 比 1 还是 1 比多?看来您想要的是在 Person 上有两个 Team 集合属性

标签: entity-framework ef-code-first entity-framework-migrations ef-fluent-api


【解决方案1】:

您没有定义外键约束。

.HasRequired(t => t.Manager)
.WithMany()
.HasForeignKey(t => t.ManagerId)

作为旁注。如果您的经理管理多个团队,WithMany 将是 .WithMany(m =&gt; m.Teams),并且您的经理模型需要这样的内容:

private ICollection<Team> _teams
public ICollection<Team> Teams 
{
    get { return _teams ?? (teams = new List<Team>()); }
    protected set { _teams = value; }
}

抱歉格式化。在我的手机上。祝你好运。

【讨论】:

  • 感谢您的建议,@nameEqualsPNamePrube,但这不是问题(请参阅下面的下一条评论)。您提出的 fluent API 只是指定我在示例代码(上图)中使用“ForeignKey”注释指定的内容的另一种方法; fluent API 和注释生成 DbMigration 派生类的相同 Up() 方法,该方法是响应包管理器控制台中的“add-migration”命令而生成的。
  • 我要解决的问题是 Team 和 Person 最终的外键指向彼此,而 Entity Framework 无法分辨 Team 和 Person 中的哪一个是主体,哪个是主体依赖 - 即 Team 包含一个 Person,Person 包含一个 Team。这就是我认为“WithRequiredPrincipal”发挥作用的地方,但正如我原来的问题中提到的那样,这导致实体框架抱怨外键冲突。如果不指定主体/依赖关系,当我尝试保存实体时会出现运行时错误(如上所述)。
  • 我很确定这是不可能的。此外,团队包含“人员”和“属于”团队的人员。我想我会先考虑修改你的数据模型。 EF 无法分辨谁是原则和依赖,因为您的模型本身也没有说明。
  • 不,我正在尝试做的是有效的。如果您不喜欢我用来演示问题的假设团队示例,请想象婚姻。婚姻不仅仅是一群人的集合。婚姻具有丈夫和妻子的特定实体,两者都是人类型。应该可以像Marriage.Husband 和Husband.Marriage 那样在两个方向上导航。事实证明,EF 有一个公认的缺点。请参阅:entityframework.codeplex.com/workitem/142。 EF 分类小组已将其标记为在未来版本中解决。同时我需要实施一个解决方法
【解决方案2】:

好的,我现在可以回答我自己的问题,以防其他人遇到同样的问题。

首先,正如我在上面的评论中提到的,我在问题中描述的问题结果是 EF 分类团队已标记为在未来版本的 EF 中解决的实体框架中的一个公认缺陷。

与此同时,许多贡献者在回答有关如何在 EF 中实现循环依赖的问题时建议的解决方法(这是我上面的示例试图做的)是分阶段进行,如下所示:

  1. 创建主体实体
  2. 在 DbContext 上调用 SaveChanges()
  3. 在再次调用 SaveChanges() 之前创建依赖实体并设置外键(在随后的某个时间点)

因此,使用我上面原始问题中的团队示例,而不是在团队构造函数中创建依赖的经理和教练实体,第一个更改是使团队的教练和经理的外键是可选的(而不是必需的) ,这样 Team 就可以在没有 Coach 和 Manager 的情况下进行实例化,然后调用 SaveChanges():

Team team = Teams.Add(new Team());
SaveChanges();

之后,我创建了依赖的 Manager 和 Coach 实体,并将它们的外键设置为 Team 实例的 ID:

team.Manager = new Person();
team.Manager.TeamID = team.TeamID;

team.Coach = new Person();
team.Coach.TeamID = team.TeamID;

在此之后的任何时候,都可以调用 SaveChanges(),而不会像以前那样导致运行时错误。

当这个问题在 EF 的未来版本中得到解决时,应该可以创建主体实体和依赖实体,而不必在两者之间调用 SaveChanges()。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-09-10
    • 1970-01-01
    • 2014-11-11
    • 1970-01-01
    • 2014-05-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多