Search code examples
c#genericsnhibernatefluent-nhibernate

NHibernate - map a property of a runtime-defined type


Alright, so I need to create a couple of tables, which need to be almost identical except for one field.

My model would roughly look like this:

class HouseGeometryModel
{
   public virtual int Id { get; set; }
   public virtual string Name { get; set; }
   //More fields...

   public virtual HouseAttributes Attributes { get; set; }
}

class DungeonGeometryModel
{
   public virtual int Id { get; set; }
   public virtual string Name { get; set; }
   //More fields, all identical to HouseGeometryModel...

   public virtual DungeonAttributes Attributes { get; set; }
}

class FortressGeometryModel
{
   public virtual int Id { get; set; }
   public virtual string Name { get; set; }
   //More fields, all identical to HouseGeometryModel...

   public virtual FortressAttributes Attributes { get; set; }
}

//More models...

So, basically only the Attributes property differs between all the models here, so I thought there could be a way to unify everything into a single (generic?) class.

I could come up with two ways to implement this:

  1. Make a generic class GeometryModel<TAttributes> which would look like:

    class GeometryModel<TAttributes>
    {
        public virtual int Id { get; set; }
        public virtual string Name { get; set; }
        //More fields...
    
        public virtual TAttributes Attributes { get; set; }
    }
    

    The problem with this is that I fail to specify a fluent mapping. The mapping should also become generic this way (to implement ClassMap<GeometryModel<TAttributes>>) and therefore it would be impossible to instantiate it with NHibernate.

  2. Make the Attributes property dynamic. It doesn't work either because NHibernate treats dynamic properties as object when creating a ClassMap<>.

Is there any solution to this?


Solution

  • I ended up doing it generic way with a runtime ClassMap<> binding.

    My model looks like this:

    class GeometryModel<TAttributes>
    {
        public virtual int Id { get; set; }
        public virtual string Name { get; set; }
        //More fields...
    
        public virtual TAttributes Attributes { get; set; }
    }
    

    My mappings look like this:

    class GeometryModelMap<TAttributes> : ClassMap<GeometryModel<TAttributes>>
    {
        public GeometryModelMap()
        {
            Id(t => t.Id).GeneratedBy.Increment();
            Map(t => t.Name);
            //More mappings...
            References(t => t.Attributes);
        }
    }
    

    I wrote the following extension method:

    private static FluentMappingsContainer AddGenericMappings(this FluentMappingsContainer container, Type genericType, IEnumerable<Type> genericArgs)
    {
        foreach (var arg in genericArgs)
        {
            var newType = genericType.MakeGenericType(arg);
            container.Add(newType);
        }
        return container;
    }
    

    And I use it like this:

    private static ISessionFactory CreateSessionFactory(string path)
    {
        return Fluently.Configure()
                       .Database(SQLiteConfiguration.Standard.UsingFile(path))
                       .Mappings(m => m.FluentMappings
                       .AddFromAssembly(Assembly.GetExecutingAssembly())
                       .AddGenericMappings(typeof(GeometryModelMap<>), new[] { typeof(HouseAttributes), typeof(DungeonAttributes), typeof(FortressAttributes) }  )
                )
                .ExposeConfiguration(config => BuildSchema(config, path))
                .BuildSessionFactory();
    }