Galaktikasoft

Hierarchical Data Implementation

Xafari framework provides a number of features for a comfortable and efficient work with hierarchical data. These features are available for objects that implement IHierarchyNode interface.

This post describes several ways to obtain hierarchical business objects:

  1. Implement IHierarchyNode interface from scratch, the result is XPO
  2. Inherit from DC.HierarchyNode, the result is Domain Component.
  3. Inherit from BC.DC.HierarchicalClassifierItem, the result is Domain Component.

1. HierarchyNodeObject class is BaseObject descendant and it implements IHierarchyNode interface:

public class HierarchyNodeObject : BaseObject, Xafari.Base.IHierarchyNode
{
    public HierarchyNodeObject(Session session) : base(session) { }

    //HierarchyNodeNamePropertyAttribute used in HierarchyNodePersistentHelper
    [HierarchyNodeNameProperty()]
    public String Name { get; set; }

    //PrentPropertyAttribute describe Parent property. You need to set it.
    [Association("Parent - Children")]
    [ParentProperty()]
    public HierarchyNodeObject Parent { get; set; }


    [Association("Parent - Children")] 
    //ChilderPropertyAttribute is obligitory
    [ChildrenProperty()]
    public XPCollection Children { get { return GetCollection("Children"); } }

    #region Implementation of IHierarchyNode

    public string GetHierarchyNodeName(string hierarchyName = null)
    {
        return HierarchyNodePersistentHelper.GetNodeName(this, hierarchyName);
    }

    public string GetHierarchyNodeFullName(string hierarchyName = null)
    {
        return HierarchyNodePersistentHelper.GetNodeFullName(this, hierarchyName);
    }
 
    public void SetHierarchyNodeFullName(string value, string hierarchyName = null)
    {
        HierarchyNodePersistentHelper.SetNodeFullName(this, value, hierarchyName);
    }
 
    public string GetHierarchyNodeFullPath(string hierarchyName = null)
    {
        return HierarchyNodePersistentHelper.GetNodeFullPath(this, hierarchyName);
    }
 
    public void SetHierarchyNodeFullPath(string value, string hierarchyName = null)
    {
        HierarchyNodePersistentHelper.SetNodeFullPath(this, value, hierarchyName);
    }

    public Xafari.Base.IHierarchyNode GetHierarchyParent(string hierarchyName = null)
    {
        return HierarchyNodePersistentHelper.GetParent(this, hierarchyName);
    }

    public void SetHierarchyParent(Xafari.Base.IHierarchyNode parent, string hierarchyName = null)
    {
        HierarchyNodePersistentHelper.SetParent(this, parent, hierarchyName);
    }
 
    public IList GetHierarchyChildren(string hierarchyName = null)
    {
        return HierarchyNodePersistentHelper.GetChildren(this, hierarchyName);
    }
 
    #endregion
}

As you can see from the code snippet above, HierarchyNodePersistentHelper class provides a ready-to-use method, thus allowing to implement IHierarchyNode interface very quickly and easily. Of course, if necessary, you can encode the methods of the interface on your own.

When implementing IHierarchyNode you must also declare Name, Parent and Children properties and decorate these with appropriate attributes: HierarchyNodeNamePropertyAttribute, ParentPropertyAtribute and ChildrenPropertyAttribute.

2. HierarchyNodeDescendant derived from Xafari.DC.HierarchyNode, it is Domain Component and it implements 4 different hierarchies.

[DomainComponent]
public interface HierarchyNodeDescendant : Xafari.DC.HierarchyNode, PersistentDynamicObject
{
    [HierarchyNodeNameProperty()]
    String Name { get; set; }

    #region Default Hierarchy

    
    [ParentProperty()]
    HierarchyNodeDescendant Parent { get; set; }

    [BackReferenceProperty("Parent")]
    [ChildrenProperty()]
    IList Children { get; }

    #endregion

    #region Hierarchy1

    [ParentProperty("Hierarchy1")]
    HierarchyNodeDescendant Parent1 { get; set; }

