代码之家  ›  专栏  ›  技术社区  ›  David

C#:将子级添加到集合中,并在同一调用中设置子级的父级

  •  2
  • David  · 技术社区  · 14 年前

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

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

    以便通过以下测试:

    什么时候父级.AddChild(child)称为:

    • 父项。子项包含子项。
    • 子级。父级设置为父级。
    • 如果子级有以前的父级,则该父级的子级集合不再包含子级

    打电话时父级.RemoveChild(儿童):

    • 子级。父级==空。

    还有那个场景子级。父级当子级有一个现有父级时,等于调用父级.RemoveChild().

    但我真的做不到!

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

    5 回复  |  直到 14 年前
        1
  •  1
  •   David Mårtensson    14 年前

    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);  
            }
        }
    }
    

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

    应收账

        2
  •  1
  •   A_Nabelsi    14 年前

    只需在parent属性的set accessor中编写将子级添加到父集合的逻辑。

        3
  •  1
  •   Mark Synowiec    14 年前

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

    // 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);
            }
        }
    }
    

    在删除子级之前,我更改了\u Parent的值,因为子级。父级否则将从Parent.Add/RemoveChild代码。

        4
  •  0
  •   David Yaw    14 年前

    我将实现我自己的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.
    }
    

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

    InvalidOperationException

        5
  •  0
  •   Brock Adams    13 年前

    我不能给出一个好的代码答案,因为我不理解该域的语义。

    • 什么是没有父母的孩子?
    • 现有子女
    • 子级和父级是否都是AggregateRoot
    • 我想这不是关于人类父母和孩子的,但是

    当parent是children的聚合根时,我将实现如下所示向父级添加一个新的子级:

    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);
      }
    }
    

    这个答案用一行代码概括:“对于应用程序中的所有状态操作,您需要一个具有名称的方法来说明它的作用。”

    我看到你的一个规格有问题:只有父级是聚合根。在这种情况下,如果没有父对象,就永远不会使用子对象。这使得双向关系毫无用处,因为当您访问父对象的子对象时,您已经知道父对象。在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);
      }
    }