定义和管理关系(实体框架)
在实体框架 中,实体可以通过关联与其他实体相关。 Association 元素定义了概念模型中实体之间的关系。 每个关系包含两个端,说明了实体类型和重数类型(一、零或一、或者多个)。 关系可由引用约束控制,该引用约束描述了关系中的哪端为 Principal Role 以及哪端为 Dependent Role。
从 .NET Framework 版本 4 开始,您可以在概念模型中包含外键。 默认情况下,实体数据模型 向导中的**“在模型中加入外键列”**选项处于选中状态。 如果选中此选项,生成的实体对象具有映射到外键列的标量属性。 加入了外键属性,您就可以通过修改依赖对象的外键值来创建或更改关系。 此类关联称为外键关联。
如果概念模型中没有加入外键列,则关联信息将作为独立对象进行管理。 关系可通过对象引用而非外键属性进行跟踪,且在 ObjectStateManager 中表示为 ObjectStateEntry 对象。此类型的关联称为独立关联。 修改独立关联的最常用方法就是修改为参与到关联中的每个实体生成的导航属性。
在两种类型的关联中,针对对象参与到其中的每个关系,各对象均可以具有导航属性。 使用导航属性,您可以在两个方向上导航和管理关系,在重数为一或者零或一的情况下,返回引用对象,在重数为多个的情况下,返回对象的集合。
混合关系
可以在您的模型中选择使用一种或两种类型的关联。 但是,如果模型中加入了仅包含外键(也称为纯联接表)的表,则将使用独立关联管理纯多对多关系,即使您已指定在模型中使用外键关联。 实体数据模型 向导不创建映射到纯联接表的实体。
创建和修改关系
在实体框架 中,可以采用多种方式创建和修改关系。
通过将一个新对象分配给导航属性。 下面的代码在
order
与customer
之间创建关系。 如果将对象附加到对象上下文,则order
将添加到customer.Orders
集合,且order
对象的相应外键属性将设置为客户的键属性值:order.Customer = customer
通过在实体集合中删除或添加对象。 例如,可以使用 Add 方法将类型为 Order 的对象添加到
customer.Orders
集合。 此操作在特定order
与customer
之间创建关系。 如果将对象附加到对象上下文,则order
对象的客户引用和外键属性将设置为适当的customer
:customer.Orders.Add(order)
在外键关联中,可以将一个新值赋予外键属性,如下面的示例所示。 如果引用处于已添加状态,则引用导航属性将不与新对象的键值同步,直至调用 SaveChanges。 由于对象上下文在键值保存前不包含已添加对象的永久键,因此不发生同步。 有关更多信息,请参见使用实体键(实体框架)。 如果必须在设置关系后对新对象进行完全同步,请使用前面两种方法之一。
order.CustomerID = newCustomer.CustomerID order.CustomerID = null
通过为特定的对象创建实体键。 如果对象上下文中具有此键的对象已存在,则 CreateEntityKey 方法将返回该现有对象的 EntityKey。 提供此方法是为了与 .NET Framework 3.5 SP1 向后兼容。
order.CustomerReference.EntityKey = ctx.CreateEntityKey("EntitySetName", newObject)
在使用上面介绍的几种方法之一更改附加到对象上下文的对象的关系时,实体框架 需要保持外键、引用以及集合处于同步状态。 实体框架 将管理合格对象的特定类型的此同步:
其代码由实体数据模型 工具生成的实体对象。
实现 IEntityWithChangeTracker 接口的自定义对象。
带有代理的 POCO 实体。 有关更多信息,请参见创建 POCO 代理的要求(实体框架)。
如果使用的是不含代理的 POCO,则必须调用 DetectChanges 方法以同步对象上下文中的相关对象。 如果使用的是已断开连接的对象,则必须手动管理同步。
下面的示例演示如何使用外键属性和导航属性关联相关对象。 通过外键关联,可以使用两种方法更改创建或修改关系。 使用独立关联,则不能使用外键属性。
' The following example creates a new StudentGrade object and associates
' the StudentGrade with the Course and Person by
' setting the foreign key properties.
Using context As New SchoolEntities()
' The database will generate the EnrollmentID.
' To create the association between the Course and StudentGrade,
' and the Student and the StudentGrade, set the foreign key property
' to the ID of the principal.
Dim newStudentGrade = New StudentGrade With
{
.EnrollmentID = 0,
.Grade = CDec(4.0),
.CourseID = 4022,
.StudentID = 17
}
' Adding the new object to the context will synchronize
' the references with the foreign keys on the newStudentGrade object.
context.StudentGrades.AddObject(newStudentGrade)
' You can access Course and Student objects on the newStudentGrade object
' without loading the references explicitly because
' the lazy loading option is set to true in the constructor of SchoolEntities.
Console.WriteLine("Student ID {0}:", newStudentGrade.Person.PersonID)
Console.WriteLine("Course ID {0}:", newStudentGrade.Course.CourseID)
context.SaveChanges()
End Using
' The following example creates a new StudentGrade and associates
' the StudentGrade with the Course and Person by
' setting the navigation properties to the Course and Person objects that were returned
' by the query.
' You do not need to call AddObject() in order to add the grade object
' to the context, because when you assign the reference
' to the navigation property the objects on both ends get synchronized by the Entity Framework.
' Note, that the Entity Framework will not synchronize the ends untill the SaveChanges method
' is called if your objects do not meet the change tracking requirements.
Using context = New SchoolEntities()
Dim courseID = 4022
Dim course = (From c In context.Courses
Where c.CourseID = courseID
Select c).First()
Dim personID = 17
Dim student = (From p In context.People
Where p.PersonID = personID
Select p).First()
' The database will generate the EnrollmentID.
' Use the navigation properties to create the association between the objects.
Dim newStudentGrade = New StudentGrade With
{
.EnrollmentID = 0,
.Grade = CDec(4.0),
.Course = course,
.Person = student
}
context.SaveChanges()
End Using
// The following example creates a new StudentGrade object and associates
// the StudentGrade with the Course and Person by
// setting the foreign key properties.
using (SchoolEntities context = new SchoolEntities())
{
StudentGrade newStudentGrade = new StudentGrade
{
// The database will generate the EnrollmentID.
EnrollmentID = 0,
Grade = 4.0M,
// To create the association between the Course and StudentGrade,
// and the Student and the StudentGrade, set the foreign key property
// to the ID of the principal.
CourseID = 4022,
StudentID = 17,
};
// Adding the new object to the context will synchronize
// the references with the foreign keys on the newStudentGrade object.
context.StudentGrades.AddObject(newStudentGrade);
// You can access Course and Student objects on the newStudentGrade object
// without loading the references explicitly because
// the lazy loading option is set to true in the constructor of SchoolEntities.
Console.WriteLine("Student ID {0}:", newStudentGrade.Person.PersonID);
Console.WriteLine("Course ID {0}:", newStudentGrade.Course.CourseID);
context.SaveChanges();
}
// The following example creates a new StudentGrade and associates
// the StudentGrade with the Course and Person by
// setting the navigation properties to the Course and Person objects that were returned
// by the query.
// You do not need to call AddObject() in order to add the grade object
// to the context, because when you assign the reference
// to the navigation property the objects on both ends get synchronized by the Entity Framework.
// Note, that the Entity Framework will not synchronize the ends untill the SaveChanges method
// is called if your objects do not meet the change tracking requirements.
using (var context = new SchoolEntities())
{
int courseID = 4022;
var course = (from c in context.Courses
where c.CourseID == courseID
select c).First();
int personID = 17;
var student = (from p in context.People
where p.PersonID == personID
select p).First();
StudentGrade grade = new StudentGrade
{
// The database will generate the EnrollmentID.
Grade = 4.0M,
// Use the navigation properties to create the association between the objects.
Course = course,
Person = student
};
context.SaveChanges();
}
状态修改
在外键关联中,在更改关系后,具有 Unchanged 状态的依赖对象的状态将更改为 Modified。
在独立关系中,更改关系将不更新依赖对象的状态。
管理并发
在外键和独立关联中,通过将 ConcurrencyMode 特性设置为 fixed,并发检查基于实体键和在概念层中定义的其他实体属性。
但在独立关联中,始终在关系的两端对对象执行关系并发检查。 此检查验证相关端的原始键值。 如果在对象从对象上下文分离时修改关系,则必须重新创建原始关系(通过重新查询或者携带原始键值),将对象附加到对象上下文,然后对该对象上下文中的关系进行适当更改。
使用重叠键
重叠键是指键中某些属性亦是实体中其他键的一部分的那些组合键。 在独立关联中不能包含重叠键。 若要更改包含重叠键的外键关联,我们建议您修改外键值而不要使用对象引用。
加载相关对象
在外键关联中,加载依赖对象的相关端时,将会基于当前位于内存中的依赖对象的外键值加载相关对象:
// Get the order where currently AddressID = 1. SalesOrderHeader order = context.SalesOrderHeaders.First(o=>o.SalesOrderID == orderId);
// Use BillToAddressID foreign key property
// to change the association. order.BillToAddressID = 2
order.AddressReference.Load();
在独立关联中,基于当前数据库中的外键值查询依赖对象的相关端。 但是,如果关系已修改,且依赖对象的引用属性指向对象上下文中加载的不同主体对象,则实体框架 将尝试按照客户端上的定义创建关系。
标识关系和非标识关系的注意事项
如果主体实体的主键亦是依赖实体的主键的一部分,则关系为标识关系。 在标识关系中,没有主体实体,依赖实体就不能存在。 在标识关系中,此约束会导致下面的行为:
删除主体对象也将删除从属对象。 这与在模型中为关系指定
<OnDelete Action="Cascade" />
的行为相同。删除关系将会删除依赖对象。 对 EntityCollection 调用 Remove 方法会将关系和依赖对象标记为待删除。
如果创建一个新的依赖对象,则在调用 SaveChanges 前,主体对象必须存在于对象上下文或数据源中。 如果主体对象不存在,则将引发 InvalidOperationException。
在非标识关系中,如果模型基于外键关联,则删除主体对象会将依赖对象的外键设置为 null(如果可以为 Null)。 对于没有主体对象就不能存在的依赖对象,必须手动删除依赖对象或为依赖对象分配一个新的主体对象。 或者,也可以在模型中指定 <OnDelete Action="Cascade" />
以确保在删除相关的主体对象后依赖对象也将删除。
本节内容
如何:使用 EntityReference 更改对象之间的关系(实体框架)
另请参见
任务
如何:使用 EntityReference 更改对象之间的关系(实体框架)
如何:使用外键属性更改对象之间的关系