Конструкция, управляемая доменом (Linq to SQL) - Как удалить части совокупности? - PullRequest
7 голосов
/ 17 марта 2009

Кажется, я немного запутался во всем этом бизнесе DDD \ LinqToSql. Я строю систему, используя POCOS и linq to sql, и у меня есть репозитории для агрегатных корней. Так, например, если у вас были классы Order-> OrderLine, у вас есть репозиторий для Order, но нет OrderLine, так как Order является корнем агрегата. В репозитории есть метод delete для удаления Order, но как вы удаляете OrderLines? Вы бы подумали, что у вас есть метод Order, называемый RemoveOrderLine, который удалил строку из коллекции OrderLines, но ему также необходимо удалить OrderLine из базовой таблицы l2s. Поскольку для OrderLine нет репозитория, как вы должны это сделать?

Возможно, есть специализированные общедоступные репозитории для запросов к корням и внутренним универсальным репозиториям, которые объекты домена фактически используют для удаления содержимого в агрегатах?

public class OrderRepository : Repository<Order> {
    public Order GetOrderByWhatever();
}

public class Order {
    public List<OrderLines> Lines {get; set;} //Will return a readonly list
    public RemoveLine(OrderLine line) {
        Lines.Remove(line);
        //************* NOW WHAT? *************//
        //(new Repository<OrderLine>(uow)).Delete(line) Perhaps??
        // But now we have to pass in the UOW and object is not persistent ignorant. AAGH!
    }
}

Мне бы очень хотелось узнать, что сделали другие люди, поскольку я не могу быть единственным, кто борется с этим .... Надеюсь .... Спасибо

Ответы [ 4 ]

2 голосов
/ 17 марта 2009

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

Позже вы вызываете метод Save / Update для репозитория, который получает измененный заказ. Конкретная задача заключается в том, чтобы узнать, что изменилось в доменном объекте, и есть несколько вариантов (я уверен, что их больше, чем перечисленных):

  • Пусть объект домена отслеживает изменения, в том числе отслеживает необходимость удаления x из строк заказа. Нечто подобное отслеживанию сущностей также может быть учтено.
  • Загрузить сохраненную версию. Имейте в хранилище код, который распознает различия между сохраненной версией и версией в памяти, и запустите изменения.
  • Загрузить сохраненную версию. Имейте код в корневом агрегате, который будет отличать вас от исходного корневого агрегата.
1 голос
/ 05 ноября 2009

После борьбы с этой проблемой я нашел решение. Посмотрев на то, что дизайнер генерирует с помощью l2sl, я понял, что решение заключается в двусторонней связи между заказом и строкой заказа. У заказа есть много строк заказа, и у строки заказа есть единственный заказ. Решение состоит в том, чтобы использовать двусторонние ассоциации и атрибут сопоставления с именем DeleteOnNull (который вы можете найти в Google для полной информации). Последнее, чего мне не хватало, было то, что ваш класс сущностей должен зарегистрироваться для добавления и удаления событий из набора сущностей l2s. В этих обработчиках вы должны установить связь Order в строке заказа, чтобы быть нулевой. Вы можете увидеть пример этого, если посмотрите на некоторый код, сгенерированный конструктором l2s.

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

1 голос
/ 17 марта 2009

Во-первых, вы должны использовать интерфейсы для получения ссылок на ваш совокупный корень (т. Е. Order ()). Используйте шаблон Factory для создания нового экземпляра Aggregate Root (т. Е. Order ()).

С учетом вышесказанного методы вашего Aggregate Root контролируют доступ к связанным объектам, а не к себе. Кроме того, никогда не раскрывайте сложные типы как общедоступные для агрегатных корней (т. Е. Коллекции IList Lines (), которую вы указали в примере). Это нарушает закон decremeter (sp ck), который гласит, что вы не можете "Dot Walk" свой путь к методам, таким как Order.Lines.Add ().

