Когда вы говорите «загрузить» в «Как мне загрузить только одну из 1000 позиций?» Вы имеете в виду «загрузить из базы данных»? Другими словами, как мне загрузить только одну дочернюю сущность совокупного корня из базы данных?
Это немного сложно, но вы можете сделать так, чтобы ваши репозитории возвращали производную от агрегатного корня, чьи поля загружаются отложенным образом. Э.Г.
namespace Domain
{
public class LineItem
{
public int Id { get; set; }
// stuff
}
public class Order
{
public int Id { get; set; }
protected ReadOnlyCollection<LineItem> LineItemsField;
public ReadOnlyCollection<LineItem> LineItems { get; protected set; }
}
public interface IOrderRepository
{
Order Get(int id);
}
}
namespace Repositories
{
// Concrete order repository
public class OrderRepository : IOrderRepository
{
public Order Get(int id)
{
Func<IEnumerable<LineItem>> getAllFunc = () =>
{
Collection<LineItem> coll;
// { logic to build all objects from database }
return coll;
};
Func<int, LineItem> getSingleFunc = idParam =>
{
LineItem ent;
// { logic to build object with 'id' from database }
return ent;
};
// ** return internal lazy-loading derived type **
return new LazyLoadedOrder(getAllFunc, getSingleFunc);
}
}
// lazy-loading internal derivative of Order, that sets LineItemsField
// to a ReadOnlyCollection constructed with a lazy-loading list.
internal class LazyLoadedOrder : Order
{
public LazyLoadedOrder(
Func<IEnumerable<LineItem>> getAllFunc,
Func<int, LineItem> getSingleFunc)
{
LineItemsField =
new ReadOnlyCollection<LineItem>(
new LazyLoadedReadOnlyLineItemList(getAllFunc, getSingleFunc));
}
}
// lazy-loading backing store for LazyLoadedOrder.LineItems
internal class LazyLoadedReadOnlyLineItemList : IList<LineItem>
{
private readonly Func<IEnumerable<LineItem>> _getAllFunc;
private readonly Func<int, LineItem> _getSingleFunc;
public LazyLoadedReadOnlyLineItemList(
Func<IEnumerable<LineItem>> getAllFunc,
Func<int, LineItem> getSingleFunc)
{
_getAllFunc = getAllFunc;
_getSingleFunc = getSingleFunc;
}
private List<LineItem> _backingStore;
private List<LineItem> GetBackingStore()
{
if (_backingStore == null)
_backingStore = _getAllFunc().ToList(); // ** lazy-load all **
return _backingStore;
}
public LineItem this[int index]
{
get
{
if (_backingStore == null) // bypass GetBackingStore
return _getSingleFunc(index); // ** lazy-load only one from DB **
return _backingStore[index];
}
set { throw new NotSupportedException(); }
}
// "getter" implementations that use lazy-loading
public IEnumerator<LineItem> GetEnumerator() { return GetBackingStore().GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
public bool Contains(LineItem item) { return GetBackingStore().Contains(item); }
public void CopyTo(LineItem[] array, int arrayIndex) { GetBackingStore().CopyTo(array, arrayIndex); }
public int Count { get { return GetBackingStore().Count; } }
public bool IsReadOnly { get { return true; } }
public int IndexOf(LineItem item) { return GetBackingStore().IndexOf(item); }
// "setter" implementations are not supported on readonly collection
public void Add(LineItem item) { throw new NotSupportedException("Read-Only"); }
public void Clear() { throw new NotSupportedException("Read-Only"); }
public bool Remove(LineItem item) { throw new NotSupportedException("Read-Only"); }
public void Insert(int index, LineItem item) { throw new NotSupportedException("Read-Only"); }
public void RemoveAt(int index) { throw new NotSupportedException("Read-Only"); }
}
}
Абоненты OrderRepository.Get(int)
получают что-то, что фактически является просто объектом Order, но на самом деле является LazyLoadedOrder. Конечно, для этого ваши совокупные корни должны предоставлять одного или двух виртуальных членов и быть рассчитаны вокруг этих точек расширения.
Изменить для обновления вопроса вопроса
В случае адреса я бы рассматривал его как объект значения, то есть неизменные композиции данных, которые вместе обрабатываются как одно значение.
public class Address
{
public Address(string street, string city)
{
Street = street;
City = city;
}
public string Street {get; private set;}
public string City {get; private set;}
}
Затем, чтобы изменить агрегат, вы создаете новый экземпляр Address. Это аналогично поведению DateTime. Вы также можете добавить методы методов в Address, такие как SetStreet(string)
, но они должны возвращать новые экземпляры Address, так же как методы DateTime возвращают новые экземпляры DateTime.
В вашем случае объекты неизменяемого значения адреса должны сочетаться с каким-либо наблюдением за коллекцией адресов. Простой и понятный метод - отслеживать добавленные и удаленные AddressValues в отдельных коллекциях.
public class Customer
{
public IEnumerable<Address> Addresses { get; private set; }
// backed by Collection<Address>
public IEnumerable<Address> AddedAddresses { get; private set; }
// backed by Collection<Address>
public IEnumerable<Address> RemovedAddresses { get; private set; }
public void AddAddress(Address address)
{
// validation, security, etc
AddedAddresses.Add(address);
}
public void RemoveAddress(Address address)
{
// validation, security, etc
RemovedAddresses.Add(address);
}
// call this to "update" an address
public void Replace(Address remove, Address add)
{
RemovedAddresses.Add(remove);
AddedAddresses.Add(add);
}
}
В качестве альтернативы вы можете добавить адрес с ObservableCollection<Address>
.
Это действительно чистое решение DDD, но вы упомянули NHibernate. Я не эксперт NHibernate, но я думаю, что вам придется добавить код, чтобы NHibernate знал, где хранятся изменения в адресах.