在Entity Framework Code First中为同一个表定义多个外键

Posted

技术标签:

【中文标题】在Entity Framework Code First中为同一个表定义多个外键【英文标题】:Defining multiple Foreign Key for the Same table in Entity Framework Code First 【发布时间】:2015-04-18 16:54:39 【问题描述】:

我的 MVC 应用程序中有两个实体,并使用 Entity Framework 6 Code First 方法填充了数据库。 Student 实体中有两个城市 id;其中一个用于 BirthCity,另一个用于 WorkingCity。当我如上所述定义外键时,迁移后会在 Student 表中创建一个名为 City_ID 的额外列。是否有错误或如何定义这些 FK?提前致谢。

学生:

public class Student

    public int ID  get; set; 

    public string Name  get; set; 

    public string Surname  get; set; 

    public int BirthCityID  get; set; 

    public int LivingCityID  get; set; 


    [ForeignKey("BirthCityID")]
    public virtual City BirthCity  get; set; 

    [ForeignKey("LivingCityID")]
    public virtual City LivingCity  get; set; 

城市:

public class City

    public int ID  get; set; 

    public string CityName  get; set; 


    public virtual ICollection<Student> Students  get; set; 

【问题讨论】:

为了清晰起见,我简化了我的代码,但我的代码与此逻辑完全相同。另一方面,我已经尝试从头开始重新创建数据库 2-3 次,但现在我再次这样做,并在几分钟内通知您。 @ChrisPratt:这是我现在遵循的步骤:1)从我的项目中删除了迁移文件夹及其内容。 2)清理并重建解决方案中的所有项目。 3) 在包管理器控制台上启用迁移,然后添加 AutomaticMigrationsEnabled = true;和 AutomaticMigrationDataLossAllowed = true;到配置文件的构造函数。 4)添加迁移。 >>> @ChrisPratt:但是在这一步之后,我再次观察到 Up 方法中有一个属性“City_ID = c.Int()”。但是,我在解决方案中进行了搜索,没有“City_ID”属性。那么,为什么这个未定义的属性被添加到项目中呢?提前致谢。 Entity Framework Code First - two Foreign Keys from same table的可能重复 【参考方案1】:

嘘。这是漫长的一天。实际上,您的代码存在一个非常明显的问题,实际上,当我发表评论时,我完全错过了。

问题是您在City 上使用了一个学生集合。这里实际发生的是 EF 无法决定它应该将该集合实际映射到哪个外键,因此它创建了另一个专门用于跟踪该关系的外键。然后,实际上您没有从BirthCityLivingCity 派生的学生集合的导航属性。

为此,您必须使用流畅的配置,因为仅使用数据注释无法正确配置。您还需要额外的学生集合,以便跟踪这两种关系:

public class City

    ...

    public virtual ICollection<Student> BirthCityStudents  get; set; 
    public virtual ICollection<Student> LivingCityStudents  get; set; 

那么,对于Student

public class Student

    ...

    public class StudentMapping : EntityTypeConfiguration<Student>
    
        public StudentMapping()
        
            HasRequired(m => m.BirthCity).WithMany(m => m.BirthCityStudents);
            HasRequired(m => m.LivingCity).WithMany(m => m.LivingCityStudents);
        
    

最后在你的上下文中:

protected override void OnModelCreating(DbModelBuilder modelBuilder)

    modelBuilder.Configurations.Add(new Student.StudentMapping());

【讨论】:

非常感谢您的回答。正如您和@octavioccl 所说,有两种方法可以实现这一点,并且它们都在起作用。由于 Data Annotations 方式似乎更容易控制此操作的实体,因此我更喜欢使用这种方式。再次感谢您的考虑。问候... 感谢StudentMapping 类...我一直在努力解决这些定义应该存在的位置,我真的很喜欢它们在Student 类中可见-信息相关-不在上下文中。 @erroric:是的,我不记得我最初是在哪里偶然发现这个的,但它比把它贴在上下文中要好几英里,因为每个地方的每个教程都会向你展示。在发现这种方法之前,我对 fluent 配置的最大问题是它与实体无关,因此除非您注意哪些实体是通过上下文配置的,否则会导致很多混乱。 谢谢,刚刚帮我走出了困境!【参考方案2】:

