Entity framework отношение не существует

Обновлено: 02.07.2024

Мне нравится тот факт, что AddOrUpdate позволяет вам указать фильтр для проверки, чтобы избежать добавления дубликатов. Но я бы хотел подобный функционал без обновления.

Сейчас я делаю что-то вроде этого:

Я знаю, что могу сделать что-то вроде этого:

Но я бы предпочел пропустить изменение данных в этом случае.

Первый пример делает то, что я хочу, но с большим количеством кода. Есть ли более простой /чистый способ сделать то, что я хочу в первом примере?

Update

Мне нравится предложение Огняна Димитрова. Я пытаюсь это реализовать. Мои модели наследуются от BaseEntity. Могу ли я разместить общую версию этого там?

Моя модель определена:

Я получаю ошибки для Any (. ) и Add (. ). Ошибка добавления (. ): «Ссылка на объект требуется для нестатического поля, метода или свойства» System.Data.Entity.DbSet.Add (object) ''

Должен ли я использовать this.Add (object)?

Обновление 2:

Я создал этот код:

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

Я могу назвать DbSetextensions следующим образом:

Большое спасибо за работу со мной, Огнян .

Вы пытались проверить, существует ли сущность, а если нет - добавить ее? Вот так:

UPDATE

Вы можете использовать этот метод напрямую и не забывать вызывать DbContext.SaveChanges () после вызова.

Я использовал что-то вроде: прочитайте эти два поста, чтобы сделать мой код. Я надеюсь помочь тем, кто нуждается в аналогичной подписи для AddOrUpdate.

Вставить или обновить шаблон

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

Обратите внимание, что при изменении состояния на Изменено все свойства объект будет помечен как измененный, и все значения свойств будут быть отправлено в базу данных при вызове SaveChanges.

Решение в порядке, когда вам нужно добавить только один элемент, но это очень дорого с точки зрения производительности, если вам нужно добавить несколько элементов. Я думаю, что есть лучшее решение:

Поэтому позже его можно использовать так:

Единственное, что приходит на ум - это использовать IEqualityComparer<T> , но это не останавливает работу, а просто абстрагирует ее и создает более чистый код.

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

Связи в EF

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

На следующем рисунке показаны две таблицы, участвующие в связи «один ко многим». Таблица Course является зависимой таблицей, так как она содержит столбец DepartmentID , связывающий его с таблицей отдела .

Таблицы отделов и курсов

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

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

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

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

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

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

Таблицы отделов и курсов со свойствами навигации

В следующем фрагменте кода показана та же модель, которая была создана с помощью Code First.

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

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

  • сведения о настройке связей в Code First см. в разделе аннотации данных и Fluent API — связи.
  • Сведения о настройке связей с помощью Entity Framework Designer см. в разделе связи с конструктором EF.

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

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

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

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

Следующий код удаляет связь, присвоив внешнему ключу значение NULL. Обратите внимание, что свойство внешнего ключа должно допускать значение null.

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

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

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

С помощью ChangeRelationshipState метода можно изменить состояние указанной связи между двумя объектами сущностей. Этот метод чаще всего используется при работе с N-уровневых приложениями и независимой ассоциацией (его нельзя использовать с Ассоциацией внешнего ключа). Кроме того, чтобы использовать этот метод, необходимо раскрывающийся список ObjectContext , как показано в примере ниже.
В следующем примере существует связь «многие ко многим» между преподавателями и курсами. При вызове ChangeRelationshipState метода и передаче EntityState.Added параметра сообщается SchoolContext о том, что между двумя объектами была добавлена связь.

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

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

При изменении связи объектов, присоединенных к контексту с помощью одного из описанных выше методов, 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 свойства навигации обычно используются для загрузки сущностей, связанных с возвращаемой сущностью, с помощью заданной ассоциации. Дополнительные сведения см. в разделе Загрузка связанных объектов.

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

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

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

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

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

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

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

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

В предыдущей статье вы узнали о соглашениях Code-First по конфигурации столбцов таблицы базы данных. Здесь мы рассмотрим соглашения по созданию между таблицами в базе данных. Эти соглашения определяют то, как связываются классы в модели и настраиваются внешние ключи, определяющие эти связи. Для настройки этих конфигураций в основном используется Fluent API, а не аннотации данных.

Вы уже видели ранее использование некоторых связей между таблицами. Например, при рассмотрении примера в статье “Использование Code-First” мы создали следующую модель данных:

