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

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

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

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 в прошлом и использовал независимые ассоциации, которые не только чувствуют себя более OO, но и (с ленивой загрузкой) имеют то преимущество, что дают мне доступ ко всему объекту Customer, а не только его идентификатору. Это позволяет мне, например, получить экземпляр заказа, а затем сделать Order.Customer.FirstName без необходимости делать явное соединение, что очень удобно.

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

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

Ответ 1

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

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!
  • При создании ассоциации вам всегда нужны entitites с обоих концов ассоциации
  • Эта ассоциация отображается так же, как и объект.

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

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

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

Edit:

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

Ответ 2

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

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

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

Ответ 3

Независимая ассоциация не работает с 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);

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

Ответ 4

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