Fluent NHibernate SubClass syntax changes

I’ve just committed a breaking change to Fluent NHibernate (as of r184), which I thought I’d document here for anyone interested; it’s a reworking of the subclass syntax.

Mapping multiple subclasses with the same parent wasn’t a very fluent affair, and it was pretty wordy too. You can see a comparison of the old and new syntaxes below.

Before

var discriminator = DiscriminateSubClassesOnColumn<string>("Type");

discriminator.SubClass<B>()
  .IdentifiedBy("bID")
  .MapSubClassColumns(m =>
  {
    m.Map(x => x.BProperty);
  });

discriminator.SubClass<C>()
  .IdentifiedBy("cID")
  .MapSubClassColumns(m =>
  {
    m.Map(x => x.CProperty);
  });

After

DiscriminateSubClassesOnColumn("Type")
  .SubClass<B>(m =>
  {
    m.Map(x => x.BProperty);
  })
  .SubClass<C>(m =>
  {
    m.Map(x => x.CProperty);
  });

Much nicer! The changes you can see here are:

  • DiscriminateSubClassesOnColumn now assumes your discriminator is a string if you don’t specify a type
  • SubClass defaults to using the subclass type name as a discriminator value
  • IdentifiedBy and MapSubClassColumns are now merged into SubClass as overloads.

Nested subclasses were never really supported in Fluent NHibernate, but they were hackable. You could abuse DiscriminateSubClassesOnColumn to let you trick it into creating nested classes. This worked but it led to some really ugly mapping code (and a nasty hack in the Fluent NHibernate codebase). I’ve given some loving to this area and have managed to really sweeten-up the syntax.

Before

DiscriminateSubClassesOnColumn<string>("Type")
  .SubClass<B>()
    .IdentifiedBy("bID")
    .MapSubClassColumns(m =>
    {
      m.Map(x => x.BProperty);
      m.DiscriminateSubClassesOnColumn<string>("Type")
        .SubClass<C>()
          .IdentifiedBy("cID")
          .MapSubClassColumns(m =>
          {
            m.Map(x => x.CProperty);
          });
    });

After

DiscriminateSubClassesOnColumn("Type")
  .SubClass<B>(m =>
  {
    m.Map(x => x.BProperty);
    m.SubClass<C>(m =>
    {
      m.Map(x => x.CProperty);
    });
  });

The changes in this one are:

  • SubClass can now be used within subclasses without having to reuse DiscriminateSubClassesOnColumn

All in all, these changes serve to make mapping subclasses in Fluent NHibernate a little bit neater.

Update

As requested, here are the domain entites that the above mappings represent.

Two subclasses with shared parent

public class A
{}

public class B : A
{
  public virtual string BProperty { get; set; }
}

public class C : A
{
  public virtual string CProperty { get; set; }
}

Subclass of a subclass

public class A
{}

public class B : A
{
  public virtual string BProperty { get; set; }
}

public class C : B
{
  public virtual string CProperty { get; set; }
}

Comments 15

  1. Paul Batum wrote:

    Very nice changes James. You’ve been a whirlwind of Fluent NHibernate activity this last week or so. You are doing a stellar job.

    Posted 05 Jan 2009 at 12:38 am
  2. Chris Marisic wrote:

    Could you post what your class objects would actually look like for these mappings?

    Posted 05 Jan 2009 at 4:46 am
  3. James Gregory wrote:

    @Paul: Thanks Paul, I put Christmas to good use for once ;)

    @Chris: I’ve updated the post with the domain entities, but they’re pretty simple really.

    Posted 05 Jan 2009 at 12:43 pm
  4. Harald Croll wrote:

    Hi,
    there is a little bug, if you try to add a version column to the ClassMap, the tag in the xml file is added before the discriminator tag and therefore raising an exception (the discriminator tag has to be directly below the id tag)

    Posted 06 Jan 2009 at 10:52 pm
  5. James Gregory wrote:

    @Harold: Thanks for that, I’ll create an issue for it and we’ll tackle it as soon as we can.

    Posted 06 Jan 2009 at 10:55 pm
  6. Ian Shimmings wrote:

    Great work. I am really loving using this.
    I noticed a bug where .DiscriminateSubClassesOnColumn(”ColumnName”) still tried to use a string type for the column. It does work with a default as .DiscriminateSubClassesOnColumn(”NetworkTypeID”, 0).
    I’ve also been working on some enhancements to the test framework. When I get them working I’ll pass them on.

    Posted 20 Jan 2009 at 5:55 pm
  7. Ian Shimmings wrote:

    Oops. With encoding included.
    I noticed a bug where .DiscriminateSubClassesOnColumn<int>(”ColumnName”) still tried to use a string type for the column. It does work with a default as .DiscriminateSubClassesOnColumn(”NetworkTypeID”, 0).

    Posted 20 Jan 2009 at 5:57 pm
  8. Guido wrote:
    public MapSample() {
      Id(x => x.Id);
      //stringa da un type
      DiscriminateSubClassesOnColumn("Type")
        .SubClass<B>(m =>{
          m.Map(x => x.BProperty);
        })
        .SubClass<C>(m =>{
          m.Map(x => x.CProperty);
        });
    }
    
    public class A :entity.Entity /*Entity holds the Id */
    {}  
    
    public class B : A
    {
      public virtual string BProperty { get; set; }
    }  
    
    public class C : A
    {
      public virtual string CProperty { get; set; }
    }

    This little Fixture is required -pun intended. Admittedly it’s obvious, yet… Thank you, I like it.

    Posted 11 Feb 2009 at 8:26 am
  9. lieben wrote:

    Interessante Informationen.

    Posted 06 Mar 2009 at 3:27 am
  10. kiwisurfer wrote:

    I’m trying to work out the following scenario with regards to Fluent NHibernate notation for the following:

    public class A { }

    public class B : A { }

    public class C : B { }

    public class D : B { }

    Where the persisted data for class B, class C and class D are all stored in different tables to A.

    If the discriminator for type C and D is stored in table B and is a simple char field, what notation is required to:

    1) Allow for a 1-to-1 relationship between the parent and subtype record in table B and table C
    2) Allow for a 1-to-1 relationship between the parent and subtype record in table B and table D?

    Posted 18 Mar 2009 at 4:17 pm
  11. kosalanp wrote:

    How do you do the same with .hbm files? meaning, 1 .hbm file per sub class (can ignore the super class) ? Its like, all 3 classes mapped to 1 table, but in 2 hbm files? (hope you know what i meant).

    Posted 25 Mar 2009 at 7:13 pm
  12. James Gregory wrote:

    Sorry but your comment got stuck in limbo for a bit there. I’d suggest you hit the mailing list for support as you’re much more likely to get a response.

    As for your particular question, I’m afraid you’re out of luck right now. We don’t currently support that scenario, but we are very much aware of a need for it.

    Posted 30 Mar 2009 at 5:00 pm
  13. Luiz Sergio wrote:

    How can I to create a mapping when a Person can be Employee and/or Customer and/or Supplier, using FNH?

    Posted 23 Apr 2009 at 6:10 pm
  14. James Gregory wrote:

    Please ask on the Fluent NHibernate mailing list.

    Posted 27 Apr 2009 at 11:34 am
  15. Dragos Haiduc wrote:

    Hi James,

    When using this approach, only table A is created for all given types.

    How can I have a table per subtype using this synthax?

    Posted 26 May 2009 at 10:56 am

Post a Comment

Your email is never published nor shared. Required fields are marked *