Code-First видит в этом примере, что вы определили навигационное свойство Orders в таблице Customer, ссылающееся на коллекцию объектов Order, что говорит о создании отношения один-ко-многим (one-to-many) между этими таблицами. Так же Code-First определит автоматически эту связь, создав внешний ключ для таблицы Order, привязанный к первичному ключу CustomerId таблицы Customer.

Далее мы опишем все соглашения, которые используются в Code-First для описания связей между таблицами.

Использование навигационных свойств

Как вы видели, Code-First автоматически добавляет отношения между таблицами если видит навигационные свойства в коде модели и, необязательно, явно указанные внешние ключи. Мы подробно опишем использование внешних ключей чуть позже, а сейчас давайте остановимся на определении отношений между таблицами без внешних ключей.

Отношения между таблицами можно определить в модели только за счет использования навигационных свойств, которые могут быть указаны в обеих таблицах (двусторонняя связь, как показано в примере выше) или в одной таблице (односторонняя связь). Ниже описаны некоторые соглашения Code-First при использовании навигационных свойств:

Code-First также будет предполагать связь один-ко-многим, если навигационное свойство используется только в одной таблице, вне зависимости от типа этого свойства (т.е. если используется односторонняя связь).

Если в обеих таблицах навигационные свойства имеют тип коллекций, то Code-First предполагает наличие связи между ними многие-ко-многим (many-to-many).

Если в обеих таблицах навигационные свойства представлены в виде ссылок друг на друга, то Code-First предполагает отношение между таблицами один-к-одному (one-to-one).

В случае реализации отношения один-к-одному, вы должны будете предоставить некоторую дополнительную информацию, чтобы Code-First знал, какая сущность является основной, а какая зависимой. Если в таблицах явно не указан внешний ключ, то Code-First смоделирует отношение один-или-ноль-к-одному (zero-or-one-to-one, 0..1-1), т.е. добавление данных в главную таблицу, необязательно должно вести к добавлению данных в зависимую таблицу.

Посмотрев пример нашей модели, показанной выше, можно проследить использование этих соглашений на классах Customer и Order. Например, Code-First автоматически создаст отношение между таблицами один-ко-многим, т.к. мы использовали тип коллекции в одной таблице и простую ссылку в другой. Также можно догадаться, что в отношении этих таблиц Customer будет главной, а Order зависимой, т.е. мы можем вставить данные заказчика в таблицу Customer, не добавляя при этом для него заказы. И наоборот, в таблицу Order мы можем вставить только заказ, привязанный к конкретному покупателю.

Стоит также сказать, что без явного указания внешних ключей в коде модели, Code-First будет генерировать эти ключи автоматически, используя имя, сочетающие в себе название базовой таблицы и первичного ключа этой таблицы. Например, для таблицы Order будет сгенерирован внешний ключ Customer_CustomerId.

Большую часть конфигурации по настройке навигационных свойств мы можем выполнить с помощью Fluent API. Некоторые настройки можно выполнить и с помощью аннотаций данных, хотя в данном случае этот подход намного более ограничен, чем использование Fluent API. Например, мы можем указать для автоматически генерируемого внешнего ключа, чтобы он не поддерживал значения NULL:

Если вы запустите приложение и обновите структуру базы данных, то обнаружите, что Code-First изменил тип внешнего ключа Customer_CustomerId – теперь он не может поддерживать значения NULL:

Отключение поддержки NULL для внешнего ключа с помощью аннотаций данных

Напомню, что мы используем в качестве примера приложение, созданное в статье “Использование Code-First” и для воссоздания базы данных при изменении модели требуется либо ее ручное удаление всякий раз, когда база данных изменилась, либо использование настроек Code-First по автоматическому обнаружению изменений в модели.

Настройка отношений с помощью Fluent API может показаться несколько запутанной, если вы не потратите некоторое время, чтобы понять основные идеи. При использовании аннотаций данных для настройки отношений вы просто устанавливаете атрибуты для навигационных свойств в коде модели. Это сильно отличается от подхода с Fluent API, где вы должны в буквальном смысле настроить отношения между таблицами. Для этого используется следующий общий шаблон (он не зависит от того, хотите ли вы использовать одностороннюю или двустороннюю связь):

