Подтвердить что ты не робот

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) при создании POCOs инициализируют свойства навигации для класса, например:

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: Какой из них лучше? Зачем? Плюсы и минусы?

Edit:

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`. Это означает, что мы должны иметь одностороннюю ссылку. (?) Как мы должны решить, какое из свойств навигации следует удалить?

4b9b3361

Ответ 1

Коллекции: Это не имеет значения.

Существует четкое различие между коллекциями и ссылками в качестве свойств навигации. Ссылка - это объект. Коллекции содержат объекты. Это означает, что инициализация коллекции бессмысленна с точки зрения бизнес-логики: она не определяет связь между сущностями. Установка ссылки выполняется.

Так что это исключительно вопрос предпочтения, независимо от того, вы или нет, или как вы инициализируете встроенные списки.

Что касается "как", некоторые люди предпочитают ленивую инициализацию:

private ICollection<Address> _addresses;

public virtual ICollection<Address> Addresses
{ 
    get { return this._addresses ?? (this._addresses = new HashSet<Address>());
}

Он предотвращает ненужные ссылочные исключения, поэтому он облегчает модульное тестирование и управление сборкой, но также предотвращает ненужную инициализацию. Последнее может иметь значение, когда класс имеет относительно много коллекций. Недостатком является то, что требуется относительно много сантехники, особенно. по сравнению с авто свойствами без инициализации. Кроме того, появление оператора null-распространения в С# сделало менее актуальным инициализацию свойств коллекции.

... если не применяется явная загрузка

Единственное, что инициализация коллекций затрудняет проверку, была ли сборка загружена 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-ядро инициализирует ссылки навигационных свойств в конструкторах могут вызвать проблемы. Не делай этого. В любом случае, это не имеет смысла,

Ответ 2

Во всех моих проектах Я следую правилу - "Коллекции не должны быть пустыми. Они либо пусты, либо имеют значения".

Первый пример возможен, когда создание этих объектов является ответственностью за код третьей части (например, ORM), и вы работаете над краткосрочным проектом.

Второй пример лучше, так как

  • вы уверены, что объект имеет все свойства, установленные
  • вы избегаете глупости NullReferenceException
  • вы делаете потребителей своего кода счастливее

Люди, которые практикуют дизайн, управляемый доменами, выставляют коллекции только для чтения и избегают установки на них. (см. Что лучше всего подходит для списков readonly в NHibernate)

Q1: Какой из них лучше? Зачем? Плюсы и минусы?

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

Q2: во втором подходе произойдет переполнение стека, если класс License имеет ссылку на класс User. Это означает, что мы должны иметь одностороннюю ссылку. (?) Как мы должны решить, какое из свойств навигации следует удалить?

Когда я самостоятельно разработал образец карты данных, я старался избегать двунаправленных ссылок и очень редко ссылался на дочерние на родительские.

Когда я использую ORM, легко иметь двунаправленные ссылки.

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

  • Я строю parent entity с emty 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; }
}

Ответ 3

Он избыточен для списка new, так как ваш POCO зависит от Lazy Loading.

Ленивая загрузка - это процесс, при котором сущность или совокупность объектов автоматически загружается из базы данных при первом доступе к объекту, относящемуся к объекту/объектам. При использовании типов сущностей POCO, ленивая загрузка достигается путем создания экземпляров производных прокси-типов, а затем переопределения виртуальных свойств для добавления загрузочного крючка.

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

Обратите внимание, что Lazy Loading - это функция, поддерживаемая инфраструктурой сущности, если вы создаете класс вне контекста DbContext, тогда зависящий код, очевидно, страдает от NullReferenceException

НТН

Ответ 4

Q1: Какой из них лучше? Зачем? Плюсы и минусы?

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

Что касается первого варианта без инициализации свойств навигации, в зависимости от того, кто/что создает объект, есть 2 ситуации:

  • Структура Entity создает объект
  • Пользователь кода создает объект

Первый вариант совершенно корректен, когда Entity Framework создает объект, но может выйти из строя, когда пользователь кода создает объект.

Решение для обеспечения того, чтобы потребитель кода всегда создавал действительный объект, заключается в использовании метода static factory:

  • Сделать защитный конструктор по умолчанию. Entity Framework отлично работает с защищенными конструкторами.

  • Добавить статический метод factory, который создает пустой объект, например. a User, устанавливает все свойства, например. Addresses и License после создания и возвращает полностью построенный объект User

Таким образом, Entity Framework использует защищенный конструктор по умолчанию для создания действительного объекта из данных, полученных из некоторого источника данных, а потребитель кода использует статический метод factory для создания допустимого объекта.

Ответ 5

Я использую ответ из этого Почему мой код Entity Framework Code First proxy null и почему я не могу его установить?

Были проблемы с инициализацией конструктора. Единственная причина, по которой я это делаю, - сделать тестовый код проще. Убедившись, что коллекция никогда не имеет значения null, я постоянно меняю инициализацию в тестах и ​​т.д.

Ответ 6

Другие ответы полностью отвечают на вопрос, но я хотел бы добавить что-то, поскольку этот вопрос по-прежнему имеет значение и появляется в поиске 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.

И лично, я думаю, что было бы неплохо настроить это, чтобы воспользоваться инициализаторами автоисточников С# 6.0:

    public virtual ICollection<OtherEntity> OtherEntities { get; set; } = new HashSet<OtherEntity>();