Связи, свойства навигации и внешние ключи

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

Связи в EF

В реляционных базах данных связи (также называемые ассоциациями) между таблицами определяются через внешние ключи. Внешний ключ (FK) — это столбец или сочетание столбцов, которые используются для установления и принудительного применения связи между данными в двух таблицах. Как правило, существуют три типа отношений: "один к одному", "один ко многим" и "многие ко многим". В связи "один ко многим" внешний ключ определяется в таблице, представляющей много конца связи. Связь "многие ко многим" включает определение третьей таблицы (называемой таблицей соединения или соединения), первичный ключ которой состоит из внешних ключей из обеих связанных таблиц. В связи "один к одному" первичный ключ выступает дополнительно в качестве внешнего ключа, и для любой таблицы нет отдельного внешнего ключевого столбца.

На следующем рисунке показаны две таблицы, участвующие в отношениях "один ко многим". Таблица "Курс" является зависимой таблицей, так как она содержит столбец DepartmentID, который связывает ее с таблицей Отдела.

Department and Course tables

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

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

Рекомендуется включить свойства в модель, которая сопоставляется с внешними ключами в базе данных. Включение свойств внешних ключей позволяет создавать или изменять отношение, изменяя значение внешнего ключа для зависимого объекта. Сопоставление такого типа называется сопоставлением на основе внешнего ключа. Использование внешних ключей еще более важно при работе с отключенными сущностями. Обратите внимание, что при работе с 1–1 или 1–0.. 1 связи, нет отдельного внешнего ключевого столбца, свойство первичного ключа выступает в качестве внешнего ключа и всегда включается в модель.

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

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

На следующем рисунке показана концептуальная модель, созданная с помощью конструктора Entity Framework. Модель содержит две сущности, участвующие в связи "один ко многим". Оба сущности имеют свойства навигации. Курс является зависимой сущностью и имеет свойство внешнего ключа DepartmentID .

Department and Course tables with navigation properties

В следующем фрагменте кода показана та же модель, которая была создана с помощью 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; }
}

Настройка или сопоставление связей

На остальной части этой страницы описывается, как получить доступ к данным и управлять ими с помощью связей. Сведения о настройке связей в модели см. на следующих страницах.

Создание и изменение связей

При изменении связи внешнего ключа состояние зависимого объекта с изменением EntityState.Unchanged состояния изменяетсяEntityState.Modified. В независимой связи изменение связи не обновляет состояние зависимого объекта.

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

  • Назначив новое значение свойству внешнего ключа, как показано в следующем примере.

    course.DepartmentID = newCourse.DepartmentID;
    
  • Следующий код удаляет связь, задав внешний ключ значение NULL. Обратите внимание, что свойство внешнего ключа должно иметь значение NULL.

    course.DepartmentID = null;
    

    Примечание.

    Если ссылка находится в добавленном состоянии (в этом примере объект курса), свойство навигации ссылок не будет синхронизировано со значениями ключей нового объекта до вызова SaveChanges. Синхронизация не выполняется, поскольку контекст объекта не содержит постоянных ключей для добавленных объектов, пока они не будут сохранены. Если вы должны иметь новые объекты полностью синхронизированы сразу после установки связи, используйте один из следующих методов.*

  • С помощью присваивания нового объекта свойству навигации. Следующий код создает связь между курсом и курсом department. Если объекты присоединены к контексту, он course также добавляется в коллекцию, а соответствующее свойство внешнего ключа объекта course присваивается department.Courses значению ключевого свойства отдела.

    course.Department = department;
    
  • Чтобы удалить связь, задайте для свойства навигации значение null. Если вы работаете с Entity Framework, основанной на .NET 4.0, необходимо загрузить связанный конец, прежде чем задать значение NULL. Например:

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

    Начиная с Entity Framework 5.0, основанной на .NET 4.5, можно задать отношение NULL без загрузки связанного конца. Текущее значение также можно задать значение NULL с помощью следующего метода.

    context.Entry(course).Reference(c => c.Department).CurrentValue = null;
    
  • Путем удаления или добавления объекта в коллекцию сущностей. Например, можно добавить объект типа Course в коллекцию department.Courses . Эта операция создает связь между определенным курсом и конкретным department. Если объекты присоединены к контексту, ссылка на отдел и свойство внешнего ключа объекта курса будут заданы соответствующим departmentобразом.

    department.Courses.Add(newCourse);
    
  • С помощью ChangeRelationshipState метода можно изменить состояние указанной связи между двумя объектами сущности. Этот метод чаще всего используется при работе с приложениями уровня N и независимой ассоциацией (ее нельзя использовать с ассоциацией внешнего ключа). Кроме того, чтобы использовать этот метод, необходимо выполнить раскрывающийся список ObjectContext, как показано в приведенном ниже примере.
    В следующем примере существует связь "многие ко многим" между инструкторами и курсами. ChangeRelationshipState Вызов метода и передача EntityState.Added параметра позволяет SchoolContext знать, что связь была добавлена между двумя объектами:

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

    Обратите внимание, что при обновлении (а не только добавлении) связи необходимо удалить старую связь после добавления новой:

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

Синхронизация изменений между внешними ключами и свойствами навигации

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

Если вы используете сущности POCO без прокси-серверов, необходимо убедиться, что метод DetectChanges вызывается для синхронизации связанных объектов в контексте. Обратите внимание, что следующие API автоматически активируют вызов DetectChanges .

  • DbSet.Add
  • DbSet.AddRange
  • DbSet.Remove
  • DbSet.RemoveRange
  • DbSet.Find
  • DbSet.Local
  • DbContext.SaveChanges
  • DbSet.Attach
  • DbContext.GetValidationErrors
  • DbContext.Entry
  • DbChangeTracker.Entries
  • Выполнение запроса LINQ для DbSet

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

Примечание.

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

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

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

Управление параллелизмом

В внешних и независимых связях параллелизм проверка основаны на ключах сущностей и других свойствах сущностей, определенных в модели. При использовании конструктора EF для создания модели задайте для атрибута ConcurrencyMode фиксированное значение, чтобы указать, что свойство должно быть проверка для параллелизма. При использовании code First для определения модели используйте заметку ConcurrencyCheck о свойствах, которые необходимо проверка для параллелизма. При работе с кодом First можно также использовать заметкуTimeStamp, чтобы указать, что свойство должно быть проверка для параллелизма. В данном классе может быть только одно свойство метки времени. Код сначала сопоставляет это свойство с полем, не допускаемым значением NULL в базе данных.

Мы рекомендуем всегда использовать связь внешнего ключа при работе с сущностями, участвующими в параллелизме проверка и разрешении.

Дополнительные сведения см. в разделе "Обработка конфликтов параллелизма".

Работа с перекрывающимися ключами

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