【问题标题】:C#: Add child to collection and set child's parent in same callC#:将孩子添加到集合并在同一个调用中设置孩子的父母
【发布时间】:2011-04-30 07:00:49
【问题描述】:

我正在构建一种领域层,我希望能够实现以下方法:

[注意:ISet 是一个不允许重复的集合类,根据使用 .Equals() 进行检查。]

public class Parent
{
public ISet Children = new HashedSet<Child>;
public void AddChild()
{
...
}
public void RemoveChild()
{
...
}
}

public class Child
{
public Parent Parent
{
get {...} set {...}
}
}

使得以下测试能够通过:

调用 parent.AddChild(child) 时:

  • parent.Children 包含孩子。
  • child.Parent 设置为父级。
  • 如果孩子有以前的父母,则该父母的孩子集合不再包含孩子

调用 parent.RemoveChild(child) 时:

  • child.Parent == null。
  • parent.Children 不包含子项。

设置 child.Parent 相当于调用 parent.AddChild()。 并且当孩子有一个现有的父母时设置 child.Parent = null 相当于调用 parent.RemoveChild()。

但我好像真的做不到!

这必须是域层中非常常见的逻辑,例如NHibernate,所以我想知道其他人是如何做到的?

【问题讨论】:

  • 这根本没有意义。想想你正在尝试做的事情的语义。这应该是什么结果: parent.Child.ElementAt(0).Parent.Child.ElementAt(5) = Parent.Child.Element(1)?
  • 我不明白你的问题。 Parent 没有 Child 属性;它有一个 Children 属性,它是 Child 对象的集合。

标签: c# nhibernate collections set


【解决方案1】:

像这样在孩子身上使用属性和变量

private Parent _parent;
public Parent parent { 
    get { return _parent; }
    set {
        if(_parent == value)
             return;  //Prevents circular assignement
        if(null != _parent) {
            Parent temp = _parent;
            _parent = null;
            temp.removeChild(this);
        }
        if(null != value) {
             _parent = value;
             value.addChild(this);  
        }
    }
}

这将导致设置子父属性将调用父方法,所有其他逻辑都可以放在这些方法中。

父母应该检查孩子的父母属性是否需要改变,但如果尝试再次设置相同的父母,set方法也会退出。 增强现实

