Используйте инициализатор для свойства, а не конструктор.
public class Parent
{
public virtual Child Child { get; set; } = new Child();
}
Редактировать: Относительно вышеизложенного и того, что
... бывают случаи, когда мне нужноустановить свойства для Child
в конструкторе ...
Простое правило: "Вы, вероятно, не должны".Роль объекта состоит в том, чтобы представлять состояние данных для этого объекта, не более того.Инициализация «графа» сущности должна выполняться не в конструкторе сущности верхнего уровня, а с использованием шаблона Factory.Например, я использую шаблон Repository с EF, который управляет не только геттерами, но и служит фабрикой, предоставляющей методы Create, а также обрабатывающей Delete для сценариев мягкого удаления.Это помогает гарантировать, что сущность с зависимостями всегда создается в «минимально завершенном» и действительном состоянии.
Даже приведенный выше пример, я бы сказал, является плохим примером.Даже при том, что это не отключает предупреждение компилятора, сущность в точке конструирования родителя не находится в завершенном и допустимом состоянии.Если бы я сделал что-то вроде:
using (var context = new MyContext ()) {var parent = new Parent ();parent.Name = "Myself";context.SaveChanges ();}
Если родительский элемент автоматически инициализирует дочерний элемент, то SaveChanges захочет сохранить этого нового дочернего элемента, и нет ничего, что гарантировало бы, что все обязательные поля дочернего элемента установлены, что вызов SaveChanges завершится неудачно.Ребенок не находится в достаточно завершенном состоянии.
Единственное место, где я бы рекомендовал автоматическую инициализацию, - это коллекции:
public class Parent
{
public virtual ICollection<Child> Children { get; internal set; } = new List<Child>();
}
Выше все еще "завершено" в том, что пустая коллекцияне буду пытаться что-либо сохранить для детей, если я заполню нового родителя, не добавляя детей.Кроме того, удобно, когда я создаю нового родителя, у меня есть возможность немедленно начать добавление / связывание дочерних элементов без отключения исключения нулевой ссылки, если у меня нет дочерних элементов.
Инициализация графа объектов с фабрикойМетод помогает гарантировать, что сущности всегда создаются в минимально завершенном состоянии, что означает, что они могут быть сохранены немедленно без ошибок.Как я уже упоминал выше, я обычно использую свой репозиторий в качестве фабрики сущностей, поскольку он уже подключен к DbContext через единицу работы для разрешения зависимостей по мере необходимости.
В качестве примера, если у меня есть сущность Orderчто я могу создать, который состоит из номера заказа, ссылки на клиента и одной или нескольких строк заказа для продуктов, которые необходимы для сохранения действительного заказа, мой OrderRepository может иметь метод CreateOrder что-то вроде этого:
public Order CreateOrder(int customerId, IEnumerable<OrderedProductViewModel> orderedProducts)
{
if (!orderedProducts.Where(x => x.Quantity > 0).Any())
throw new ArgumentException("No products selected.");
var customer = Context.Customers.Single(x => x.CustomerId == customerId);
var products = Context.Products.Where(x => orderedProducts.Where(o => o.Quantity > 0).Select(o => o.ProductId).Contains(x.ProductId)).ToList();
if (products.Count() < orderedProducts.Count())
throw new ArgumentException("Invalid products included in order.");
var order = new Order
{
Customer = customer,
OrderLines = orderedProducts.Select(x => new OrderLine
{
Product = products.Single(p => p.ProductId == x.ProductId),
Quantity = x.Quantity
}
}
Context.Orders.Add(order);
return order;
}
Это контекстуальный пример фабричного метода, который я мог бы использовать, и некоторые из основных проверок.OrderedProductViewModel эффективно представляет кортеж ProductId и количества.Это вместе с идентификатором клиента представляет минимальное состояние заказа, который я бы позволил сохранить.Могут быть и другие необязательные детали, которые могут быть установлены вне этого метода, прежде чем ордер будет считаться достаточно полным для отправки, но фабрика гарантирует, что он будет достаточно полным для сохранения.
Я мог бы вызвать код вызова, например:
using (var contextScope = ContextScopeFactory.Create())
{
var order = OrderRepository.Create(selectedCustomerId, selectedProducts);
contextScope.SaveChanges();
}
И это было бы здорово.Или я мог бы продолжить устанавливать доступную информацию о заказе перед вызовом SaveChanges.Мой OrderRepository не будет иметь какой-либо бизнес-логики, потому что эта бизнес-логика может зависеть от конфигурации для клиента, и у репозитория нет деловой информации об этом;но он может иметь зависимость для чего-то вроде IOrderValidator для передачи недавно предложенного Ордера, в который может перейти бизнес-логика, чтобы утверждать, что Орден достаточно действителен для сохранения.Репозиторий / фабрика утверждает, что он достаточно завершен, но его можно привязать к валидатору обратно в бизнес-логике, чтобы утверждать, что он достаточно действителен.(Т.е. минимальный / максимальный размер / стоимость заказа и т. Д.)
Это в сочетании с DDD, когда я делаю все установщики internal
и использую методы действий для своих сущностей, помогает контролировать, чтобы мои сущности всегда поддерживались в полном состоянии.Я предполагаю, что это то, что вы пытаетесь обеспечить с помощью конструкторов, поэтому я решил поделиться приведенным выше примером в качестве примера, чтобы предоставить некоторые возможные идеи и альтернативы для достижения этой цели.