Параметр Multiplicity в этом шаблоне указывает на окончание используемых методов Has… и With…, он может иметь следующие значения: Optional (навигационное свойство может иметь один или ноль экземпляров), Required (навигационное свойство может иметь только один экземпляр) и Many (навигационное свойство содержит коллекцию экземпляров).

Соответственно Entity Framework определяет следующий набор методов, определяющих настройки первичных навигационных свойств:

Для настройки отношений во второй таблице, используются следующие методы:

При вызове этих методов в качестве параметра, им передается делегат, в котором указывается навигационное свойство. При использовании односторонней связи (когда в одной из таблиц отсутствует навигационное свойство), можно вызвать соответствующий метод без параметров. Ниже показан пример настройки навигационных свойств для наших таблиц, который соответствует автоматическим соглашениям Code-First. Т.е. фактически он создает связь один-ко-многим между нашими таблицами, где таблица Customer является главной:

Ранее мы показали, как с помощью аннотаций данных можно ограничить поддержку NULL значений для автоматически сгенерированного внешнего ключа. Давайте реализуем это с помощью Fluent API:

В этом примере мы просто поменяли вызов метода WithOptional() на WithRequired(). Если вы запустите пример и посмотрите на структуру таблицы Orders, то увидите, что ее структура аналогична той, которая показана на первом рисунке в статье, когда мы использовали атрибуты метаданных. На рисунке ниже наглядно показано, как выполняется этот запрос:

Выполнение запроса Fluent API для определения отношений между таблицами

Указание внешних ключей

Ранее мы рассмотрели, как реализовать отношения между таблицами без прямого использования внешних ключей. Например, класс Order содержит свойство-ссылку на класс Customer, но при этом в этом классе не определено свойство, которое будет использоваться в качестве внешнего ключа для связи между таблицами. В этом случае мы видели, что Code-First автоматически сгенерирует внешний ключ за вас. Теперь давайте рассмотрим, что происходит, если мы явно задаем внешний ключ.

Первое что мы сделаем, это добавим новое свойство CustomerId в класс модели Order:

Запустите приложение. Code-First поймет, что вы внесли изменения в модель и воссоздаст базу данных. Если вы рассмотрите структуру столбцов таблицы Order, то заметите, что Code-First автоматически распознал поле CustomerId как внешний ключ и заменил автоматически генерируемый ключ Customer_CustomerId на CustomerId:

Явное указание внешнего ключа для таблицы

Как вы понимаете, Code-First использует определенные соглашения при поиске внешнего ключа в свойствах модели. Эти правила основаны на имени свойства и придерживаются следующих шаблонов:

В нашем случае имя свойства CustomerId подходит под первое правило, т.к. в главной таблице Customer используется одноименное свойство, которое является первичным ключом таблицы. Также стоит отметить, что Code-First не чувствителен к регистру символов при поиске внешнего ключа, т.е. если бы в таблице Order у нас было бы свойство CusToMeRId, то Code-First автоматически бы распознал его как внешний ключ.

К данному моменту может возникнуть вопрос, зачем может понадобиться явное определение внешних ключей в классе модели, если Code-First способен автоматически создавать эти ключи? Ответом на этот вопрос будет то, что иногда гораздо удобней получить доступ к родительскому объекту в коде через внешний ключ, нежели чем через ссылку. Например, в коде вы могли бы создать новый объект Order и указать через ссылку объект Customer, к которому он должен принадлежать:

Очевидно, для того, чтобы загрузить объект Customer в экземпляр myCustomer, вам необходимо будет сначала обратиться к базе данных. Использование внешнего ключа позволяет просто указать идентификатор заказчика не ссылаясь на него. Чтобы получить идентификатор заказчика, зачастую нужно также обратиться к базе данных, но бывают случаи, когда у вас есть доступ к значению ключа этого объекта. Например, если мы знаем что заказ myOrder принадлежит заказчику с идентификатором 5, мы могли бы использовать внешний ключ вместо ссылки на объект:

Кроме того, при использовании ссылки иногда возникает более серьезная ошибка. Entity Framework отслеживает состояние объектов сущностных классов и при их изменении помечает объект, изменяя свойство DbEntityEntry.State. Если вы создадите новый объект myOrder, укажите в нем ссылку на уже существующий в памяти объект myCustomer и попытаетесь сохранить объект myCustomer в базе данных, то EF пометит состояние этого объекта как Added, а не Modified, т.к. в коде изменился список заказов, связанных с этим покупателем и EF предполагает, что был создан новый покупатель. В результате в таблицу будет добавлен новый заказчик, хотя предполагалось просто добавить заказ для уже существующего заказчика. Эту проблему можно избежать либо сохранив в базе данных только объект myOrder, либо используя внешний ключ.

