Does NHibernate update version on select?

591 Views Asked by At

I have a very simple mapping like:

public abstract class EntityMap<TEnt, TId, TDto> : ClassMap<TEnt>
    where TEnt: Entity<TId, TDto> 
    where TDto : DataTransferObject<TDto, TId>
{
    protected EntityMap()
    {
        Id(x => x.Id).GeneratedBy.GuidComb();
        Map(x => x.Name).Not.Nullable().Unique().Length(255);
        Version(x => x.Version);
        SelectBeforeUpdate();
    } 

I use this mapping to perform a test like:

    [TestMethod]
    public void ThatVersionDoesNotChangedAfterOnlyAReadAction()
    {
        var services = BrandServices.WithDto(BrandTestFixtures.GetDto()).Get();
        Assert.AreEqual(1, services.Version);
        Context.CurrentSession().Transaction.Commit();
        Context.CurrentSession().Transaction.Begin();
        var brand = BrandServices.Brands.Single(x => x.Name == BrandTestFixtures.GetDto().Name);
        Assert.AreEqual(1, brand.Version);
    }

So, in this test I create an object mapped to the entity map and this object is inserted into the database. Semantically, there should only be 1 version of the object. However, what happens is that upon commit the inserted object is immediately updated with a new version. Moreover, when I retrieve the object again to check the version, the version is incremented again. The expected version should be 1, the version of the test brand is 3 but in the database the version is 4!?!?!

I tried various flavors of mapping options. The most puzzling part to me is that version is incremented at the moment the object is retrieved from the database. In my full application with multiple bidirectional relations this leads to severe problems. Is there a way to modify this behavior, or do I have to resort to a home made versioning mechanism?

-- statement #1
select brand0_.Id      as Id0_,
       brand0_.Version as Version0_,
       brand0_.Name    as Name0_
from   [Brand] brand0_

-- statement #2
select brand0_.Id      as Id0_,
       brand0_.Version as Version0_,
       brand0_.Name    as Name0_
from   [Brand] brand0_

-- statement #3
INSERT INTO [Brand]
           (Version,
            Name,
            Id)
VALUES     (1 /* @p0_0 */,
            'Dynatra' /* @p1_0 */,
            '8ca15020-2e23-4ace-adff-9f4800b706d5' /* @p2_0 */)

-- statement #4
UPDATE [Brand]
SET    Version = 2 /* @p0 */,
       Name = 'Dynatra' /* @p1 */
WHERE  Id = '8ca15020-2e23-4ace-adff-9f4800b706d5' /* @p2 */
       AND Version = 1 /* @p3 */

-- statement #5
commit transaction

-- statement #6
begin transaction with isolation level: Unspecified

-- statement #7
UPDATE [Brand]
SET    Version = 3 /* @p0 */,
       Name = 'Dynatra' /* @p1 */
WHERE  Id = '8ca15020-2e23-4ace-adff-9f4800b706d5' /* @p2 */
       AND Version = 2 /* @p3 */

-- statement #8
select brand0_.Id      as Id0_,
       brand0_.Version as Version0_,
       brand0_.Name    as Name0_
from   [Brand] brand0_

-- statement #9
UPDATE [Brand]
SET    Version = 4 /* @p0 */,
       Name = 'Dynatra' /* @p1 */
WHERE  Id = '8ca15020-2e23-4ace-adff-9f4800b706d5' /* @p2 */
       AND Version = 3 /* @p3 */
2

There are 2 best solutions below

1
On BEST ANSWER

Ok, I found what was wrong. In the class I was testing I had mapped a collection and I was using 'lazy instantiation' of that collection like:

    public virtual ISet<Product> Products
    {
        get  { return _products ?? (_products = new HashedSet<Product>(); }
        protected set { _products = value;}
    }

Turns out that this is a no go with NH. This thing with NH replacing your collection instance with it's own implementation has caused me a lot of headaches, being a NH newbie. Hope, that other 'newbies' can learn from this. If you want to know everything you can do wrong on this subject, read my posts.

0
On

I found also one... The problem was there:

public enum ContractValidationImportance
{
    Low,
    Neutral,
    Intermediate,
    High
}

...entity.cs
.....

    private ContractValidationImportance _validationImportance;
    public ContractValidationImportance ValidationImportance
    {
        get { return _validationImportance; }
        set { _validationImportance = value; }
    }

... entity.hbm.xml

<property name="ValidationImportance" column="ValidationImportance" type="Int32" />