EF codefirst: я должен инициализировать свойства навигации?
Я видел, как некоторые книги (например, программирование кода сущности объекта сначала Джулия Лерман) определяют свои доменные классы (POCO) без инициализации свойств навигации, таких как:
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public virtual ICollection<Address> Address { get; set; }
public virtual License License { get; set; }
}
некоторые другие книги или инструменты (например, Entity Framework Power Tools) при генерации POCO инициализируют свойства навигации класса, например:
public class User
{
public User()
{
this.Addresses = new IList<Address>();
this.License = new License();
}
public int Id { get; set; }
public string UserName { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
public virtual License License { get; set; }
}
Q1: какой из них лучше? Зачем? Плюсы и минусы?
Редактировать:
public class License
{
public License()
{
this.User = new User();
}
public int Id { get; set; }
public string Key { get; set; }
public DateTime Expirtion { get; set; }
public virtual User User { get; set; }
}
Q2: Во втором подходе будет переполнение стека, если класс `License` также имеет ссылку на класс`User`. Это означает, что у нас должна быть односторонняя ссылка.(?) Как мы должны решить, какое из свойств навигации должно быть удалено?
6 ответов
Коллекции: это не имеет значения.
Существует четкое различие между коллекциями и ссылками в качестве свойств навигации. Ссылка - это сущность. Коллекция содержит сущности. Это означает, что инициализация коллекции не имеет смысла с точки зрения бизнес-логики: она не определяет связь между сущностями. Установка ссылки делает.
Так что вопрос о том, инициализируете ли вы встроенные списки, зависит от того, предпочитаете ли вы или нет, или как.
Что касается "как", некоторые люди предпочитают ленивую инициализацию:
private ICollection<Address> _addresses;
public virtual ICollection<Address> Addresses
{
get { return this._addresses ?? (this._addresses = new HashSet<Address>());
}
Он предотвращает исключения нулевых ссылок, поэтому он облегчает модульное тестирование и манипулирование коллекцией, но также предотвращает ненужную инициализацию. Последнее может иметь значение, когда класс имеет относительно много коллекций. Недостатком является то, что требуется относительно много сантехники, особенно по сравнению с автоматическими свойствами без инициализации. Кроме того, появление в C# оператора нулевого распространения сделало его менее актуальным для инициализации свойств коллекции.
... если не применяется явная загрузка
Единственное, что при инициализации коллекций сложно проверить, была ли коллекция загружена Entity Framework. Если коллекция инициализирована, оператор как...
var users = context.Users.ToList();
...создаст User
объекты, имеющие пустой, не нулевой Addresses
коллекции (отложенная загрузка). Проверка, загружена ли коллекция, требует кода типа...
var user = users.First();
var isLoaded = context.Entry(user).Collection(c => c.Addresses).IsLoaded;
Если коллекция не инициализирована простой null
проверить сделаю. Поэтому, когда выборочная явная загрузка является важной частью вашей практики кодирования, то есть...
if (/*check collection isn't loaded*/)
context.Entry(user).Collection(c => c.Addresses).Load();
... может быть удобнее не инициализировать свойства коллекции.
Свойства ссылки: Не
Ссылочные свойства являются сущностями, поэтому присвоение им пустого объекта имеет смысл.
Хуже того, если вы инициируете их в конструкторе, EF не будет перезаписывать их при материализации вашего объекта или ленивой загрузке. Они всегда будут иметь свои начальные значения, пока вы их не замените. Хуже того, вы можете даже сохранить пустые объекты в базе данных!
И есть еще один эффект: исправление отношений не произойдет. Исправление отношений - это процесс, с помощью которого EF связывает все объекты в контексте с помощью своих свойств навигации. Когда User
и Licence
загружаются отдельно, еще User.License
будет заселено и наоборот. Если, конечно, если License
был инициализирован в конструкторе. Это также верно для ассоциаций 1:n. Если Address
будет инициализировать User
в своем конструкторе, User.Addresses
не будет заселено!
Ядро Entity Framework
На исправление отношений в ядре Entity Framework (2.1 на момент написания) не влияют инициализированные ссылочные навигационные свойства в конструкторах. То есть, когда пользователи и адреса извлекаются из базы данных отдельно, заполняются свойства навигации.
Однако отложенная загрузка не перезаписывает инициализированные ссылочные навигационные свойства. Итак, в заключение, также в EF-ядре инициализация эталонных навигационных свойств в конструкторах может вызвать проблемы. Не делай этого. В любом случае это не имеет смысла,
Во всех моих проектах я следую правилу - "Коллекции не должны быть нулевыми. Они либо пусты, либо имеют значения".
Первый пример возможен, когда создание этих объектов является ответственностью кода третьей части (например, ORM), и вы работаете над краткосрочным проектом.
Второй пример лучше, так как
- Вы уверены, что у объекта есть все свойства, установленные
- ты избегаешь глупости
NullReferenceException
- Вы делаете потребителей своего кода счастливее
Люди, которые практикуют доменно-управляемый дизайн, представляют коллекции только для чтения и избегают их установки. (см. Какова лучшая практика для списков только для чтения в NHibernate)
Q1: какой из них лучше? Зачем? Плюсы и минусы?
Лучше выставлять ненулевые наборы, так как вы избегаете дополнительных проверок в вашем коде (например, Addresses
). Это хороший контракт в вашей кодовой базе. Но для меня нормально показывать пустую ссылку на одну сущность (например, License
)
Q2: во втором подходе будет переполнение стека, если License
класс имеет ссылку на User
класс тоже. Это означает, что у нас должна быть односторонняя ссылка.(?) Как мы должны решить, какое из свойств навигации должно быть удалено?
Когда я самостоятельно разработал шаблон отображения данных, я старался избегать двунаправленных ссылок и очень редко имел ссылки от ребенка к родителю.
Когда я использую ORM, легко иметь двунаправленные ссылки.
Когда необходимо создать тестовую сущность для моих модульных тестов с двунаправленным набором ссылок, я выполняю следующие шаги:
- я строю
parent entity
с пустымиchildren collection
, - Тогда я добавляю evey
child
в отношенииparent entity
вchildren collection
,
Защищено наличием конструктора без параметров в License
типа я бы сделал user
собственность требуется.
public class License
{
public License(User user)
{
this.User = user;
}
public int Id { get; set; }
public string Key { get; set; }
public DateTime Expirtion { get; set; }
public virtual User User { get; set; }
}
Другие ответы полностью отвечают на вопрос, но я хотел бы добавить кое-что, так как этот вопрос все еще актуален и появляется в поиске Google.
При использовании мастера "сначала модель кода из базы данных" в Visual Studio все коллекции инициализируются следующим образом:
public partial class SomeEntity
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public SomeEntity()
{
OtherEntities = new HashSet<OtherEntity>();
}
public int Id { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<OtherEntity> OtherEntities { get; set; }
}
Я склонен воспринимать вывод мастера как официальную рекомендацию Microsoft, поэтому я добавляю этот пятилетний вопрос. Поэтому я бы инициализировал все коллекции как HashSet
s.
И лично я думаю, что было бы неплохо настроить вышесказанное, чтобы воспользоваться инициализаторами авто-свойств C# 6.0:
public virtual ICollection<OtherEntity> OtherEntities { get; set; } = new HashSet<OtherEntity>();
Это избыточно new
список, так как ваш POCO зависит от отложенной загрузки.
Ленивая загрузка - это процесс, при котором сущность или коллекция сущностей автоматически загружаются из базы данных при первом обращении к свойству, ссылающемуся на сущность / сущности. При использовании типов сущностей POCO ленивая загрузка достигается созданием экземпляров производных типов прокси и затем переопределением виртуальных свойств для добавления ловушки загрузки.
Если вы удалите виртуальный модификатор, то отключите ленивую загрузку, и в этом случае ваш код больше не будет работать (потому что ничто не будет инициализировать список).
Обратите внимание, что отложенная загрузка - это функция, поддерживаемая структурой сущностей. Если вы создадите класс вне контекста DbContext, то зависимый код, очевидно, будет страдать от NullReferenceException
НТН
Q1: какой из них лучше? Зачем? Плюсы и минусы?
Второй вариант, когда виртуальные свойства устанавливаются внутри конструктора сущностей, имеет определенную проблему, которая называется " вызов виртуального члена в конструкторе".
Что касается первого варианта без инициализации свойств навигации, есть две ситуации в зависимости от того, кто / что создает объект:
- Entity Framework создает объект
- Потребитель кода создает объект
Первый вариант совершенно действителен, когда Entity Framework создает объект, но может потерпеть неудачу, когда потребитель кода создает объект.
Чтобы гарантировать, что потребитель кода всегда создает допустимый объект, нужно использовать статический метод фабрики:
Сделать конструктор по умолчанию защищенным. Entity Framework прекрасно работает с защищенными конструкторами.
Добавьте статический метод фабрики, который создает пустой объект, например
User
объект, устанавливает все свойства, напримерAddresses
а такжеLicense
, после создания и возвращает полностью построенныйUser
объект
Таким образом, Entity Framework использует защищенный конструктор по умолчанию для создания допустимого объекта из данных, полученных из некоторого источника данных, а потребитель кода использует метод статической фабрики для создания допустимого объекта.
Я использую ответ из этого. Почему моя коллекция прокси Entity Framework Code First пуста и почему я не могу установить ее?
Были проблемы с инициализацией конструктора. Единственная причина, по которой я это делаю, - это сделать тестовый код проще. Убедиться, что коллекция никогда не будет нулевой, спасет меня от постоянной инициализации в тестах и т.д.