views:

79

answers:

1

I am trying to use NHibernate to map a legacy database which uses a decimal(9,0) (~32 bit integer) "version property" (for optimistic locking of each row).

The fields are declared nullable in the database, and in section 5.1.7 of the NHibernate reference manual it states:

Version numbers may be of type Int64, Int32, Int16, Ticks, Timestamp, or TimeSpan (or their nullable counterparts in .NET 2.0).

... so I mapped them as int? version properties.

My mapping document looks similar to this:

<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping namespace="Model" assembly="Model" xmlns="urn:nhibernate-mapping-2.2">
    <class name="Bar" lazy="true" table="`BAR`" schema="`dbo`">
        <id name="ID" access="property" column="`ID`">
            <generator class="assigned" />
        </id>
        <version name="Version" column="`VERSION`" type="int?" />
        <!-- ... etc ... -->
    </class>
</hibernate-mapping>

However, at runtime, this fails miserably with an exception similar to:

failed: NHibernate.MappingException : Could not compile the mapping document: Model.Mappings.Bar.hbm.xml
  ----> NHibernate.MappingException : Could not determine type for: Model.int?,  for columns: NHibernate.Mapping.Column(VERSION)
    at NHibernate.Cfg.Configuration.LogAndThrow(Exception exception)
    at NHibernate.Cfg.Configuration.AddValidatedDocument(NamedXmlDocument doc)
    at NHibernate.Cfg.Configuration.ProcessMappingsQueue()
    at NHibernate.Cfg.Configuration.AddDocumentThroughQueue(NamedXmlDocument document)
    at NHibernate.Cfg.Configuration.AddXmlReader(XmlReader hbmReader, String name)
    at NHibernate.Cfg.Configuration.AddInputStream(Stream xmlInputStream, String name)
    at NHibernate.Cfg.Configuration.AddResource(String path, Assembly assembly)
    at NHibernate.Cfg.Configuration.AddAssembly(Assembly assembly)
    Core\DAOUtils.cs(33,0): at Core.DAOUtils.OpenSession()
    test\cs\FooTest.cs(65,0): at DataModel.Tests.FooTest.TestSelectStar()
    --MappingException
    at NHibernate.Mapping.SimpleValue.get_Type()
    at NHibernate.Cfg.XmlHbmBinding.RootClassBinder.BindProperty(HbmVersion versionSchema, Property property, IDictionary`2 inheritedMetas)
    at NHibernate.Cfg.XmlHbmBinding.RootClassBinder.BindVersion(HbmVersion versionSchema, PersistentClass rootClass, Table table, IDictionary`2 inheritedMetas)
    at NHibernate.Cfg.XmlHbmBinding.RootClassBinder.Bind(XmlNode node, HbmClass classSchema, IDictionary`2 inheritedMetas)
    at NHibernate.Cfg.XmlHbmBinding.MappingRootBinder.AddRootClasses(XmlNode parentNode, IDictionary`2 inheritedMetas)
    at NHibernate.Cfg.XmlHbmBinding.MappingRootBinder.Bind(XmlNode node)
    at NHibernate.Cfg.Configuration.AddValidatedDocument(NamedXmlDocument doc)

Is there some trick to correctly using version properties with NHibernate, or am I doing something wrong?

One odd thing to me is that NHibernate seems to be appending my assembly/namespace name to the beginning of the int? type, which seems wrong.

Edit:

After changing the mapping files to use int instead of int? I am now hitting the following exception:

System.InvalidCastException : Unable to cast object of type 'NHibernate.Type.DecimalType' to type 'NHibernate.Type.IVersionType'.
    at NHibernate.Tuple.PropertyFactory.BuildVersionProperty(Property property, Boolean lazyAvailable)
    at NHibernate.Tuple.Entity.EntityMetamodel..ctor(PersistentClass persistentClass, ISessionFactoryImplementor sessionFactory)
    at NHibernate.Persister.Entity.AbstractEntityPersister..ctor(PersistentClass persistentClass, ICacheConcurrencyStrategy cache, ISessionFactoryImplementor factory)
    at NHibernate.Persister.Entity.SingleTableEntityPersister..ctor(PersistentClass persistentClass, ICacheConcurrencyStrategy cache, ISessionFactoryImplementor factory, IMapping mapping)
    at NHibernate.Persister.PersisterFactory.CreateClassPersister(PersistentClass model, ICacheConcurrencyStrategy cache, ISessionFactoryImplementor factory, IMapping cfg)
    at NHibernate.Impl.SessionFactoryImpl..ctor(Configuration cfg, IMapping mapping, Settings settings, EventListeners listeners)
    at NHibernate.Cfg.Configuration.BuildSessionFactory()

Unfortunately, NHibernate isn't giving me a very detailed error message here (it would be nice to know which class out of the hundreds of classes is throwing this), and I haven't been able to convince log4net to work, so I'm at a loss for the moment.

Edit #2:

To summarize the "tricks":

  • Assuming integer version properties, (and even if they are declared decimal in the database) make sure all version properties are declared as int in the mapping XML. (though it should work if int? is declared in the POCO)
  • Make sure no version properties are set as a decimal type (either in the POCOs or the mapping XML)
+1  A: 

You shouldn't include the '?' in the mapping - instead write type="Int32". If your property in your POCO is nullable it should work out fine. (At least it does for properties - I haven't tried having a nullable version column...).

Goblin
Thanks; very helpful. That got me past the original exception. Now I'm hitting a different one. (question updated)
Mike
I think I misread your question... are you using decimal as the type in the version-column? Type decimal is not supported as a version column (hence the - cannot cast to IVersionType-exception).
Goblin
Yes and no... yes, in that I am mapping a legacy database whose version properties are defined as "decimal(9,0)". (I can't change the schema.) No, in that I am trying to map them all as 32-bit integers. Not sure why this wouldn't work, except that if I get version numbers above 999,999,999 the database may complain.
Mike
The problem is that NHibernate when reading from the Database gets a decimal (as it should) and a decimal cannot be a version-column. NHibernate only supports whole numbers in the database column as well as the property in your POCO.
Goblin
Sadly, I believe you need to handle versioning manually by putting it into a normal decimal property and then handling the concurrency in an interceptor.
Goblin
@Goblin, are you certain? Because it looks like NHibernate is throwing this exception before it even tries to read from the database; it's only opening the session. (I guess maybe by this time it's already talking to the database and comparing notes on its idea of the schema vs. the real database...)
Mike
You are correct - I didn't look closely at the stacktrace. It is indeed when creating the sessionfactory - what is the type of the property in the POCO? (NHibernate creates a proxy of each mapped POCO to check that it is possible when creating the sessionfactory.
Goblin
@Goblin, on my first pass, all the POCO properties were type "int?". Since I had lingering doubts about this not matching the XML mappings, I since changed them to "int". (no change; same error)
Mike
Hmm... from the error - it seems that one of your POCOs has a decimal property set as the version property... I'm about fresh out of ideas if that isn't the case.
Goblin
Thanks. Actually, it wasn't quite that. I had the wrong column set to the version property in one of the mapping .xml files, and it happened to be a decimal column. After I fixed that, it worked! Thanks for all your help.
Mike