关系、导航属性和外键Relationships, navigation properties, and foreign keys

本文概述了实体框架如何管理实体之间的关系。This article gives an overview of how Entity Framework manages relationships between entities. 它还提供了一些有关如何映射和操作关系的指导。It also gives some guidance on how to map and manipulate relationships.

EF 中的关系Relationships in EF

在关系数据库中,关系 (也称为关联) 在表之间通过外键定义。In relational databases, relationships (also called associations) between tables are defined through foreign keys. 外键 (FK) 是用于建立和加强两个表数据之间的链接的一列或多列。A foreign key (FK) is a column or combination of columns that is used to establish and enforce a link between the data in two tables. 通常有三种类型的关系:一对一、一对多和多对多的关系。There are generally three types of relationships: one-to-one, one-to-many, and many-to-many. 在一对多关系中,外键在表示关系的多个端的表上定义。In a one-to-many relationship, the foreign key is defined on the table that represents the many end of the relationship. 多对多关系涉及到定义第三个表 (称为联接表或联接表) ,其主键由两个相关表中的外键构成。The many-to-many relationship involves defining a third table (called a junction or join table), whose primary key is composed of the foreign keys from both related tables. 在一对一关系中,主键除了作为外键以外,对于任何一个表都没有单独的外键列。In a one-to-one relationship, the primary key acts additionally as a foreign key and there is no separate foreign key column for either table.

下图显示了参与一对多关系的两个表。The following image shows two tables that participate in one-to-many relationship. 课程表是依赖表,因为它包含将其链接到部门表的DepartmentID列。The Course table is the dependent table because it contains the DepartmentID column that links it to the Department table.

部门和课程表

在实体框架中,可以通过关联或关系将实体与其他实体相关。In Entity Framework, an entity can be related to other entities through an association or relationship. 每个关系都包含两个端,分别描述了该关系中的两个实体 (一个、零或一或多个) 类型的实体类型和重数。Each relationship contains two ends that describe the entity type and the multiplicity of the type (one, zero-or-one, or many) for the two entities in that relationship. 关系可由引用约束控制,该引用约束描述了关系中的哪端为 Principal Role 以及哪端为 Dependent Role。The relationship may be governed by a referential constraint, which describes which end in the relationship is a principal role and which is a dependent role.