Есть еще один момент, который нужно упомянуть при обсуждении соглашений Code-First по внешним ключам. Когда ранее мы не использовали внешних ключей, Code-First автоматически генерировал ключ, который поддерживал значения NULL. Если вы взгляните на рисунок выше, то увидите, что внешний ключ CustomerId в таблице Order имеет не обнуляемый тип NOT NULL, поэтому вы не сможете сохранить новый заказ не указав идентификатор покупателя (используя внешний ключ или ссылку). Тем не менее, можно явно указать, что внешний ключ должен поддерживать значения NULL. Для этого измените тип свойства CustomerId в классе модели Order на обнуляемый тип данных:

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

Настройка внешних ключей в обход соглашениям Code-First

Иногда может возникнуть вопрос, что происходит, если имя вашего внешнего ключа не соответствует соглашениям Code-First? Например, вы могли бы использовать в таблице Order внешний ключ с именем UserId, как показано ниже:

Если вы запустите приложение и воссоздадите базу данных, то увидите, что Code-First проигнорировал поле UserId и создал автоматически генерируемый внешний ключ Customer_CustomerId, а поле UserId было добавлено как обычный столбец. Вы можете решить эту проблему используя атрибут ForeignKey в классе модели данных, как показано в примере:

В конструкторе этого атрибута указывается имя навигационного свойства, если оно имеется в классе модели. Альтернативным способом является применение атрибута ForeignKey к навигационному свойству:

В данном случае в конструкторе указывается имя свойства, являющегося внешним ключом. В Fluent API используется специальный метод HasForeignKey(), как показано в примере ниже:

Указание произвольного внешнего ключа

Работа с обратными навигационными свойствами

Пока мы использовали по одному навигационному свойству между двумя классами модели, Code-First понимал, как настроить отношения между ними. Существует такие случаи, когда между двумя таблицами базы данных нужно определить несколько отношений. Например, таблица Customers могла бы ссылаться на все заказы, на обработанные заказы (которые оплатил покупатель) и необработанные заказы. Логичнее всего решить данную проблему, это просто добавить новый столбец, например IsProcess, в таблицу Orders, который имел бы логическое значение и указывал бы на то, обработан заказ или нет. Но также эту проблему можно решить использовав три внешних ключа, связывающих эти таблицы.

В модели классов это решение будет выглядеть следующим образом:

В данном примере Code-First не сможет автоматически распознать связь между навигационными свойствами этих классов. Если вы выполните этот пример, то увидите, что в созданной таблице Orders было добавлено пять внешних ключей – по одному для каждого несвязанного навигационного свойства, и один ключ для связанных свойств Orders и Customer (если вы удалите настройку Fluent API, показанную ранее, в которой мы привязали эти свойства и указали внешний ключ, то Code-First сгенерирует 6 внешних ключей).

Отсутствие связи между навигационными свойствами

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

Теперь будет создано три внешних ключа, как и требовалось:

Настройка нескольких отношений между таблицами

Использование однонаправленной связи между таблицами

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

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

Ранее мы уже сказали, что по соглашению Code-First сгенерирует автоматически внешний ключ, если он не объявлен явно в классе модели. Это же соглашение работает, если мы используем однонаправленную связь между таблицами. Класс Customer по прежнему имеет навигационное свойство, определяющие его отношение с Order, поэтому будет генерироваться внешний ключ с именем Customer_CustomerId в таблице Orders.

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

Теперь давайте рассмотрим случай, когда мы явно указываем внешний ключ в зависимой таблице и при этом желаем изменить его имя, которое не будет соответствовать соглашениям Code-First по именованию внешних ключей, например:

Как мы описывали раньше, чтобы явно указать классу Order, что UserId является внешним ключом, можно использовать атрибут ForeignKey и передать ему имя навигационного свойства в параметре. Что делать, если мы используем одностороннюю связь и в классе Order не используем навигационное свойство?

Для решения этой проблемы мы можем использовать этот атрибут в главной таблице к навигационному свойству Orders или использовать Fluent API, как показано в примере ниже:

