Extend/Modify NHibernate classes at runtime

2k views Asked by At

Apologies if there's an on point answer already out there but I haven't found it. I'm using NH3 and I've got a use case where I want to add a Set onto any entity who's class implements a specific interface. I have a configuration builder class so I can make these changes before creating the session factory.

Given this reduced example:

public class Person : IHasExtraItems
{
    public Person()
    {
        this.ExtraItems = new HashSet<ExtraItem>();
    }
    public virtual Guid Id { get; set; }
    public virtual string Name { get; set; }
    public virtual DateTime Birthdate { get; set; }
    public virtual ICollection<ExtraItem> ExtraItems { get; protected set; }
}

public class ExtraItem
{
    public virtual Guid Id { get; set; }
}

And this example mapping:

  <class name="Person">
    <id name="Id">
      <generator class="guid"/>
    </id>
    <property name="Name"/>
    <property name="Birthdate"/>
    <set name="Extra" table="PersonExtraItems" cascade="all">
      <key column="PersonId"/>
      <many-to-many column="ExtraItemId" class="ExtraItem" unique="true" />
    </set>
  </class>

Since I want to be able to apply this functionality to many classes transparently -- just by implementing the interface -- I don't want to put the "ExtraItem" in the mapping. Instead I want to add it at runtime. So if I remove the property from the xml mapping, how can I add this at runtime?

There's this describing exactly the type of change I'm trying to make: http://ayende.com/Blog/archive/2008/05/01/Dynamic-Mapping-with-NHibernate.aspx

But it doesn't map a many to many set, and my feeble brain has been unable to decipher the in-memory mapping representation nhibernate uses to create the effect. This is the closest I've come, based on trying to get properties visible in the debugger to match

foreach (var cls in cfg.ClassMappings)
{
    if (typeof(IHasExtraItems).IsAssignableFrom(cls.MappedClass))
    {
        NHibernate.Mapping.Property property = new NHibernate.Mapping.Property();
        NHibernate.Mapping.Set value = new NHibernate.Mapping.Set(cls);
        value.Role = cls.EntityName + ".ExtraItems";
        value.IsGeneric = true;
        var table = new Table();
        table.Name = cls.MappedClass.Name + "ExtraItems";
        value.CollectionTable = table;
        value.GenericArguments = new Type[] { typeof(ExtraItem) };
        value.IsOptimisticLocked = true;
        value.IsLazy = true;
        mappings.AddCollection(value);
        property.Value = value;

        property.Name = "ExtraItems";
        property.PersistentClass = cls;
        property.Cascade = "all";
        cls.AddProperty(property);
    }
}

In a test, this produces a runtime error because the key is null, however the XML mapped version works, and looks more or less identical at the time I'm making the changes.

Bonus points: I want a many-to-many specifically because I want a joined table. This lets me map an entity to the extending data with true foreign keys for performance. The ExtraItems should really be a value type rather than a true entity, but I couldn't figure out how to map that, even in XML.

Bonus points, part 2: Could I do this with confORM? I don't want to switch all my existing mappings to confORM, and I couldn't find an example of mixing confORM with conventional XML mappings, nevermind modifying existing mappings. Fluent would be another option, but I'm using NH3 and I don't think Fluent supports that yet.

Thanks in advance!

EDIT I'm fairly certain that my issue is that I'm not defining the elements of the set. However, I can't discern how to define the elements of the set properly.

1

There are 1 answers

0
jonnii On BEST ANSWER

There are builds of fluent for NH3, so you can use it (I do), and if you are using fluent you can very easily do this with a convention. You mention that in your bonus points section, so maybe you should give it another look?