要实现你想要的,你需要提供一些额外的配置。代码优先约定可以识别双向关系,但当有 两个实体之间的多个双向关系。您可以添加配置(使用 Data AnnotationsFluent API)来呈现这个 模型构建者的信息。使用数据注释,您将使用注释 叫InverseProperty。使用 Fluent API,您将使用 Has/With 方法的组合来指定这些关系的正确结束。

使用数据注释可能是这样的:

public class Student

  public int ID  get; set; 

  public string Name  get; set; 

  public string Surname  get; set; 

  public int BirthCityID  get; set; 

  public int LivingCityID  get; set; 


  [ForeignKey("BirthCityID")]
  [InverseProperty("Students")]
  public virtual City BirthCity  get; set; 

  [ForeignKey("LivingCityID")]
  public virtual City LivingCity  get; set; 

通过这种方式,您明确指定要将BirthCity 导航属性与关系另一端的Students 导航属性相关联。

使用 Fluent Api 可能是这样的:

protected override void OnModelCreating(DbModelBuilder modelBuilder)

     modelBuilder.Entity<Student>().HasRequired(m => m.BirthCity)
                                 .WithMany(m => m.Students).HasForeignKey(m=>m.BirthCityId);
     modelBuilder.Entity<Student>().HasRequired(m => m.LivingCity)
                                 .WithMany().HasForeignKey(m=>m.LivingCityId);

使用最后一种解决方案,您无需使用任何属性。

现在,@ChristPratt 的建议在您的 City 类中为每个关系收集 Student 非常有用。如果你这样做了,那么使用 Data Annotations 的配置可能是这样的:

public class Student

  public int ID  get; set; 

  public string Name  get; set; 

  public string Surname  get; set; 

  public int BirthCityID  get; set; 

  public int LivingCityID  get; set; 


  [ForeignKey("BirthCityID")]
  [InverseProperty("BirthCityStudents")]
  public virtual City BirthCity  get; set; 

  [ForeignKey("LivingCityID")]
  [InverseProperty("LivingCityStudents")]
  public virtual City LivingCity  get; set; 

或者按照同样的思路使用Fluent Api

protected override void OnModelCreating(DbModelBuilder modelBuilder)

     modelBuilder.Entity<Student>().HasRequired(m => m.BirthCity)
               .WithMany(m => m.BirthCityStudents).HasForeignKey(m=>m.BirthCityId);
     modelBuilder.Entity<Student>().HasRequired(m => m.LivingCity)
               .WithMany(m => m.LivingCityStudents).HasForeignKey(m=>m.LivingCityId);

【讨论】:

非常感谢您的回答。正如您和@ChrisPratt 所说,有两种方法可以实现这一目标,并且它们都有效。由于 Data Annotations 方式似乎更容易控制此操作的实体,因此我更喜欢使用这种方式。再次感谢您的考虑。问候... 如果 Student 和 City 之间的关系是 1 -> 0..1 怎么办?你怎么能用流畅的 API 来实现呢 @octavioccl 这个答案正是应该如何处理的。这会在数据库中创建正确的导航以及正确的 ForeignKeys 集。我发现 InverseProperty 和 ForeignKey 属性最容易阅读代码并实际告诉它在做什么。 我必须使 BirthCityIDHomeCityID 可以为空 (int?) 才能使 Data Annotation 方法起作用。否则我总是得到错误 SqlException: Introducing FOREIGN KEY constraint '...' on table '...' 可能会导致循环或多个级联路径。 City 类的 Students 属性在做什么?

以上是关于在Entity Framework Code First中为同一个表定义多个外键的主要内容,如果未能解决你的问题,请参考以下文章

Entity Framework 5.0 Code First全面学习

转:Entity Framework 5.0 Code First全面学习

Entity Framework Code First 不允许Entity直接实现接口

Entity Framework Code First 迁移 Migrations

Entity Framework Code First迁移基本面拾遗

Entity Framework 中的Code First 中引入数据库函数