导航属性提供了一种在两个实体类型之间导航关联的方法。Navigation properties provide a way to navigate an association between two entity types. 针对对象参与到其中的每个关系,各对象均可以具有导航属性。Every object can have a navigation property for every relationship in which it participates. 使用导航属性,您可以在两个方向上导航和管理关系,如果重数为1或零或一个) ,则返回引用对象 (; 如果重数为多) ,则返回 (集合。Navigation properties allow you to navigate and manage relationships in both directions, returning either a reference object (if the multiplicity is either one or zero-or-one) or a collection (if the multiplicity is many). 您还可以选择使用单向导航,在这种情况下,只需在参与关系的一种类型上定义导航属性,而不是在两者上定义。You may also choose to have one-way navigation, in which case you define the navigation property on only one of the types that participates in the relationship and not on both.

建议在模型中包含映射到数据库中的外键的属性。It is recommended to include properties in the model that map to foreign keys in the database. 加入了外键属性,您就可以通过修改依赖对象的外键值来创建或更改关系。With foreign key properties included, you can create or change a relationship by modifying the foreign key value on a dependent object. 此类关联称为外键关联。This kind of association is called a foreign key association. 在处理断开连接的实体时,使用外键更为重要。Using foreign keys is even more essential when working with disconnected entities. 请注意,当使用1对1或1到0时,请注意。1关系,没有单独的外键列,主键属性充当外键,并且始终包含在模型中。Note, that when working with 1-to-1 or 1-to-0..1 relationships, there is no separate foreign key column, the primary key property acts as the foreign key and is always included in the model.

如果模型中不包含外键列,则会将关联信息作为独立对象进行管理。When foreign key columns are not included in the model, the association information is managed as an independent object. 关系通过对象引用而不是外键属性进行跟踪。Relationships are tracked through object references instead of foreign key properties. 这种类型的关联称为 独立关联This type of association is called an independent association. 修改 独立关联 的最常见方法是修改为参与关联的每个实体生成的导航属性。The most common way to modify an independent association is to modify the navigation properties that are generated for each entity that participates in the association.

可以在您的模型中选择使用一种或两种类型的关联。You can choose to use one or both types of associations in your model. 但是,如果您具有通过仅包含外键的联接表连接的一种纯多对多关系,则 EF 将使用独立的关联来管理这类多对多关系。However, if you have a pure many-to-many relationship that is connected by a join table that contains only foreign keys, the EF will use an independent association to manage such many-to-many relationship.   

下图显示了使用 Entity Framework Designer 创建的概念模型。The following image shows a conceptual model that was created with the Entity Framework Designer. 该模型包含两个参与一对多关系的实体。The model contains two entities that participate in one-to-many relationship. 这两个实体都具有导航属性。Both entities have navigation properties. 当然 是依赖实体,并且定义了 DepartmentID 外键属性。Course is the dependent entity and has the DepartmentID foreign key property defined.

具有导航属性的部门和课程表

下面的代码片段显示了用 Code First 创建的同一模型。The following code snippet shows the same model that was created with Code First.

public class Course
{
  public int CourseID { get; set; }
  public string Title { get; set; }
  public int Credits { get; set; }
  public int DepartmentID { get; set; }
  public virtual Department Department { get; set; }
}

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

配置或映射关系Configuring or mapping relationships

本页的其余部分介绍如何使用关系访问和操作数据。The rest of this page covers how to access and manipulate data using relationships. 有关设置模型中的关系的信息,请参阅以下页面。For information on setting up relationships in your model, see the following pages.

创建和修改关系Creating and modifying relationships

外键关联中,当你更改关系时,状态为的依赖对象的状态将 EntityState.Unchanged 更改为 EntityState.ModifiedIn a foreign key association, when you change the relationship, the state of a dependent object with an EntityState.Unchanged state changes to EntityState.Modified. 在独立关系中,更改关系不会更新依赖对象的状态。In an independent relationship, changing the relationship does not update the state of the dependent object.

下面的示例演示如何使用外键属性和导航属性将相关对象关联起来。The following examples show how to use the foreign key properties and navigation properties to associate the related objects. 使用外键关联,可以使用这两种方法更改、创建或修改关系。With foreign key associations, you can use either method to change, create, or modify relationships. 使用独立关联,则不能使用外键属性。With independent associations, you cannot use the foreign key property.

  • 将新值分配给外键属性,如下面的示例中所示。By assigning a new value to a foreign key property, as in the following example.

    course.DepartmentID = newCourse.DepartmentID;
    
  • 下面的代码通过将外键设置为 null来移除关系。The following code removes a relationship by setting the foreign key to null. 请注意,外键属性必须可以为 null。Note, that the foreign key property must be nullable.

    course.DepartmentID = null;
    

    备注

    如果引用处于 "已添加" 状态 (在此示例中,课程对象) ,则在调用 SaveChanges 之前,引用导航属性将不会与新对象的键值同步。If the reference is in the added state (in this example, the course object), the reference navigation property will not be synchronized with the key values of a new object until SaveChanges is called. 由于对象上下文在键值保存前不包含已添加对象的永久键,因此不发生同步。Synchronization does not occur because the object context does not contain permanent keys for added objects until they are saved. 如果在设置关系后,必须完全同步新对象,请使用以下方法之一。If you must have new objects fully synchronized as soon as you set the relationship, use one of the following methods.*

  • 通过将一个新对象分配给导航属性。By assigning a new object to a navigation property. 下面的代码创建一个课程与之间的关系 departmentThe following code creates a relationship between a course and a department. 如果将对象附加到上下文,则 course 也会将添加到 department.Courses 集合中,对象上的相应外键属性 course 将设置为该部门的键属性值。If the objects are attached to the context, the course is also added to the department.Courses collection, and the corresponding foreign key property on the course object is set to the key property value of the department.

    course.Department = department;
    
  • 若要删除关系,请将导航属性设置为 nullTo delete the relationship, set the navigation property to null. 如果使用的是基于 .NET 4.0 的实体框架,则需要加载相关端,然后将其设置为 null。If you are working with Entity Framework that is based on .NET 4.0, then the related end needs to be loaded before you set it to null. 例如:For example:

    context.Entry(course).Reference(c => c.Department).Load();
    course.Department = null;
    

    从基于 .NET 4.5 实体框架5.0 开始,可以在不加载相关端的情况下将关系设置为 null。Starting with Entity Framework 5.0, that is based on .NET 4.5, you can set the relationship to null without loading the related end. 还可以使用以下方法将当前值设置为 null。You can also set the current value to null using the following method.

    context.Entry(course).Reference(c => c.Department).CurrentValue = null;
    
  • 通过在实体集合中删除或添加对象。By deleting or adding an object in an entity collection. 例如,可以将类型的对象添加 Coursedepartment.Courses 集合中。For example, you can add an object of type Course to the department.Courses collection. 此操作在特定的 课程 与特定的之间建立关系 departmentThis operation creates a relationship between a particular course and a particular department. 如果将对象附加到上下文,则会将 课程 对象上的部门引用和外键属性设置为相应的 departmentIf the objects are attached to the context, the department reference and the foreign key property on the course object will be set to the appropriate department.

    department.Courses.Add(newCourse);
    
  • 通过使用 ChangeRelationshipState 方法更改两个实体对象之间的指定关系的状态。By using the ChangeRelationshipState method to change the state of the specified relationship between two entity objects. 此方法最常用于使用 N 层应用程序和 独立关联 , (不能将其与外键关联) 一起使用。This method is most commonly used when working with N-Tier applications and an independent association (it cannot be used with a foreign key association). 此外,若要使用此方法,必须将下拉到 ObjectContext ,如下面的示例中所示。Also, to use this method you must drop down to ObjectContext, as shown in the example below.
    在下面的示例中,讲师和课程之间存在多对多的关系。In the following example, there is a many-to-many relationship between Instructors and Courses. 调用 ChangeRelationshipState 方法并传递 EntityState.Added 参数,可以 SchoolContext 了解在两个对象之间添加了关系:Calling the ChangeRelationshipState method and passing the EntityState.Added parameter, lets the SchoolContext know that a relationship has been added between the two objects:

    
    ((IObjectContextAdapter)context).ObjectContext.
      ObjectStateManager.
      ChangeRelationshipState(course, instructor, c => c.Instructor, EntityState.Added);
    

    请注意,如果您正在更新 (不只是添加) 关系,则必须在添加新关系之后删除旧关系:Note that if you are updating (not just adding) a relationship, you must delete the old relationship after adding the new one:

    ((IObjectContextAdapter)context).ObjectContext.
      ObjectStateManager.
      ChangeRelationshipState(course, oldInstructor, c => c.Instructor, EntityState.Deleted);
    

同步外键和导航属性之间的更改Synchronizing the changes between the foreign keys and navigation properties

使用上述方法之一更改附加到上下文的对象的关系时,实体框架需要使外键、引用和集合保持同步。实体框架会自动管理此同步 (也称为具有代理的 POCO 实体的关系修补) 。When you change the relationship of the objects attached to the context by using one of the methods described above, Entity Framework needs to keep foreign keys, references, and collections in sync. Entity Framework automatically manages this synchronization (also known as relationship fix-up) for the POCO entities with proxies. 有关详细信息,请参阅使用 代理For more information, see Working with Proxies.

如果使用不带代理的 POCO 实体,则必须确保调用 DetectChanges 方法来同步上下文中的相关对象。If you are using POCO entities without proxies, you must make sure that the DetectChanges method is called to synchronize the related objects in the context. 请注意,以下 Api 会自动触发 DetectChanges 调用。Note that the following APIs automatically trigger a DetectChanges call.

  • DbSet.Add
  • DbSet.AddRange
  • DbSet.Remove
  • DbSet.RemoveRange
  • DbSet.Find
  • DbSet.Local
  • DbContext.SaveChanges
  • DbSet.Attach
  • DbContext.GetValidationErrors
  • DbContext.Entry
  • DbChangeTracker.Entries
  • 对执行 LINQ 查询 DbSetExecuting a LINQ query against a DbSet

在实体框架通常使用导航属性来加载通过定义的关联与返回的实体相关的实体。In Entity Framework you commonly use navigation properties to load entities that are related to the returned entity by the defined association. 有关详细信息,请参阅 加载相关对象For more information, see Loading Related Objects.

备注

在外键关联中,加载依赖对象的相关端时,将会基于当前位于内存中的依赖对象的外键值加载相关对象:In a foreign key association, when you load a related end of a dependent object, the related object will be loaded based on the foreign key value of the dependent that is currently in memory:

    // Get the course where currently DepartmentID = 2.
    Course course = context.Courses.First(c => c.DepartmentID == 2);

    // Use DepartmentID foreign key property
    // to change the association.
    course.DepartmentID = 3;

    // Load the related Department where DepartmentID = 3
    context.Entry(course).Reference(c => c.Department).Load();

在独立关联中,基于当前数据库中的外键值查询依赖对象的相关端。In an independent association, the related end of a dependent object is queried based on the foreign key value that is currently in the database. 但是,如果关系已修改,且依赖对象的引用属性指向对象上下文中加载的其他主体对象,实体框架将尝试创建关系,因为它是在客户端上定义的。However, if the relationship was modified, and the reference property on the dependent object points to a different principal object that is loaded in the object context, Entity Framework will try to create a relationship as it is defined on the client.

管理并发Managing concurrency

在外键和独立关联中,并发检查基于在模型中定义的实体键和其他实体属性。In both foreign key and independent associations, concurrency checks are based on the entity keys and other entity properties that are defined in the model. 使用 EF 设计器创建模型时,请将属性设置 ConcurrencyMode 为 " 固定 ",以指定应检查属性的并发性。When using the EF Designer to create a model, set the ConcurrencyMode attribute to fixed to specify that the property should be checked for concurrency. 使用 Code First 定义模型时,请 ConcurrencyCheck 在要检查其并发性的属性上使用批注。When using Code First to define a model, use the ConcurrencyCheck annotation on properties that you want to be checked for concurrency. 使用 Code First 时,还可以使用 TimeStamp 批注来指定应检查属性的并发性。When working with Code First you can also use the TimeStamp annotation to specify that the property should be checked for concurrency. 给定类中只能有一个时间戳属性。You can have only one timestamp property in a given class. Code First 将此属性映射到数据库中不可以为 null 的字段。Code First maps this property to a non-nullable field in the database.

建议你始终在使用参与并发检查和解析的实体时使用外键关联。We recommend that you always use the foreign key association when working with entities that participate in concurrency checking and resolution.

有关详细信息,请参阅 处理并发冲突For more information, see Handling Concurrency Conflicts.

使用重叠键Working with overlapping Keys

重叠键是指键中某些属性亦是实体中其他键的一部分的那些组合键。Overlapping keys are composite keys where some properties in the key are also part of another key in the entity. 在独立关联中不能包含重叠键。You cannot have an overlapping key in an independent association. 若要更改包含重叠键的外键关联,我们建议您修改外键值而不要使用对象引用。To change a foreign key association that includes overlapping keys, we recommend that you modify the foreign key values instead of using the object references.