Friday, January 6, 2012

Mapping-by-Code naming convention resembling Fluent

Recently I've posted about my first impressions about NHibernate's mapping-by-code and the one thing I was surprised was that the default naming conventions provided are not usable.

I was using default Fluent NHibernate's naming convention for quite a long time and there were only few scenarios (not counting legacy databases) when I needed to specify columns or table names on my own. With mapping-by-code it is not possible to map simple many-to-many without changing the default naming convention or specifying the names in the mapping, quite XML-style. A bit annoying.

So, as a part of my experiments with the tool, I've written another naming convention that is aimed to resemble Fluent NHibernate's default naming convention, so that:

  • foreign key columns in child-parent relationships are called "Parent_id"
  • many-to-many intermediate table is called "FirstToSecond" and its foreign key columns are "First_id" and "Second_id"
  • on bidirectional relations, both sides match together automatically

Nothing really impressing, but missing in default mapping-by-code naming convention.

The code is here on GitHub. Feel free to use it if you find it useful. It is of course far from being complete - it's not even trying to cope with features like maps, one-to-one's, any's, many-to-any's etc.

My convention doesn't support join either and it seems that it is not possible with current mapping-by-code implementation - there's no event to attach to in case of join mapping and it looks like joins are implemented in different way. Foreign key column of joined table is called "parent_key", and can be overriden in ClassMapping only.

I'm inheriting ConventionModelMapper behaviors and adding few own rules, that are applied before mapping, so that it can be overriden in ClassMappings in a standard way. I've used few extension methods provided by NHibernate's code. It's a pity that they are not mentioned anywhere - I've just found it in the source code quite accidentally.

When using my ModelMapperWithNamingConventions in place of ModelMapper or ConventionModelMapper, many-to-many verbose mapping I've presented previously is reduced to something more friendly and quite elegant, like Fluent NHibernate has accustomed me - I can remove key column definitions from both Bag and ManyToMany mapping and remove Table name definition from Bag mapping.

public class Street
{
public Street()
{
Districts = new List<District>();
}

public virtual int Id { get; protected set; }
public virtual string Name { get; set; }
public virtual ICollection<District> Districts { get; set; }
}

public class StreetMap : ClassMapping<Street>
{
public StreetMap()
{
Id(x => x.Id, m => m.Generator(Generators.Identity));
Property(x => x.Name, m => m.Length(SqlClientDriver.MaxSizeForLengthLimitedString + 1));

Bag(x => x.Districts, c =>
{
c.Key(k => k.NotNullable(true));
c.Cascade(Cascade.All);
c.Inverse(true);
}, r => r.ManyToMany());
}
}

And here are the tables generated by Hbm2dll tool:

3 comments:

  1. Hi,
    Nice addition to the excellent mapping-by-code, but your implementation of the IsPersistentProperty delegate causes it to incorrectly attempt mapping of properties where the get or set is missing, i.e. calculated properties. To me, a better solution was this:

    IsPersistentProperty((m, d) =>
    {
    var propertyInfo = m as PropertyInfo;

    bool rv = true;
    if (propertyInfo != null)
    // Re-instate convention that we only map
    // to database if property is read/write
    rv = (propertyInfo.CanRead && propertyInfo.CanWrite);
    rv = rv && !_ignoredMembers.Contains(m);
    return rv;
    });

    /David

    ReplyDelete
  2. The only thing I would add is the default configuration for Id property to be native.

    ReplyDelete
  3. I am trying to get an entirely convention based mapping going so I only need to define classmaps for special custom cases. I was doing OK until I tried to do Many to many's. I specified a lambda for IsManyToMany and BeforeMaps for Set and ManyToMany however it does not seem to work - http://stackoverflow.com/questions/13330554/nhibernate-bycode-mapping-how-to-map-manytomany-entirely-by-convention.

    I seems I still must define a class mapping and Set for the property. I can leave everything out of the set definition except the relationship definition which MUST specify the column (same as what the BeforeManyToMany event is doing). If I dont it is treated like a OneToMany. So it's quirky... and seems like a bug. Last theory I have is to specify more of the IsXXX methods, at least for the other collection types, perhaps there is some ambiguity there i need to resolve.

    ReplyDelete