Обратите внимание, что при использовании Fluent API в вызове метода WithRequired() мы не передаем параметр делегата с выбором навигационного свойства из модели, т.к. используем одностороннюю связь и у нас отсутствует навигационное свойство в классе Order.


Взаимосвязи EF Core - концепции и свойства навигации

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

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

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

Кроме того, давайте объясним отношения Required и Optional в EF Core. Обязательная связь - это связь, в которой внешний ключ не может быть нулевым. Это означает, что должен существовать главный объект. Необязательное отношение - это отношение, в котором внешний ключ может иметь значение NULL и, следовательно, основной объект может отсутствовать.

Настройка One-to-One связи

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

Самый простой способ настроить этот тип отношений - использовать подход по соглашению, и это именно то, что мы собираемся сделать. Итак, давайте сначала создадим еще один класс в проекте Entities с именем StudentDetails :

Теперь, чтобы установить связь между классами Student и StudentDetails , нам нужно добавить свойство навигации по ссылкам с обеих сторон. Итак, давайте сначала изменим класс Student :

И давайте изменим класс StudentDetails :

Мы можем видеть, что класс Student имеет свойство навигации по ссылке к классу StudentDetails , а класс StudentDetails имеет внешний ключ и свойство навигации Student .

В результате мы можем создать новую миграцию и применить ее:

Настройка One-to-One связи EF Core

Отлично, отлично работает.

Дополнительные пояснения

Как мы объяснили в первой статье, EF Core ищет все общедоступные свойства DbSet<T> в классе DbContext для создания таблиц в базе данных. Затем он ищет все общедоступные свойства в классе T для сопоставления столбцов. Но он также выполняет поиск всех общедоступных свойств навигации в классе T и создает дополнительные таблицы и столбцы, связанные с типом свойства навигации. Итак, в нашем примере в классе Student EF Core находит свойство навигации StudentDetails и создает дополнительную таблицу со своими столбцами.

Конфигурация отношений One-to-Many

В этом разделе мы узнаем, как создавать отношения "один ко многим" всеми тремя способами. Итак, прежде чем мы начнем, давайте создадим дополнительный класс модели Evaluation в проекте Entities :

Использование условного подхода для создания отношений «один ко многим»

Давайте посмотрим на различные соглашения, которые автоматически настраивают связь "один ко многим" между классами Student и Evaluation .

Первый подход включает свойство навигации в основной сущности, классе Student :

В классе ApplicationContext есть свойство DbSet , и, как мы объяснили, EF Core выполняет поиск по классу Student , чтобы найти все свойства навигации для создания соответствующих таблиц в базе данных.

Еще один способ создать связь "один ко многим" - это добавить свойство Student в класс Evaluation без свойства ICollection в классе Student класс :

Чтобы этот подход работал, мы должны добавить свойство DbSet<Evaluation> Evaluations в класс ApplicationContext .

Третий подход по Конвенции заключается в использовании комбинации предыдущих. Итак, мы можем добавить свойство навигации ICollection<Evaluation> Evaluations в класс Student и добавить свойство навигации Student Student в класс Evaluation . Конечно, при таком подходе нам не нужно свойство DbSet<Evaluation> Evaluations в классе ApplicationContext .

Это результат любого из этих трех подходов:

Настройка связей сущностей в EF Core

Мы видим, что связь была создана правильно, но наш внешний ключ является полем, допускающим значение NULL. Это связано с тем, что оба свойства навигации имеют значение по умолчанию null. Это отношение также называется необязательным отношением (мы говорили об этом в первой части этой статьи).

Если мы хотим создать требуемую связь между сущностями Student и Evaluation , мы должны включить внешний ключ в класс Evaluation . :

Теперь, когда мы выполним нашу миграцию, мы увидим следующий результат:

Миграции EF Core

Очевидно, что наши отношения сейчас необходимы.

Подход с аннотациями данных

Подход с использованием аннотаций к данным содержит только два атрибута, связанных с отношениями. Атрибуты [ForeignKey] и [InverseProperty] .

Атрибут [ForeignKey] позволяет нам определять внешний ключ для свойства навигации в классе модели. Итак, давайте изменим класс Evaluation , добавив этот атрибут:

Мы применили атрибут [ForeignKey] поверх свойства StudentId (которое является внешним ключом в этом классе), присвоив ему имя свойства навигации Student . Но работает и наоборот:

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

[ForeignKey («Свойство1», «Свойство2»)] .

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

