Определение отношений и управление отношениями (платформа Entity Framework)

В Entity Framework сущность может быть связана с другими сущностями с помощью сопоставлений. Это отношение между сущностями определяется в концептуальной модели элементом Association. Каждое отношение содержит два конечных элемента, описывающих тип сущности и кратность типа (один, ноль или один, много). Отношение может управляться справочным ограничением, описывающим, какой из конечных элементов отношения относится к основной роли, а какой к зависимой роли.

Начиная с версии 4 .NET Framework, в концептуальную модель можно включать внешние ключи. По умолчанию в мастере модель EDM (сущностная модель данных) установлен параметр Включить столбцы внешнего ключа в модель. Если этот параметр выбран, то формируемые объекты сущности имеют скалярные свойства, сопоставленные со столбцами внешнего ключа. Включение свойств внешних ключей позволяет создавать или изменять отношение, изменяя значение внешнего ключа для зависимого объекта. Сопоставление такого типа называется сопоставлением на основе внешнего ключа.

Если столбцы внешних ключей не включены в концептуальную модель, данные сопоставления управляются как независимый объект. Отношения отслеживаются с помощью ссылок на объекты, а не свойств внешних ключей и представляются как объект ObjectStateEntry в ObjectStateManager. Сопоставление такого типа называется независимым сопоставлением. Чаще всего изменение независимого сопоставления осуществляется путем изменения свойств навигации, которые создаются для каждой участвующей в сопоставлении сущности.

В сопоставлениях обоих типов у каждого объекта может быть свойство навигации для каждого отношения, в котором он участвует. С помощью свойств навигации можно переходить по отношениям и управлять ими в обоих направлениях, при этом для кратности «один» либо «ноль или один» возвращается ссылочный объект, а для кратности «много» возвращается коллекция объектов.

Смешение отношений

В модели можно использовать один или оба типа сопоставлений. Однако в случае включения в модель таблицы, содержащей только внешние ключи (так называемой чистой соединяемой таблицы), независимое сопоставление будет использоваться для управления отношением «многие ко многим», даже если для модели выбрано использование сопоставления на основе внешнего ключа. Мастер модель EDM (сущностная модель данных) не создает сущность, сопоставляемую с чистой соединяемой таблицей.

Создание и изменение отношений

В Entity Framework создавать и изменять отношения можно несколькими способами.

  1. С помощью присваивания нового объекта свойству навигации. Следующий код создает отношение между order и customer. Если объекты присоединены к контексту объекта, объект order добавляется в коллекцию customer.Orders, а соответствующему свойству внешнего ключа для объекта order присваивается значение свойства ключа клиента:

    order.Customer = customer 
    
  2. Путем удаления или добавления объекта в коллекцию сущностей. Например, можно использовать метод Add для добавления объекта типа Order в коллекцию customer.Orders. Эта операция создает отношение между конкретным объектом order и customer. Если объекты присоединены к контексту объекта, ссылке на клиента и свойству внешнего ключа для объекта order присваиваются значения, соответствующие данному объекту customer:

    customer.Orders.Add(order)
    
  3. В сопоставлениях на основе внешнего ключа можно присвоить новое значение свойству внешнего ключа, как показано в следующем примере. Если ссылка находится в добавленном состоянии, свойство навигации ссылки не будет синхронизировано со значениями ключа нового объекта, пока не будет вызван метод SaveChanges. Синхронизация не выполняется, поскольку контекст объекта не содержит постоянных ключей для добавленных объектов, пока они не будут сохранены. Дополнительные сведения см. в разделе Работа с ключами сущностей (платформа Entity Framework). Если необходимо выполнить полную синхронизацию новых объектов немедленно после задания отношения, используйте один из двух предыдущих методов.

    order.CustomerID = newCustomer.CustomerID 
    order.CustomerID = null
    
  4. Путем создания ключа сущности для конкретного объекта. Если объект с таким ключом уже существует в контексте объекта, метод CreateEntityKey возвращает EntityKey существующего объекта. Этот метод предоставляется для обеспечения обратной совместимости с .NET Framework 3.5 с пакетом обновления 1 (SP1).

    order.CustomerReference.EntityKey = ctx.CreateEntityKey("EntitySetName", newObject)
    

При изменении отношения объектов, присоединенных к контексту объекта, с использованием одного из описанных выше методов Entity Framework необходимо поддерживать внешние ключи, ссылки и коллекции в синхронизированном состоянии. Entity Framework управляет этой синхронизацией для определенных типов допустимых объектов.

При использовании сущностей 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();  

В независимом сопоставлении связанный конечный элемент зависимого объекта запрашивается на основе значения внешнего ключа зависимого объекта, находящегося на момент загрузки в базе данных. Однако, если отношение изменено и ссылочное свойство зависимого объекта указывает на другой основной объект, загружаемый в контексте объекта, Entity Framework пытается создать отношение в том виде, в котором оно определено на клиенте.

Рекомендации по использованию идентифицирующих и неидентифицирующих отношений

Если первичный ключ основной сущности является также частью первичного ключа зависимой сущности, то отношение называется идентифицирующим. В идентифицирующем отношении зависимая сущность не может существовать без основной сущности. Этим ограничением обусловлены следующие особенности идентифицирующего отношения.

  • Удаление основного объекта приводит к удалению зависимого объекта. Результат этого действия аналогичен результату задания атрибута <OnDelete Action="Cascade" /> в модели для этого отношения.

  • При удалении отношения удаляется и зависимый объект. При вызове метода Remove для коллекции EntityCollection отношение и зависимый объект помечаются для удаления.

  • При создании нового зависимого объекта основной объект должен существовать в контексте объекта или в источнике данных перед вызовом метода SaveChanges. Если основной объект не существует, происходит исключение InvalidOperationException.

В неидентифицирующем отношении, если модель основана на сопоставлениях на основе внешнего ключа, при удалении основного объекта внешним ключам зависимых объектов присваивается значение null (если они допускают это значение). Зависимые объекты, существование которых невозможно без основного объекта, необходимо удалить вручную или назначить для них новый основной объект. В качестве альтернативы можно задать в модели атрибут <OnDelete Action="Cascade" />, чтобы обеспечить удаление зависимых объектов при удалении связанного основного объекта.

В этом разделе

Свойства навигации

Как использовать свойство внешнего ключа для изменения связей между объектами

Как изменить связи между объектами с помощью объекта EntityReference (платформа Entity Framework)

Как изменить связи между сущностями POCO (платформа Entity Framework)

См. также

Задачи

Как изменить связи между объектами с помощью объекта EntityReference (платформа Entity Framework)
Как использовать свойство внешнего ключа для изменения связей между объектами

Основные понятия

Создание, добавление, изменение и удаление объектов (платформа Entity Framework)
Работа с сущностями POCO (платформа Entity Framework)