【讨论】:

    【解决方案2】:

    我将从 ISet 派生 Parent 类并重写 Add 和 Remove 方法来完成将子项添加到列表并设置其父项的工作。

    只需在 Parent 属性的 set 访问器中编写将子元素添加到父集合的逻辑即可。

    【讨论】:

    • 从 ISet 派生不是一个选项,因为某些实体会有多个子集合。
    【解决方案3】:

    可能有一种更聪明的方法,但最终您只需要确保设置父/子关系的每种方法都检查其他方法,(即添加子检查以查看父属性是否已设置并设置父属性检查以确保父级包含子级)。

    // in Parent
    public void AddChild(Child c)
    {
        // need to check if Parent hasn't yet been set to this
        if (!c.Parent.Equals(this)) c.Parent = this;
        ...
    }
    public void RemoveChild(Child c)
    {
        // need to check if Parent is still set to this
        if (c.Parent.Equals(this)) c.Parent = null;
        ...
    }
    public bool Contains(Child c)
    {
        // assuming ISet implements this function
        return Children.Contains(c);
    }
    
    // in Child
    public Parent Parent
    {
        ...
        set
        {
            Parent old = _Parent;
            _Parent = value;
            if ((old != null) &&
                (old.Contains(this)))
            {
                old.RemoveChild(this);
            }
            if ((_Parent != null) &&
                (!_Parent.Contains(this)))
            {
                _Parent.AddChild(this);
            }
        }
    }
    

    我在删除子之前更改了 _Parent 的值,因为否则会从 Parent.Add/RemoveChild 代码中调用 Child.Parent 集代码。

    【讨论】:

      【解决方案4】:

      我无法给出一个好的代码答案,因为我不了解域的语义。

      • 什么是没有父母的孩子?
      • 为什么 addchild 既要添加无父子节点又要移动子节点 现有的孩子
      • 孩子和父母都是聚合根
      • 我想这不是关于人类父母和孩子的,而是 关于一些特定的领域。使用 在这个例子中可以做到 可能会提供更好的答案。

      当我知道所有这些事情后,我建议您从域中删除所有公共设置器,并为所有域操作添加具有描述性名称的方法。用户不操作数据,但他们做事。方法名称应该是您的领域语义,并包含执行该操作的所有业务逻辑。

      当父级是子级的聚合根时,我会像这样实现向父级添加一个新子级:

      public class Child
      {
      
        protected Child() { } // Constructor to please NHiberante's "Poco" implementation
      
        // internal to prevent other assemblies than the domain assembly from constructing childs
        internal Child(string somethingElse, Parent parent)
        {
          SomethingElse = somethingElse;
          Parent = parent;
        }
      
        // Parent can not be changed by the child itself, because parent is the aggregate root of child.
        public Parent Parent { get; private set; }
      
        public string SomethingElse { get; private set; }
      }
      
      public class Parent
      {
        private readonly ISet<Child> children;
      
        public Parent()
        {
          children = new HashedSet<Child>();
        }
      
        public IEnumerable<Child> Children
        {
          // only provide read access to the collection because manipulating the collection will disturb the domain semantics.
          get { return children; }
        }
      
        // This method wouldn't be called add child, but ExecuteSomeBusinessLogic in real code
        public void AddChild(string somethingElse)
        {
          // child constructor can only be called here because the parent is the aggregate root.
          var child = new Child(somethingElse, parent);
          children.Add(child);
        }
      }
      

      如果您提供更多信息,我可以回答您的其他要求。

      这个答案总结在一个单行词中:“对于应用程序中的所有状态操作,您需要一个方法,其名称说明它的作用。”

      编辑:对 cme​​ts 的反应。 我发现您的一个规范存在问题:只有父级是聚合根。在这种情况下,您永远不会在没有父母的情况下使用孩子。这使得双向关系毫无用处,因为当您访问它的孩子时,您已经知道父母。双向关系在 DDD 中很难管理,因此最好的解决方案是避免它们。我知道在使用 NHibernate 时无法避免它们的原因有很多。 以下代码具有双向关系,并通过使用内部辅助方法解决了域处于临时无效状态的问题。内部方法只能从域程序集中调用,不对外暴露。我不太喜欢这种方法,但它是我认为最不差的解决方案。

      public class Client : AggregateRoot
      {
      
        private readonly ISet<Contact> contacts;
      
        public Client()
        {
          contacts = new HashedSet<Contact>();
        }
      
        public IEnumerable<Contact> Contacts
        {
          get { return contacts; }
        }
      
        public void LogCall(string description)
        {
           var contact = new Contact(description, this);
           AddContact(contact);
        }
      
        internal void AddContact(Contact contact) 
        { 
          contacts.Add(contact);
        }
      
        internal void RemoveContact(Contact contact)
        {
          contacts.Remove(contact);
        }
      }
      
      public class Contact : AggregateRoot
      {
        protected Contact { }
      
        public Contact(string description, Client client)
        {
          if (description == null) throw new ArgumentNullException("description");
          if (client == null) throw new ArgumentNullException("client");
      
          Client = client;
          Description = description;
        }
      
        public Client Client { get; private set; }
      
        public string Description { get;private set; }
      
        // I assumed that moving a contact to another client would only be done by the user to correct mistakes?
        // Isn't it an UI problem when the user frequently makes this mistake?
        public void CorrectMistake(Client client)
        {
          Client.RemoveContact(this);
          Client = client;
          client.AddContact(this);
        }
      }
      

      【讨论】:

      • 感谢您的回复。回答您的问题:
      • 1) 没有父级的子级在域方面毫无意义,但在子级实例化之后和添加到父级之前可能会暂时存在。
      • 2) AddChild 需要将 Child 添加到父级的 Children 集合中,并将其从之前父级的 Children 集合中移除,因为 Child 无法在父级之间共享。
      • 3) 只有 Parent 是聚合根(参见 1)。
      • 4) 你是对的。 Child 和 Parent 是专门为这个问题发明的类名。我认为这会简化事情......!实际上,聚合根是一个客户(购买了电话指导服务的人),它拥有一组联系人(即电话)。
      【解决方案5】:

      我会实现我自己的 ISet,并在那里实现父跟踪。使用常规 HashSet 来完成存储对象的工作,但在您的类中做一些额外的工作。

      public class ChildrenSet : ISet<Child>
      {
          private HashSet<Child> backing = new HashSet<Child>();
          private Parent parent;
      
          internal ChildrenSet(Parent p)
          {
              this.parent = p;
          }
      
          public bool Add(Child item)
          {
              if(backing.Add(item))
              {
                  item.Parent = this.parent;
                  return true;
              }
              return false;
          }
      
          public bool Remove(Child item)
          {
              if(backing.Remove(item))
              {
                  item.Parent = null;
                  return true;
              }
              return false;
          }
      
          // etc for the rest of ISet. Also GetEnumerator, if desired.
      }
      

      但是,这并没有实现您提到的一件事:“如果孩子有以前的父母,那么该父母的孩子集合不再包含孩子”,因为您还说过“某些实体将有多个子集合”。

      将孩子从一个父母移到另一个父母时,如果它可能在多个集合中,您必须单独检查所有这些集合。由于听起来您在描述父母的层次结构,因此在尝试添加已经有父母的孩子时,可能更容易抛出InvalidOperationException

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-01-27
        • 1970-01-01
        • 1970-01-01
        • 2014-10-18
        • 2014-06-03
        • 2015-01-07
        • 1970-01-01
        • 2016-07-25
        相关资源
        最近更新 更多