Fluent API - 关系

注意

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

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

介绍

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

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

配置必需对可选关系(一对零或一)

以下示例将配置一对零或一的关系。 OfficeAssignment 具有 InstructorID 属性(前者是主键,后者是外键),因为该属性的名称未遵循 HasKey 方法用于配置主键的约定。

// Configure the primary key for the OfficeAssignment
modelBuilder.Entity<OfficeAssignment>()
    .HasKey(t => t.InstructorID);

// Map one-to-zero or one relationship
modelBuilder.Entity<OfficeAssignment>()
    .HasRequired(t => t.Instructor)
    .WithOptional(t => t.OfficeAssignment);

配置两端均为必需类型的关系(一对一)

在大多数情况下,实体框架可以推断出某个关系中哪个类型是从属,哪个类型是主体。 但是,如果关系的两端都是必需类型或者都是可选类型,实体框架就无法识别从属和主体。 如果关系的两端都是必需类型,请在 HasRequired 方法之后使用 WithRequiredPrincipal 或 WithRequiredDependent。 如果关系的两端都是可选类型,请在 HasOptional 方法之后使用 WithOptionalPrincipal 或 WithOptionalDependent。

// Configure the primary key for the OfficeAssignment
modelBuilder.Entity<OfficeAssignment>()
    .HasKey(t => t.InstructorID);

modelBuilder.Entity<Instructor>()
    .HasRequired(t => t.OfficeAssignment)
    .WithRequiredPrincipal(t => t.Instructor);

配置多对多关系

以下代码将在 Course 和 Instructor 类型之间配置一个多对多关系。 在以下示例中,使用默认 Code First 约定创建联接表。 因此,CourseInstructor 表是用 Course_CourseID 和 Instructor_InstructorID 列创建的。

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

如果要指定联接表名称和表中列的名称,需要使用 Map 方法进行额外的配置。 以下代码将生成包含 CourseID 和 InstructorID 列的 CourseInstructor 表。

modelBuilder.Entity<Course>()
    .HasMany(t => t.Instructors)
    .WithMany(t => t.Courses)
    .Map(m =>
    {
        m.ToTable("CourseInstructor");
        m.MapLeftKey("CourseID");
        m.MapRightKey("InstructorID");
    });

使用一个导航属性配置关系

单向关系是指仅在关系的一端而不是两端定义导航属性。 按照约定,Code First 始终将单向关系解释为一对多。 例如,如果你希望在 Instructor 和 OfficeAssignment 之间建立一对一的关系(只有 Instructor 类型上有导航属性),则需要使用 Fluent API 来配置此关系。

// Configure the primary Key for the OfficeAssignment
modelBuilder.Entity<OfficeAssignment>()
    .HasKey(t => t.InstructorID);

modelBuilder.Entity<Instructor>()
    .HasRequired(t => t.OfficeAssignment)
    .WithRequiredPrincipal();

启用级联删除

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

可以使用以下命令删除这些级联删除约定:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>()
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>()

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

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

配置复合外键

如果 Department 类型上的主键由 DepartmentID 和 Name 属性组成,则配置 Department 的主键和 Course 类型上的外键,如下所示:

// Composite primary key
modelBuilder.Entity<Department>()
.HasKey(d => new { d.DepartmentID, d.Name });

// Composite foreign key
modelBuilder.Entity<Course>()  
    .HasRequired(c => c.Department)  
    .WithMany(d => d.Courses)
    .HasForeignKey(d => new { d.DepartmentID, d.DepartmentName });

重命名未在模型中定义的外键

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

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

配置不遵循 Code First 约定的外键名称

如果 Course 类上外键属性的名称为 SomeDepartmentID 而不是 DepartmentID,你需要执行以下命令,以指定你希望将 SomeDepartmentID 用作外键:

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

示例中使用的模型

本页上的示例使用以下 Code First 模型。

using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
// add a reference to System.ComponentModel.DataAnnotations DLL
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System;

public class SchoolEntities : DbContext
{
    public DbSet<Course> Courses { get; set; }
    public DbSet<Department> Departments { get; set; }
    public DbSet<Instructor> Instructors { get; set; }
    public DbSet<OfficeAssignment> OfficeAssignments { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // Configure Code First to ignore PluralizingTableName convention
        // If you keep this convention then the generated tables will have pluralized names.
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
    }
}

public class Department
{
    public Department()
    {
        this.Courses = new HashSet<Course>();
    }
    // Primary key
    public int DepartmentID { get; set; }
    public string Name { get; set; }
    public decimal Budget { get; set; }
    public System.DateTime StartDate { get; set; }
    public int? Administrator { get; set; }

    // Navigation property
    public virtual ICollection<Course> Courses { get; private set; }
}

public class Course
{
    public Course()
    {
        this.Instructors = new HashSet<Instructor>();
    }
    // Primary key
    public int CourseID { get; set; }

    public string Title { get; set; }
    public int Credits { get; set; }

    // Foreign key
    public int DepartmentID { get; set; }

    // Navigation properties
    public virtual Department Department { get; set; }
    public virtual ICollection<Instructor> Instructors { get; private set; }
}

public partial class OnlineCourse : Course
{
    public string URL { get; set; }
}

public partial class OnsiteCourse : Course
{
    public OnsiteCourse()
    {
        Details = new Details();
    }

    public Details Details { get; set; }
}

public class Details
{
    public System.DateTime Time { get; set; }
    public string Location { get; set; }
    public string Days { get; set; }
}

public class Instructor
{
    public Instructor()
    {
        this.Courses = new List<Course>();
    }

    // Primary key
    public int InstructorID { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public System.DateTime HireDate { get; set; }

    // Navigation properties
    public virtual ICollection<Course> Courses { get; private set; }
}

public class OfficeAssignment
{
    // Specifying InstructorID as a primary
    [Key()]
    public Int32 InstructorID { get; set; }

    public string Location { get; set; }

    // When Entity Framework sees Timestamp attribute
    // it configures ConcurrencyCheck and DatabaseGeneratedPattern=Computed.
    [Timestamp]
    public Byte[] Timestamp { get; set; }

    // Navigation property
    public virtual Instructor Instructor { get; set; }
}