Self-Referential Mapping to Implement Hierarchical Tree Structure

1,089 views
Skip to first unread message

Craig

unread,
Jan 15, 2009, 2:26:09 PM1/15/09
to Fluent NHibernate
Wondering if anyone has attempted to create a hierarchical tree
structure using Fluent NHibernate? I've been experimenting with
various mappings, but can't seem to get it.

The example in this case is a basic Category hierarchy. Ideally, I'd
prefer to use a nested set model implementation (http://dev.mysql.com/
tech-resources/articles/hierarchical-data.html), but I think the more
complex SQL would make this especially challenging or require
overrides. So, I've reverted to attempting an implementation of an
adjacency list model.

// Category Entity
public class Category
{
public Category(string name) { Name = name; }
public Category() {}
public IList<Category> Children { get; set; }
public Category Parent { get; set; }
}

// Category Map ???
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
WithTable("Category");
Id(x => x.ID, "CategoryID").GeneratedBy.Assigned();
References(x => x.Parent, "ParentID");
//HasMany<Category>(x => x.Children).AsList(); ??
}
}

I've been looking at a few references online, mostly related to
Hibernate and found some example mappings but don't know how to
translate them to Fluent.
* http://www.hibernate.org/86.html
* http://lykke.wordpress.com/2006/08/04/self-referencing-classes-in-hibernate/

Is this possible with Fluent?

Gabriel Schenker

unread,
Jan 16, 2009, 3:15:07 AM1/16/09
to fluent-n...@googlegroups.com
please have a look at the following post (the Node hierarchy)
http://blogs.hibernatingrhinos.com/nhibernate/archive/2008/05/14/how-to-map-a-tree-in-nhibernate.aspx

the corresponding mapping in FNH would be

    public class NodeMap : ClassMap<Node>
    {
        public NodeMap()
        {
            Id(x => x.Id);
            Map(x => x.Name);
            References(x => x.Parent)
                .TheColumnNameIs("ParentId");
            HasMany<Node>(x => x.Children)
                .WithKeyColumn("ParentId")
                .WithForeignKeyConstraintName("fk_Node_ParentNode")
                .Cascade.All()
                .AsSet();
            HasManyToMany<Node>(x => x.Descendants)
                .WithParentKeyColumn("ParentId")
                .WithChildKeyColumn("ChildId")
                .WithTableName("NodeHierarchy")
                .AsSet()
                .IsInverse();
            HasManyToMany<Node>(x => x.Ancestors)
                .WithParentKeyColumn("ChildId")
                .WithChildKeyColumn("ParentId")
                .WithTableName("NodeHierarchy")
                .AsSet();

Craig

unread,
Jan 16, 2009, 11:45:48 AM1/16/09
to Fluent NHibernate
Thanks Gabriel, the example you have uses two tables, not one
(allowing a fancier tree than I need), but it convinced me that I was
going in the right direction with my previous attempts.

First I got this hbm working, easily enough:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="Category" table="Category">

<id name="ID" type="int">
<meta attribute="use-in-equals">true</meta>
<column name="CategoryID" />
<generator class="increment" />
</id>

<property name="Name" type="string">
<meta attribute="use-in-tostring">true</meta>
<column name="Name" length="255" not-null="true"
unique="false" />
</property>

<bag name="SubCategories" lazy="false" cascade="all-delete-
orphan" inverse="true">
<key column="ParentID" />
<one-to-many class="Category" />
</bag>

<many-to-one name="ParentCategory" class="Category">
<column name="ParentID" />
</many-to-one>
</class>

</hibernate-mapping>

Then working with your mapping, finalized on:

public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
WithTable("Category");
Id(x => x.ID, "CategoryID").GeneratedBy.Increment
().WithUnsavedValue(0);
Map(x => x.Name);
References(x => x.ParentCategory).TheColumnNameIs
("ParentId");
HasMany<Category>(x => x.SubCategories)
.WithKeyColumn("ParentId")
.IsInverse()
.Cascade.All()
.AsSet();
}
}

A couple problems I encountered was using AsList() instead of AsSet or
AsBag. AsList() added another field to the query, which didn't exist
in my table ('idx'). Also, (using asp.net/mvc), the Default Model
Binder had issues with the circular reference in my Entity causing a
Stack Overflow exception (see: http://www.codeplex.com/WorkItem/View.aspx?ProjectName=aspnet&WorkItemId=2540).
This was solved by removing the ParentCategory from the action method
in my controller, e.g.:

public virtual ActionResult Edit(int id, [Bind(Exclude =
"ParentCategory")]Category category)

Thanks for your help!

On Jan 16, 3:15 am, Gabriel Schenker <gnschen...@gmail.com> wrote:
> please have a look at the following post (the Node hierarchy)http://blogs.hibernatingrhinos.com/nhibernate/archive/2008/05/14/how-...
> > tech-resources/articles/hierarchical-data.html<http://dev.mysql.com/tech-resources/articles/hierarchical-data.html>),
> > but I think the more
> > complex SQL would make this especially challenging or require
> > overrides. So, I've reverted to attempting an implementation of an
> > adjacency list model.
>
> > // Category Entity
> > public class Category
> > {
> >        public Category(string name) { Name = name; }
> >        public Category() {}
> >        public IList<Category> Children { get; set; }
> >        public Category Parent { get; set; }
> > }
>
> > // Category Map ???
> > public class CategoryMap : ClassMap<Category>
> > {
> >        public CategoryMap()
> >        {
> >            WithTable("Category");
> >            Id(x => x.ID, "CategoryID").GeneratedBy.Assigned();
> >            References(x => x.Parent, "ParentID");
> >            //HasMany<Category>(x => x.Children).AsList(); ??
> >        }
> > }
>
> > I've been looking at a few references online, mostly related to
> > Hibernate and found some example mappings but don't know how to
> > translate them to Fluent.
> > *http://www.hibernate.org/86.html
> > *
> >http://lykke.wordpress.com/2006/08/04/self-referencing-classes-in-hib...
Reply all
Reply to author
Forward
0 new messages