Кроме того, вы нарушаете правило, позволяющее клиенту получать доступ к ссылке на внутренний объект в совокупном корне. Агрегированные корни могут возвращать ссылку на внутренний объект. Пока внешнему клиенту не разрешено хранить ссылку на этот объект. То есть, ваш «OrderLine» вы передаете в RemoveLine (). Вы не можете позволить внешнему клиенту управлять внутренним состоянием вашей модели (т. Е. Order () и его OrderLines ()). Поэтому следует ожидать, что OrderLine будет новым экземпляром, который будет действовать соответственно.

public interface IOrderRepository
{
  Order GetOrderByWhatever();
}

internal interface IOrderLineRepository
{
  OrderLines GetOrderLines();
  void RemoveOrderLine(OrderLine line);
}

public class Order
{
  private IOrderRepository orderRepository;
  private IOrderLineRepository orderLineRepository;
  internal Order()
  {
    // constructors should be not be exposed in your model.
    // Use the Factory method to construct your complex Aggregate
    // Roots.  And/or use a container factory, like Castle Windsor
    orderRepository = 
            ComponentFactory.GetInstanceOf<IOrderRepository>();
    orderLineRepository = 
            ComponentFactory.GetInstanceOf<IOrderLineRepository>();
  }
  // you are allowed to expose this Lines property within your domain.
  internal IList<OrderLines> Lines { get; set; }  
  public RemoveOrderLine(OrderLine line)
  {
    if (this.Lines.Exists(line))
    {
      orderLineRepository.RemoveOrderLine(line);
    }
  }
}

Не забудьте свою фабрику по созданию новых экземпляров Order ():

public class OrderFactory
{
  public Order CreateComponent(Type type)
  {
    // Create your new Order.Lines() here, if need be.
    // Then, create an instance of your Order() type.
  }
}

Ваш внешний клиент имеет право на прямой доступ к IOrderLinesRepository через интерфейс для получения ссылки на объект значения в вашем Агрегированном корне. Но я пытаюсь заблокировать это, заставляя мои ссылки использовать все методы Aggregate Root. Таким образом, вы можете пометить описанный выше IOrderLineRepository как внутренний, чтобы он не отображался.

На самом деле я группирую все свои совокупные коренные создания на несколько Фабрик. Мне не понравился подход: «Некоторые совокупные корни будут иметь фабрики для сложных типов, другие не будут». Гораздо проще иметь ту же логику, которой следуют на протяжении всего моделирования предметной области. «О, так что Sales () является агрегатным корнем, подобным Order (). Для него тоже должна быть фабрика».

И последнее замечание: если у вас есть комбинация, то есть SalesOrder (), которая использует две модели Sales () и Order (), вы бы использовали Сервис для создания и использования этого экземпляра SalesOrder (), так как Совокупные корни Sales () или Order (), а также их репозитории или фабрики контролируют сущность SalesOrder ().

Я очень, очень рекомендую эту бесплатную книгу Абеля Аврама и Флойда Маринеску о дизайне доменного диска (DDD), так как она прямо отвечает на ваши вопросы, напечатанной крупным шрифтом на 100 страниц. Наряду с тем, как разделить сущности вашего домена на модули и тому подобное.

Редактировать: добавлено больше кода

0 голосов
/ 15 апреля 2009

Как продолжение .... Я перешел на использование nhibernate (вместо ссылки на sql), но в действительности вам не нужны репозитории для OrderLine. Если вы просто удалите OrderLine из коллекции в Order, он просто удалит OrderLine из базы данных (при условии, что вы правильно сделали отображение). Как я обмениваюсь с репозиториями в памяти, если вы хотите искать конкретную строку заказа (не зная родителя заказа), вы можете написать запрос linq to nhibernate, который связывает заказ с строкой заказа, где orderlineid = значение. Таким образом, он работает при запросах из БД и из памяти. Ну вот и все ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...