    [BackReferenceProperty("Parent1")]
    [ChildrenProperty("Hierarchy1", DeletingMode = DeletingMode.Cascade)]
    IList Children1 { get; }

    [FieldSize(1000)]
    [ModelDefault("RowCount", "0")]
    [ModelDefault("AllowEdit", "false")]
    [HierarchyFullNameProperty("Hierarchy1")]
    string HierarchyFullName1 { get; set; }

    #endregion

    #region Hierarchy2
    [ParentProperty("Hierarchy2")]
    HierarchyNodeDescendant Parent2 { get; set; }

    [BackReferenceProperty("Parent2")]
    [ChildrenProperty("Hierarchy2", DeletingMode = DeletingMode.Replacement)]
    IList Children2 { get;}

    [FieldSize(1000)]
    [ModelDefault("RowCount", "0")]
    [ModelDefault("AllowEdit", "false")]
    [HierarchyFullNameProperty("Hierarchy2")]
    string HierarchyFullName2 { get; set; }

    #endregion

    #region Hierarchy3
    [ParentProperty("Hierarchy3")]
    HierarchyNodeDescendant Parent3 { get; set; }

    [BackReferenceProperty("Parent3")]
    [ChildrenProperty("Hierarchy3", DeletingMode = DeletingMode.Replacement)]
    IList Children3 { get; }

    [FieldSize(1000)]
    [ModelDefault("RowCount", "0")]
    [ModelDefault("AllowEdit", "false")]
    [HierarchyFullNameProperty("Hierarchy3")]
    string HierarchyFullName3 { get; set; }

    #endregion
}

Inheritance from PersistentDynamicObject eliminates the need to implement the logic of IList<HierarchyNodeDescendant> Children fields.

Children and Parent fields are defined for each hierarchy, HierarchyFullName field allows you to visualize the nesting.

When implementing multiple hierarchies, you must pass the hierarchy name as a parameter to the following attributes: HierarchyFullNamePropertyAttribute, ParentPropertyAttribute, ChildrenPropertyAttribute, HierarchyFullPathPropertyAttribute, HierarchyNodeNamePropertyAttribute.

Obtained Domain Component need to register in module:

public override void Setup(XafApplication application)
{
    base.Setup(application);
    XafTypesInfo.Instance.RegisterEntity("HierarchyNodeDescendant", typeof(HierarchyNodeDescendant)); 
}

3. HierarchicalClassifierItemDescendant Domain Component derived from Xafari.BC.DC.HierarchicalClassifierItem:

[DomainComponent]
public interface HierarchicalClassifierItemDescendant : Xafari.BC.DC.HierarchicalClassifierItem
{
    [ParentProperty]
    HierarchicalClassifierItemDescendant Parent { get; set; }

    [ChildrenProperty]
    [BackReferenceProperty("Parent")]
    IList Children { get; }

    [RuleFromBoolProperty("IsValidParentDC", DefaultContexts.Save, "Нельзя выбрать самого себя в вышестоящие")]
    [NonPersistentDc]
    [MemberDesignTimeVisibility(false)]
    [Browsable(false)]
    bool IsValidParent { get; }
}

[DomainLogic(typeof(HierarchicalClassifierItemDescendant))]
public class DCHierarchyNodeObjectLogic : DomainLogicBase
{
    public DCHierarchyNodeObjectLogic(HierarchicalClassifierItemDescendant instance) : base(instance) { }

    public static bool Get_IsValidParent(HierarchicalClassifierItemDescendant instance)
    {
        return HierarchyNodePersistentHelper.CheckCircularReference(instance);
    }
}

This implementation only requires the declaration of Parent and Children properties. To exclude the situation when node sets himself as a parent, the IsValidParent property has been declared. Thus it was possible to avoid infinite loops.

Complete sample project is available at HierarchyNode.

In case you have any other questions regarding hierarchical data implementation in Xafari Framework, just contact us.