Entity Framework
Сопоставление отношений с Entity Framework Code Сначала: индивидуально и вариации
Поиск…
Вступление
В этом разделе обсуждается, как сопоставить отношения типа один к одному с помощью Entity Framework.
Отображение от одного до нуля или одного
Итак, скажем еще раз, что у вас есть следующая модель:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
}
public class Car
{
public int CarId { get; set; }
public string LicensePlate { get; set; }
}
public class MyDemoContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Car> Cars { get; set; }
}
И теперь вы хотите настроить его так, чтобы вы могли выразить следующую спецификацию: у одного человека может быть один или нулевой автомобиль, и каждый автомобиль принадлежит одному человеку точно (отношения двунаправленные, поэтому, если CarA принадлежит PersonA, то PersonA 'принадлежит «CarA».
Итак, немного изменим модель: добавьте свойства навигации и свойства внешнего ключа:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public int CarId { get; set; }
public virtual Car Car { get; set; }
}
public class Car
{
public int CarId { get; set; }
public string LicensePlate { get; set; }
public int PersonId { get; set; }
public virtual Person Person { get; set; }
}
И конфигурация:
public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
public CarEntityTypeConfiguration()
{
this.HasRequired(c => c.Person).WithOptional(p => p.Car);
}
}
К этому времени это должно быть само собой разумеющимся. У автомобиля есть необходимый человек ( HasRequired () ), с лицом, имеющим дополнительный автомобиль ( WithOptional () ). Опять же, не имеет значения, с какой стороны вы настраиваете это отношение, просто будьте осторожны, когда вы используете правильную комбинацию Has / With и Required / Optional. Со стороны « Person
это будет выглядеть так:
public class PersonEntityTypeConfiguration : EntityTypeConfiguration<Person>
{
public PersonEntityTypeConfiguration()
{
this.HasOptional(p => p.Car).WithOptional(c => c.Person);
}
}
Теперь давайте рассмотрим схему db:
Посмотрите внимательно: вы можете видеть, что FK в People
упоминает Car
. Кроме того, FK в Car
- это не PersonId
, а CarId
. Вот сценарий для FK:
ALTER TABLE [dbo].[Cars] WITH CHECK ADD CONSTRAINT [FK_dbo.Cars_dbo.People_CarId] FOREIGN KEY([CarId])
REFERENCES [dbo].[People] ([PersonId])
Таким образом, это означает, что свойства ключа CarId
и PersonId
foregn, которые мы имеем в модели, в основном игнорируются. Они находятся в базе данных, но они не являются внешними ключами, как и следовало ожидать. Это связано с тем, что сопоставления «один к одному» не поддерживают добавление FK в вашу EF-модель. И это потому, что сопоставление «один к одному» довольно проблематично в реляционной базе данных.
Идея состоит в том, что каждый человек может иметь ровно одну машину, и этот автомобиль может принадлежать только этому человеку. Или могут быть записи людей, у которых нет автомобилей, связанных с ними.
Итак, как это можно представить с помощью внешних ключей? Очевидно, что в Car
может быть PersonId
и CarId
в People
. Чтобы обеспечить, чтобы каждый человек мог иметь только один автомобиль, PersonId
должен был быть уникальным в Car
. Но если PersonId
уникален в People
, то как вы можете добавить две или более записи, где PersonId
- NULL
(более одного автомобиля, у которого нет владельцев)? Ответ: вы не можете (ну, на самом деле, вы можете создать отфильтрованный уникальный индекс в SQL Server 2008 и новее, но давайте забудем об этой техничности на мгновение, не говоря уже о других СУБД). Не говоря уже о том, где вы указываете оба конца отношений ...
Единственный реальный способ обеспечить соблюдение этого правила, если таблицы People
и Car
имеют «тот же» первичный ключ (те же значения в подключенных записях). И для этого CarId
in Car
должен быть как ПК, так и FK для ПК людей. И это делает всю схему беспорядок. Когда я использую это, я скорее PersonId
PK / FK в Car
PersonId
и настраиваю его следующим образом:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public virtual Car Car { get; set; }
}
public class Car
{
public string LicensePlate { get; set; }
public int PersonId { get; set; }
public virtual Person Person { get; set; }
}
public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
public CarEntityTypeConfiguration()
{
this.HasRequired(c => c.Person).WithOptional(p => p.Car);
this.HasKey(c => c.PersonId);
}
}
Не идеальный, но, может быть, немного лучше. Тем не менее, вы должны быть настороже при использовании этого решения, потому что это противоречит обычным соглашениям об именах, которые могут сбить вас с пути. Вот схема, сгенерированная из этой модели:
Таким образом, эта связь не применяется схемой базы данных, а сама Entity Framework. Вот почему вы должны быть очень осторожны, когда используете это, а не позволять кому-либо работать непосредственно с базой данных.
Отображение взаимно однозначного
Сопоставление «один к одному» (когда обе стороны требуются) также является сложной задачей.
Представим себе, как это можно представить с помощью внешних ключей. Опять же, CarId
в People
который относится к CarId
в Car
, и PersonId
в автомобиле, который относится к PersonId
в People
.
Теперь, что произойдет, если вы хотите вставить автомобильную пластинку? Чтобы это было успешным, в этой автомобильной записи должен быть указан PersonId
, потому что это необходимо. И для PersonId
чтобы этот PersonId
был действительным, соответствующая запись в People
должна существовать. Хорошо, давайте продолжим и вставьте запись человека. Но для этого, чтобы добиться успеха, действительный CarId
должен быть в записи человека, но этот автомобиль еще не вставлен! Этого не может быть, потому что мы должны сначала вставить запись упомянутого лица. Но мы не можем вставить запись упомянутого лица, потому что она относится к записи автомобиля, поэтому ее необходимо вставить сначала (внешний ключ-цессионал :)).
Таким образом, это не может быть представлено «логическим» способом. Опять же, вам нужно сбросить один из внешних ключей. Который вы бросаете, зависит от вас. Сторона, которая остается с внешним ключом, называется «зависимой», сторона, которая остается без внешнего ключа, называется «основной». И снова, чтобы гарантировать уникальность в зависимом, ПК должен быть FK, поэтому добавление столбца FK и импорт его в вашу модель не поддерживаются.
Итак, вот конфигурация:
public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
public CarEntityTypeConfiguration()
{
this.HasRequired(c => c.Person).WithRequiredDependent(p => p.Car);
this.HasKey(c => c.PersonId);
}
}
К настоящему времени вы действительно должны были получить логику этого :) Просто помните, что вы также можете выбрать другую сторону, просто будьте осторожны, чтобы использовать версии WithRequired Dependent / Principal (и вам все равно нужно настроить PK в автомобиле).
public class PersonEntityTypeConfiguration : EntityTypeConfiguration<Person>
{
public PersonEntityTypeConfiguration()
{
this.HasRequired(p => p.Car).WithRequiredPrincipal(c => c.Person);
}
}
Если вы проверите схему DB, вы обнаружите, что она точно такая же, как и в случае решения «один к одному» или «нуль». Это потому, что опять же, это не выполняется схемой, а самой EF. Так что, будьте осторожны :)
Отображение одного или нуля к одному или нуля
И чтобы закончить, давайте вкратце рассмотрим случай, когда обе стороны являются необязательными.
К настоящему моменту вам должно быть очень скучно с этими примерами :), поэтому я не буду вдаваться в подробности и играть с идеей наличия двух FK-ов и потенциальных проблем и предупреждать вас об опасностях не применять эти правила в схемы, но только в самом EF.
Вот конфигурация, которую вам нужно применить:
public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
public CarEntityTypeConfiguration()
{
this.HasOptional(c => c.Person).WithOptionalPrincipal(p => p.Car);
this.HasKey(c => c.PersonId);
}
}
Опять же, вы можете настроить и с другой стороны, просто будьте осторожны, чтобы использовать правильные методы :)