Code First: независимые ассоциации против ассоциаций иностранных ключей?

103

Я мысленно спорим с собой каждый раз, когда начинаю работать над новым проектом и разрабатываю свои POCO. Я видел много руководств / примеров кода, которые, кажется, отдают предпочтение ассоциациям внешних ключей :

Ассоциация внешнего ключа

public class Order
{
    public int ID { get; set; }
    public int CustomerID { get; set; } // <-- Customer ID
    ...
}

В отличие от независимых ассоциаций :

Независимая ассоциация

public class Order
{
    public int ID { get; set; }
    public Customer Customer { get; set; } // <-- Customer object
    ...
}

Я работал с NHibernate в прошлом и использовал независимые ассоциации, которые не только ощущаются более объектно ориентированными, но также (с отложенной загрузкой) имеют то преимущество, что дают мне доступ ко всему объекту Customer, а не только к его идентификатору. Это позволяет мне, например, получить экземпляр Order, а затем обойтись Order.Customer.FirstNameбез явного соединения, что чрезвычайно удобно.

Итак, мои вопросы таковы:

  1. Есть ли существенные недостатки в использовании независимых ассоциаций? и...
  2. Если их нет, зачем вообще использовать ассоциации внешнего ключа?
Даниэль Люцци
источник

Ответы:

106

Если вы хотите в полной мере использовать ORM, вы обязательно воспользуетесь ссылкой на сущность:

public class Order
{
    public int ID { get; set; }
    public Customer Customer { get; set; } // <-- Customer object
    ...
}

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

Entity framework v4 предлагает новый тип ассоциации, называемый ассоциацией внешнего ключа. Наиболее очевидное различие между независимой ассоциацией и ассоциацией внешнего ключа заключается в классе Order:

public class Order
{
    public int ID { get; set; }
    public int CustomerId { get; set; }  // <-- Customer ID
    public Customer Customer { get; set; } // <-- Customer object
    ...
}

Как видите, у вас есть как свойство FK, так и ссылка на сущность. Между двумя типами ассоциаций различий больше:

Независимая ассоциация

  • Он представлен как отдельный объект в ObjectStateManager. Он свой EntityState!
  • При построении ассоциации вам всегда нужны права с обоих концов ассоциации.
  • Эта ассоциация отображается так же, как и сущность.

Ассоциация внешнего ключа

  • Он не представлен как отдельный объект в ObjectStateManager . В связи с этим вы должны соблюдать некоторые особые правила.
  • При построении ассоциации вам не нужны оба конца ассоциации. Достаточно иметь дочернюю сущность и PK родительской сущности, но значение PK должно быть уникальным. Таким образом, при использовании ассоциации внешних ключей вы также должны назначить временные уникальные идентификаторы вновь созданным объектам, используемым в отношениях.
  • Эта ассоциация не отображается, а вместо этого определяет ссылочные ограничения.

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

Редактировать:

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

Ладислав Мрнка
источник
Спасибо за очень проницательный ответ, а также за внимание к правильной терминологии, которое помогло мне найти множество ресурсов по этой теме и за / против обоих методов.
Даниэль Лиуцци,
1
Я только что наткнулся на вашу статью, Ладислав. Очень интересное чтение и отличный ресурс, чтобы лучше понять разницу между этими двумя подходами. Ура.
Daniel Liuzzi
1
@GaussZ: Насколько я знаю, не было никаких изменений в способе обработки ассоциаций с EF4 (где были введены ассоциации FK).
Ладислав Мрнка 03
1
Этот и другие ответы, похоже, не касаются проблемы производительности. Однако, согласно разделу 2.2 «Факторы, влияющие на производительность создания представлений» из статьи MSDN, использование независимой ассоциации, по-видимому, увеличивает стоимость создания представлений по сравнению с ассоциациями внешнего ключа.
Veverke 07
1
@LadislavMrnka: не могли бы вы дважды проверить, что ссылка на упомянутую вами статью работает? Я не могу получить к нему доступ.
Veverke 07
34

Используйте оба. И сделайте ссылки на объекты виртуальными, чтобы обеспечить ленивую загрузку. Как это:

public class Order
{
  public int ID { get; set; }
  public int CustomerID { get; set; }
  public virtual Customer Customer { get; set; } // <-- Customer object
  ...
}

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

Сет Полсон
источник
5
Согласовано. Именно этим я и закончил, как предложил Ладислав. Это действительно дает вам лучшее из обоих миров; весь объект, когда вам нужны все его свойства, и его идентификатор, когда вам нужен только PK, а все остальное не нужно.
Даниэль Лиуцци,
9

Независимая ассоциация не работает с тем, AddOrUpdateчто обычно используется в Seedметоде. Когда ссылка является существующим элементом, она будет повторно вставлена.

// Existing customer.
var customer = new Customer { Id = 1, Name = "edit name" };
db.Set<Customer>().AddOrUpdate(customer);

// New order.
var order = new Order { Id = 1, Customer = customer };
db.Set<Order>().AddOrUpdate(order);

В результате существующий клиент будет повторно вставлен, а новый (повторно вставленный) клиент будет связан с новым заказом.


Если мы не используем ассоциацию внешнего ключа и не присвоим идентификатор.

 // Existing customer.
var customer = new Customer { Id = 1, Name = "edit name" };
db.Set<Customer>().AddOrUpdate(customer);

// New order.
var order = new Order { Id = 1, CustomerId = customer.Id };
db.Set<Order>().AddOrUpdate(order);

У нас ожидаемое поведение, существующий клиент будет связан с новым заказом.

Юлиам Чандра
источник
2
Это хорошая находка. Однако обходной путь (и я думаю, что правильный способ) прикрепить клиента к заказу - загрузить его из контекста базы данных следующим образом: var order = new Order { Id = 1, Customer = db.Customers.Find(1) }; Или вы можете использовать метод Select для загрузки клиента из контекста базы данных. Это работает с независимой ассоциацией.
tala9999
4

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

CarneyCode
источник