Установление связей меду таблицами в EF Core

Подход Fluent API для конфигурации One-to-Many

Чтобы создать отношение «один ко многим» с этим подходом, нам нужно удалить атрибут [ForeignKey] из класса Evaluation и изменить StudentConfiguration , добавив этот код:

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

Результат будет таким же:

One-to-Many связь в EF Core

Здесь нужно упомянуть одну вещь.

Для модели базы данных, такой как мы определили, нам не нужен метод HasForeignKey . Это потому, что свойство внешнего ключа в классе Evaluation имеет тот же тип и то же имя, что и первичный ключ в классе Student. Это означает, что по Конвенции это отношение все равно будет обязательным. Но если бы у нас был внешний ключ с другим именем, например StudId, тогда понадобился бы метод HasForeignKey , потому что в противном случае ядро EF создало бы необязательную связь между классами Evaluation и Student.

Конфигурация отношений Many-to-Many (многие-ко-многим)

Это реализация версии 3.1 EF Core. Это справедливо для EF Core версии 5, но в версии 5 это можно было бы сделать немного иначе. Мы объясним это в следующем разделе.

Прежде чем мы начнем объяснять, как настроить эту связь, давайте создадим необходимые классы в проекте Entities :

Теперь мы можем изменить классы Student и Subject , предоставив свойство навигации для каждого класса по направлению к классу StudentSubject :

В Entity Framework Core мы должны создать объединяющуюся сущность для объединяемой таблицы (StudentSubject). Этот класс содержит внешние ключи и свойства навигации из классов Student и Subject . Кроме того, классы Student и Subject имеют свойства навигации ICollection по отношению к классу StudentSubject . Таким образом, отношения «многие ко многим» - это всего лишь два отношения «один ко многим».

Мы создали наши сущности, и теперь нам нужно создать необходимую конфигурацию. Для этого давайте создадим класс StudentSubjectConfiguration в папке Entities/Configuration:

Как мы уже говорили, многие-ко-многим - это всего лишь две взаимосвязи EF Core «один ко многим», и это именно то, что мы настраиваем в нашем коде. Мы создаем первичный ключ для таблицы StudentSubject , который в данном случае является составным ключом. После настройки первичного ключа мы используем знакомый код для создания конфигураций отношений.

Теперь нам нужно изменить метод OnModelBuilder в классе ApplicationContext :

После этих изменений мы можем создать миграцию и применить ее:

PM> Add-Migration ManyToManyRelationship

Связь Many-to-Many в EF Core

Отличная работа. Давай продолжаем.

По сути, класс Student должен иметь public ICollection Subjects , а класс Subject должен иметь public ICollection Students свойство. Нет необходимости ни в третьем классе, ни в свойствах навигации для этого класса.

Но если вы хотите изначально заполнить данные для таблиц Student и Subject и заполнить третью таблицу идентификаторами обоих таблиц, вам придется использовать реализацию, которую мы использовали для версии 3.1.

Метод OnDelete

Метод OnDelete настраивает действия удаления между реляционными сущностями. Мы можем добавить этот метод в конец конфигурации отношений, чтобы решить, как будут выполняться действия удаления.

В методе OnDelete можно использовать следующие значения:

  • Restrict - действие удаления не применяется к зависимым объектам. Это означает, что мы не можем удалить основную сущность, если у нее есть связанная зависимая сущность.
  • SetNull - зависимая сущность не удаляется, но для ее свойства внешнего ключа установлено значение null.
  • ClientSetNull - если EF Core отслеживает зависимую сущность, ее внешний ключ имеет значение null, и эта сущность не удаляется. Если он не отслеживает зависимую сущность, то применяются правила базы данных.
  • Cascade - зависимая сущность удаляется вместе с основной сущностью.

Мы также можем видеть это из кода в нашем файле миграции:

onDelete в миграциях EF Core

Мы можем изменить этот тип поведения, изменив код конфигурации в классе StudentConfiguration :

Давайте создадим еще один перенос:

PM> Добавление миграции StudentEvaluationRestrictDelete

И взгляните на сгенерированный код миграции:

Миграции FluentApi с onDelete в EF Core

Заключение

Настройка взаимосвязей EF Core в нашей модели базы данных - очень важная часть процесса моделирования.

Мы увидели, что EF Core предлагает нам несколько способов добиться этого и максимально упростить процесс.

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

Читайте также: