Search code examples
c#nhibernatefluent-nhibernatefluent-nhibernate-mapping

Fluent NHibernate Convention mapping tree structure


I want to put a tree structure (stored as an adjacency list) into a database, and I want to use Fluent NHibernate with Convention mapping to do it. I have the following code:

[...]

NamespaceAutomapping automapping = new NamespaceAutomapping("NHibernateTree.Model", false); //Maps the classes in the namespace NHibernateTree.Model, false meaning to not map sub-namespaces like NHibernateTree.Model.ABC 

FluentConfiguration fluentConfiguration = Fluently.Configure()
  .Database(FluentNHibernate.Cfg.Db.PostgreSQLConfiguration.PostgreSQL82.ConnectionString(connectionString))
  .Mappings(m => m.AutoMappings.Add(AutoMap.AssemblyOf<TreeNode>(automapping)
      //.Conventions.Add<ForeignKeyNameConvention>()
      ));

[...]

ISessionFactory SessionFactory = fluentConfiguration.BuildSessionFactory();

using (ISession session = SessionFactory.OpenSession())
{
    TreeNode root = new TreeNode() { Value = "Root" };

    TreeNode childA = new TreeNode() { Parent = root, Value = "Child A" };
    root.Children.Add(childA);

    TreeNode childB = new TreeNode() { Parent = root, Value = "Child B" };
    root.Children.Add(childB);

    session.Save(root);
    session.Save(childA);
    session.Save(childB);
}

using (ISession session = SessionFactory.OpenSession())
{
    TreeNode root = session.Query<TreeNode>().First(tn => tn.Value == "Root");
    Console.WriteLine(root.ToString());
}

The convention that is out-commented looks like this:

public class ForeignKeyNameConvention : ForeignKeyConvention
{
    protected override String GetKeyName(Member property, Type type)
    {
        return String.Format("\"{0}Id\"", null == property ? type.Name : property.Name);
    }
}

And the class NHibernateTree.Model.TreeNode looks like this: (This is the only class in that namespace)

class TreeNode
{
    public virtual int Id { get; set; }
    public virtual TreeNode Parent { get; set; }
    public virtual ICollection<TreeNode> Children { get; set; }
    public virtual String Value { get; set; }

    public TreeNode()
    {
        Children = new Collection<TreeNode>();
    }

    public override string ToString()
    {
        [...]
    }
}

Now, if I run this code, the output is:

Root
  Child A
  Child B

And the table in the database looks like this:

  Column   |          Type
-----------+-----------------------
 id        | integer
 value     | character varying(255)
 parent_id | integer

 id |  value  | parent_id 
----+---------+-----------
  1 | Root    |
  2 | Child A |         1
  3 | Child B |         1

So far, so good. But if I un-comment the line in the program that adds the convention, then strange things happen. The output of the program is only "Root" (i.e. NHibernate does not find the reference to the children anymore), and the table looks like this instead:

   Column   |          Type
------------+-----------------------
 id         | integer
 value      | character varying(255)
 ParentId   | integer
 TreeNodeId | integer

 id |  value  | ParentId | TreeNodeId
----+---------+----------+------------
  1 | Root    |          |
  2 | Child A |        1 |
  3 | Child B |        1 |

The column "TreeNodeId" is NULL in all rows.

Obviously the convention is messing something up. I would want to use this convention to get the names of foreign keys nice, but I can't get it to work.

I have found some posts on SO where people solve basically the same problem, but not with conventions.

Any help appreciated!


Solution

  • I finally found the solution: The class that my Convention is extending is a standard Fluent NHibernate class, and it looks like this: https://github.com/jagregory/fluent-nhibernate/blob/master/src/FluentNHibernate/Conventions/ForeignKeyConvention.cs

    The interesting method in it looks like this:

    public void Apply(ICollectionInstance instance)
    {
        var columnName = GetKeyName(null, instance.EntityType);
    
        instance.Key.Column(columnName);
    }
    

    This makes the collection Children in TreeNode map by adding a column on the type of the collection (which in this case is the same class). In my case I already have this column, ParentId, but NHibernate adds another one named TreeNodeId .

    My solution to this was to skip mapping of collections that contains the same type as the containing class. Meaning that NHibernate wont create the extra column TreeNodeId. So instead of extending ForeignKeyConvention I copied the baseclass ForeignkeyConvention, and modified the above method to the following:

    public void Apply(ICollectionInstance instance)
    {
        if (instance.EntityType != instance.ChildType)
        {
            String columnName = GetKeyName(null, instance.EntityType);
    
            instance.Key.Column(columnName);
        }
    }