熟知 API-关系Fluent API - Relationships

备注

本页提供有关使用 Fluent API 设置 Code First 模型中的关系的信息。 有关 EF 中的关系以及如何使用关系访问和操作数据的一般信息,请参阅关系 & 导航属性

使用 Code First 时,可通过定义域 CLR 类定义模型。 默认情况下,实体框架使用 Code First 约定将类映射到数据库架构。 如果你使用 Code First 命名约定,则在大多数情况下,你可以依赖于 Code First 根据你在类上定义的外键和导航属性来设置表之间的关系。 如果在定义类时不遵循约定,或者若要更改约定的工作方式,可以使用 Fluent API 或数据批注来配置类,以便 Code First 可以映射表之间的关系。

介绍Introduction

在配置与 Fluent API 的关系时,请从 EntityTypeConfiguration 实例开始,然后使用 HasRequired、HasOptional 或 HasMany 方法来指定此实体参与的关系的类型。 HasRequired 和 HasOptional 方法采用表示引用导航属性的 lambda 表达式。 HasMany 方法采用表示集合导航属性的 lambda 表达式。 然后,可以通过使用 WithRequired、WithOptional 和 WithMany 方法配置反向导航属性。 这些方法具有不带参数的重载,可用于通过单向导航指定基数。

然后,可以使用 HasForeignKey 方法配置外键属性。 此方法采用一个表示要用作外键的属性的 lambda 表达式。

配置所需的可选关系(一对零或一)Configuring a Required-to-Optional Relationship (One-to–Zero-or-One)

下面的示例将配置一对零或一关系。 OfficeAssignment 具有 InstructorID 属性,该属性是主键和外键,因为属性的名称不遵循该约定。 HasKey 方法用于配置主键。

  1. // Configure the primary key for the OfficeAssignment
  2. modelBuilder.Entity<OfficeAssignment>()
  3. .HasKey(t => t.InstructorID);
  4. // Map one-to-zero or one relationship
  5. modelBuilder.Entity<OfficeAssignment>()
  6. .HasRequired(t => t.Instructor)
  7. .WithOptional(t => t.OfficeAssignment);

配置两个端都需要的关系(一对一)Configuring a Relationship Where Both Ends Are Required (One-to-One)

在大多数情况下实体框架可以推断哪个类型是依赖项并且是关系中的主体。 但是,如果需要关系的两端,或者两个两侧都是可选的,则实体框架无法识别依赖项和主体。 如果关系的两端都是必需的,请在 HasRequired 方法后面使用 WithRequiredPrincipal 或 WithRequiredDependent。 如果关系两端都是可选的,请在 HasOptional 方法后面使用 WithOptionalPrincipal 或 WithOptionalDependent。

  1. // Configure the primary key for the OfficeAssignment
  2. modelBuilder.Entity<OfficeAssignment>()
  3. .HasKey(t => t.InstructorID);
  4. modelBuilder.Entity<Instructor>()
  5. .HasRequired(t => t.OfficeAssignment)
  6. .WithRequiredPrincipal(t => t.Instructor);

配置多对多关系Configuring a Many-to-Many Relationship

下面的代码配置课程类型和指导员类型之间的多对多关系。 在下面的示例中,使用了默认 Code First 约定来创建联接表。 因此,Courseinstructor.courseid 表是使用 Course_CourseID 和 Instructor_InstructorID 列创建的。

  1. modelBuilder.Entity<Course>()
  2. .HasMany(t => t.Instructors)
  3. .WithMany(t => t.Courses)

如果要使用 Map 方法指定联接表名称和表中列的名称,则需要执行其他配置。 下面的代码生成包含 CourseID 和 InstructorID 列的 Courseinstructor.courseid 表。

  1. modelBuilder.Entity<Course>()
  2. .HasMany(t => t.Instructors)
  3. .WithMany(t => t.Courses)
  4. .Map(m =>
  5. {
  6. m.ToTable("CourseInstructor");
  7. m.MapLeftKey("CourseID");
  8. m.MapRightKey("InstructorID");
  9. });

使用一个导航属性配置关系Configuring a Relationship with One Navigation Property

单向(也称为单向)关系是指仅在一个关系端上定义导航属性,而不是在两者上定义。 按照约定,Code First 始终将单向关系解释为一对多。 例如,如果你想要在讲师与 OfficeAssignment 之间进行一对一关系,其中仅有指导员类型的导航属性,则需要使用 Fluent API 来配置此关系。

  1. // Configure the primary Key for the OfficeAssignment
  2. modelBuilder.Entity<OfficeAssignment>()
  3. .HasKey(t => t.InstructorID);
  4. modelBuilder.Entity<Instructor>()
  5. .HasRequired(t => t.OfficeAssignment)
  6. .WithRequiredPrincipal();

启用级联删除Enabling Cascade Delete

您可以通过使用 WillCascadeOnDelete 方法为关系配置级联删除。 如果从属实体上的外键不可为 null,则 Code First 在关系上设置级联删除。 如果从属实体上的外键可为 null,则 Code First 不会对关系设置级联删除,并且在删除主体时,外键将设置为 null。

您可以使用以下方法删除这些级联删除约定:

modelBuilder()
modelBuilder()

下面的代码将关系配置为 “必需”,然后禁用级联删除。

  1. modelBuilder.Entity<Course>()
  2. .HasRequired(t => t.Department)
  3. .WithMany(t => t.Courses)
  4. .HasForeignKey(d => d.DepartmentID)
  5. .WillCascadeOnDelete(false);

配置复合外键Configuring a Composite Foreign Key

如果部门类型上的主键由 DepartmentID 和 Name 属性组成,则可以为该部门配置主键,并为课程类型配置外键,如下所示:

  1. // Composite primary key
  2. modelBuilder.Entity<Department>()
  3. .HasKey(d => new { d.DepartmentID, d.Name });
  4. // Composite foreign key
  5. modelBuilder.Entity<Course>()
  6. .HasRequired(c => c.Department)
  7. .WithMany(d => d.Courses)
  8. .HasForeignKey(d => new { d.DepartmentID, d.DepartmentName });

重命名未在模型中定义的外键Renaming a Foreign Key That Is Not Defined in the Model

如果选择不在 CLR 类型上定义外键,但要指定它应在数据库中具有的名称,请执行以下操作:

  1. modelBuilder.Entity<Course>()
  2. .HasRequired(c => c.Department)
  3. .WithMany(t => t.Courses)
  4. .Map(m => m.MapKey("ChangedDepartmentID"));

配置不遵循 Code First 约定的外键名称Configuring a Foreign Key Name That Does Not Follow the Code First Convention

如果课程类的外键属性称为 SomeDepartmentID 而不是 DepartmentID,则需要执行以下操作,以指定要 SomeDepartmentID 为外键:

  1. modelBuilder.Entity<Course>()
  2. .HasRequired(c => c.Department)
  3. .WithMany(d => d.Courses)
  4. .HasForeignKey(c => c.SomeDepartmentID);

示例中使用的模型Model Used in Samples

以下 Code First 模型用于此页上的示例。

  1. using System.Data.Entity;
  2. using System.Data.Entity.ModelConfiguration.Conventions;
  3. // add a reference to System.ComponentModel.DataAnnotations DLL
  4. using System.ComponentModel.DataAnnotations;
  5. using System.Collections.Generic;
  6. using System;
  7. public class SchoolEntities : DbContext
  8. {
  9. public DbSet<Course> Courses { get; set; }
  10. public DbSet<Department> Departments { get; set; }
  11. public DbSet<Instructor> Instructors { get; set; }
  12. public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
  13. protected override void OnModelCreating(DbModelBuilder modelBuilder)
  14. {
  15. // Configure Code First to ignore PluralizingTableName convention
  16. // If you keep this convention then the generated tables will have pluralized names.
  17. modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
  18. }
  19. }
  20. public class Department
  21. {
  22. public Department()
  23. {
  24. this.Courses = new HashSet<Course>();
  25. }
  26. // Primary key
  27. public int DepartmentID { get; set; }
  28. public string Name { get; set; }
  29. public decimal Budget { get; set; }
  30. public System.DateTime StartDate { get; set; }
  31. public int? Administrator { get; set; }
  32. // Navigation property
  33. public virtual ICollection<Course> Courses { get; private set; }
  34. }
  35. public class Course
  36. {
  37. public Course()
  38. {
  39. this.Instructors = new HashSet<Instructor>();
  40. }
  41. // Primary key
  42. public int CourseID { get; set; }
  43. public string Title { get; set; }
  44. public int Credits { get; set; }
  45. // Foreign key
  46. public int DepartmentID { get; set; }
  47. // Navigation properties
  48. public virtual Department Department { get; set; }
  49. public virtual ICollection<Instructor> Instructors { get; private set; }
  50. }
  51. public partial class OnlineCourse : Course
  52. {
  53. public string URL { get; set; }
  54. }
  55. public partial class OnsiteCourse : Course
  56. {
  57. public OnsiteCourse()
  58. {
  59. Details = new Details();
  60. }
  61. public Details Details { get; set; }
  62. }
  63. public class Details
  64. {
  65. public System.DateTime Time { get; set; }
  66. public string Location { get; set; }
  67. public string Days { get; set; }
  68. }
  69. public class Instructor
  70. {
  71. public Instructor()
  72. {
  73. this.Courses = new List<Course>();
  74. }
  75. // Primary key
  76. public int InstructorID { get; set; }
  77. public string LastName { get; set; }
  78. public string FirstName { get; set; }
  79. public System.DateTime HireDate { get; set; }
  80. // Navigation properties
  81. public virtual ICollection<Course> Courses { get; private set; }
  82. }
  83. public class OfficeAssignment
  84. {
  85. // Specifying InstructorID as a primary
  86. [Key()]
  87. public Int32 InstructorID { get; set; }
  88. public string Location { get; set; }
  89. // When Entity Framework sees Timestamp attribute
  90. // it configures ConcurrencyCheck and DatabaseGeneratedPattern=Computed.
  91. [Timestamp]
  92. public Byte[] Timestamp { get; set; }
  93. // Navigation property
  94. public virtual Instructor Instructor { get; set; }
  95. }