通过


Fluent API - 关联

注释

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

使用 Code First 时,可以通过定义域 CLR 类来定义模型。 默认情况下,Entity Framework 使用 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);

配置需要两端的关系(一对一)

在大多数情况下,Entity Framework 可以推断哪个类型是依赖类型,哪个类型是关系中的主体。 但是,当关系两端都是必需的或两端都是可选的时候,Entity Framework 无法标识被依赖方和主体方。 如果两端关系都是必需的,请在 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);

配置多对多关系

以下代码配置课程和讲师类型之间的多对多关系。 在以下示例中,默认 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 之间建立一对一关系,且导航属性仅在讲师类型上,则需要使用 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 属性组成,则你将在课程类型上配置主键和外键,如下所示:

// 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"));

配置不遵循代码第一约定的外键名称

如果 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; }
}