1
votes

We are using currently NHibernate to support a flexible entity model. The system fields are stored in one table and the dynamic fields are stored in an extension table. The tables shared the same primary keys.

We use a JOIN component to map a dictionary in the extension table. The JOIN component is mapped as optional but the following bug occurss:

  1. Create a new entity with no dynamic attributes. Hibernate stores an empty record in the second table - all columns are stored as null except for the foreign/primary key.

  2. Load the created record and set a dynamic field.

  3. Save the updated record will result in an error. NHibernate will issue a new insert instead of an update statement.

There is currently a bug in Nhibernate which may be related to this: https://nhibernate.jira.com/browse/NH-2876.

I'm wondering if currently there is a solution to this NHibernate Core bug or if anyone has a workaround to it?

Thank you!

2

2 Answers

2
votes

I was able to get the code to work by using mapping to a function that points to the id on the component:

<hibernate-mapping
xmlns="urn:nhibernate-mapping-2.2"
assembly="NHibernate.Test"
namespace="NHibernate.Test.NHSpecificTest.NH2876"
default-lazy="false">

<class name="Customer">
    <id name="Id">
        <generator class="guid" />
    </id>
    <property name="Name" column="Name" />
    <join table="CustomerData" optional="true">
        <key column="CustomerId" unique="true" />
        <component name="ExtendedData">
            <property name="CustomerId" formula="[CustomerId]" insert="false" update="false" />
            <property name="SomeData" />
        </component>
    </join>
</class>

This also requires that the CustomerId property be added to the component class.

The reason that this happens is that when NHibernate pulls the existing record it does not populate the the related component if there are no non-null values. So when it checks the "old values" when calling UpdateOrInsert in AbstractEntityPersister it sees just a null object for the component attribute and thus thinks it is an insert. This is the same behavior when there is no record in the table. By adding the mapped formula field using the key field the component attribute has a value in the "old fields" and it properly knows that it needs to perform an update.

Here is a repo with the additional unit tests and the working solution (to be used with the NHibernate.Test project) based on the original unit tests found here - https://nhibernate.jira.com/browse/NH-2876:

https://github.com/kfehribach/NH2876

Kent

1
votes

Few lines about dynamic column mapping could be found here: NHibernate Dynamic Columns Number. And really in this scenario this could be the way.

<join table="ElemntValues" >

  <key column="ElementId" />
  <dynamic-component>
    ...

But honestly, I ended up with a bit different solution.

So this is my C# entity (e.g. Contact) with some MuchMore fields

IDictionary _muchMore;

// se the property name ... used later for mapping
public virtual IDictionary MuchMore
{
    get { return _muchMore?? (_muchMore= new Hashtable()); }
    set { _muchMore= value; }
}

Now, there is unstopable amount of mapping possiblities with NHibernate

4.4. Dynamic models

Persistent entities don't necessarily have to be represented as POCO classes at runtime. NHibernate also supports dynamic models (using Dictionaries of Dictionarys at runtime) . With this approach, you don't write persistent classes, only mapping files.

...

First, in the mapping file, an entity-name has to be declared instead of (or in addition to) a class name

That's cool. Becuase now we can create brand virtual mapping of ContactMuchMore entity, which will reperesent the joined table:

<class entity-name="ContactMuchMore"  table="Contact_MuchMore_table"
       dynamic-insert="true" dynamic-update="true" batch-size="25" >

  <id name="ID" column="Contact_ID" type="int">
    <generator class="foreign">
      <param name="property">Parent</param>
    </generator>
  </id>
  <one-to-one name="Parent" class="Contact" constrained="true" />

Unbelievable... we with NHibenrate do have mapped entity - virtual one. It has dependency on Contact.. so we can use the real contact as an ID generator and one-to-one mapping

And here is the Contact.cs mapping:

<class name="Contact"  ... >
  ...
  // here we mapp the property name
  // to our virtual entity 
  <one-to-one name="MuchMore" 
              entity-name="ContactMuchMore"  
              cascade="all" />

There are some dark sides of one-to-one (both tables are loaded always) but could be solved with projections. With cascade all - no issues with persisting values into any of these tables (contact or contact_hasmore).

My answer is describing xml mapping (not fluent). But I guess that if the concept is clear, we can use similar stuff in fluent, or exceptionally - use xml.