熟知 API-关系Fluent API - Relationships

备注

本页提供有关使用 Fluent API 设置 Code First 模型中的关系的信息。This page provides information about setting up relationships in your Code First model using the fluent API. 有关 EF 中的关系以及如何使用关系访问和操作数据的一般信息,请参阅 关系 & 导航属性For general information about relationships in EF and how to access and manipulate data using relationships, see Relationships & Navigation Properties.

使用 Code First 时,可通过定义域 CLR 类定义模型。When working with Code First, you define your model by defining your domain CLR classes. 默认情况下,实体框架使用 Code First 约定将类映射到数据库架构。By default, Entity Framework uses the Code First conventions to map your classes to the database schema. 如果你使用 Code First 命名约定,则在大多数情况下,你可以依赖于 Code First 根据你在类上定义的外键和导航属性来设置表之间的关系。If you use the Code First naming conventions, in most cases you can rely on Code First to set up relationships between your tables based on the foreign keys and navigation properties that you define on the classes. 如果在定义类时不遵循约定,或者若要更改约定的工作方式,可以使用 Fluent API 或数据批注来配置类,以便 Code First 可以映射表之间的关系。If you do not follow the conventions when defining your classes, or if you want to change the way the conventions work, you can use the fluent API or data annotations to configure your classes so Code First can map the relationships between your tables.

简介Introduction

在配置与 Fluent API 的关系时,请从 EntityTypeConfiguration 实例开始,然后使用 HasRequired、HasOptional 或 HasMany 方法来指定此实体参与的关系的类型。When configuring a relationship with the fluent API, you start with the EntityTypeConfiguration instance and then use the HasRequired, HasOptional, or HasMany method to specify the type of relationship this entity participates in. HasRequired 和 HasOptional 方法采用表示引用导航属性的 lambda 表达式。The HasRequired and HasOptional methods take a lambda expression that represents a reference navigation property. HasMany 方法采用表示集合导航属性的 lambda 表达式。The HasMany method takes a lambda expression that represents a collection navigation property. 然后,可以通过使用 WithRequired、WithOptional 和 WithMany 方法配置反向导航属性。You can then configure an inverse navigation property by using the WithRequired, WithOptional, and WithMany methods. 这些方法具有不带参数的重载,可用于通过单向导航指定基数。These methods have overloads that do not take arguments and can be used to specify cardinality with unidirectional navigations.

然后,可以使用 HasForeignKey 方法配置外键属性。You can then configure foreign key properties by using the HasForeignKey method. 此方法采用一个表示要用作外键的属性的 lambda 表达式。This method takes a lambda expression that represents the property to be used as the foreign key.

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

下面的示例将配置一对零或一关系。The following example configures a one-to-zero-or-one relationship. OfficeAssignment 具有 InstructorID 属性,该属性是主键和外键,因为属性的名称不遵循该约定。 HasKey 方法用于配置主键。The OfficeAssignment has the InstructorID property that is a primary key and a foreign key, because the name of the property does not follow the convention the HasKey method is used to configure the primary key.

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

配置一种关系,在这种情况下,这两个端都需要 (一对一) Configuring a Relationship Where Both Ends Are Required (One-to-One)

在大多数情况下实体框架可以推断哪个类型是依赖项并且是关系中的主体。In most cases Entity Framework can infer which type is the dependent and which is the principal in a relationship. 但是,如果需要关系的两端,或者两个两侧都是可选的,则实体框架无法识别依赖项和主体。However, when both ends of the relationship are required or both sides are optional Entity Framework cannot identify the dependent and principal. 如果关系的两端都是必需的,请在 HasRequired 方法后面使用 WithRequiredPrincipal 或 WithRequiredDependent。When both ends of the relationship are required, use WithRequiredPrincipal or WithRequiredDependent after the HasRequired method. 如果关系两端都是可选的,请在 HasOptional 方法后面使用 WithOptionalPrincipal 或 WithOptionalDependent。When both ends of the relationship are optional, use WithOptionalPrincipal or WithOptionalDependent after the HasOptional method.

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

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

下面的代码配置课程类型和指导员类型之间的多对多关系。The following code configures a many-to-many relationship between the Course and Instructor types. 在下面的示例中,使用了默认 Code First 约定来创建联接表。In the following example, the default Code First conventions are used to create a join table. 因此,Courseinstructor.courseid 表是使用 Course_CourseID 和 Instructor_InstructorID 列创建的。As a result the CourseInstructor table is created with Course_CourseID and Instructor_InstructorID columns.

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

如果要使用 Map 方法指定联接表名称和表中列的名称,则需要执行其他配置。If you want to specify the join table name and the names of the columns in the table you need to do additional configuration by using the Map method. 下面的代码生成包含 CourseID 和 InstructorID 列的 Courseinstructor.courseid 表。The following code generates the CourseInstructor table with CourseID and InstructorID columns.

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

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

一种单向 (也称为单向) 关系,只是在一个关系端上定义导航属性,而不是在两者上定义。A one-directional (also called unidirectional) relationship is when a navigation property is defined on only one of the relationship ends and not on both. 按照约定,Code First 始终将单向关系解释为一对多。By convention, Code First always interprets a unidirectional relationship as one-to-many. 例如,如果你想要在讲师与 OfficeAssignment 之间进行一对一关系,其中仅有指导员类型的导航属性,则需要使用 Fluent API 来配置此关系。For example, if you want a one-to-one relationship between Instructor and OfficeAssignment, where you have a navigation property on only the Instructor type, you need to use the fluent API to configure this relationship.

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

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

启用级联删除Enabling Cascade Delete

您可以通过使用 WillCascadeOnDelete 方法为关系配置级联删除。You can configure cascade delete on a relationship by using the WillCascadeOnDelete method. 如果从属实体上的外键不可为 null,则 Code First 在关系上设置级联删除。If a foreign key on the dependent entity is not nullable, then Code First sets cascade delete on the relationship. 如果从属实体上的外键可为 null,则 Code First 不会对关系设置级联删除,并且在删除主体时,外键将设置为 null。If a foreign key on the dependent entity is nullable, Code First does not set cascade delete on the relationship, and when the principal is deleted the foreign key will be set to null.

您可以使用以下方法删除这些级联删除约定:You can remove these cascade delete conventions by using:

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

下面的代码将关系配置为 "必需",然后禁用级联删除。The following code configures the relationship to be required and then disables cascade delete.

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

配置复合外键Configuring a Composite Foreign Key

如果部门类型上的主键由 DepartmentID 和 Name 属性组成,则可以为该部门配置主键,并为课程类型配置外键,如下所示:If the primary key on the Department type consisted of DepartmentID and Name properties, you would configure the primary key for the Department and the foreign key on the Course types as follows:

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

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

如果选择不在 CLR 类型上定义外键,但要指定它应在数据库中具有的名称,请执行以下操作:If you choose not to define a foreign key on the CLR type, but want to specify what name it should have in the database, do the following:

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

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

如果课程类的外键属性称为 SomeDepartmentID 而不是 DepartmentID,则需要执行以下操作,以指定要 SomeDepartmentID 为外键:If the foreign key property on the Course class was called SomeDepartmentID instead of DepartmentID, you would need to do the following to specify that you want SomeDepartmentID to be the foreign key:

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

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

以下 Code First 模型用于此页上的示例。The following Code First model is used for the samples on this page.

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