Archives

Archives / 2008 / October
  • Extension Method Competition

    I plan on holding an extension method competition, basically the idea is that you submit your favourite most useful/elegant/sexy extension method and these will be judged by a selection of judges (to be picked) and there will also be some form of reward for the winner.

  • Set the value of a version column in NHibernate manually

    My first post in a long time :), look forward to more from me.

    I am currently working on a project and for the first time am using NHibernate, I must say compared to LINQ to Sql I am in love, NHibernate ROCKS! An issue I was having was that I have a version column in my database defined as an integer and then in my NHibernate mapping files have defined a <version> element to map to this column. Now one issue is that in my service methods I do something like this:

    public void Update(MyDTO dto) {

    // Select the item.
    var item = this.repository.SelectById(dto.Id);

    // Map values from DTO to model.
    item.Name = dto.Name;
    item.Version = dto.Version;

    // Call update
    this.repository.Update(item);

    }

    Because I am passing in a DTO object from my UI layer I need to first load the entity from the repository by Id. Then I map across my properties including the version, which in this case was populated from a hidden field on the UI into the DTO. Next I call update, now my initial assumption was that this would work as I excepted for optimistic locking. I thought that for example say the current version in the DB was 2, and the item we are updating is version 1, so it would load the item with a version = 2.

    Then we override the version to 1 and upon update I would expect it to use this version 1 for the optimistic check. WRONG it from what I understand uses the version value which is cached from a copy of the item upon loading it. If you look at the documentation there are 3 types approaches to optimistic concurrency.



    10.4.1. Long session with automatic versioning

    A single ISession instance and its persistent instances are used for the whole application transaction.

    The ISession uses optimistic locking with versioning to ensure that many database transactions appear to the application as a single logical application transaction. The ISession is disconnected from any underlying ADO.NET connection when waiting for user interaction. This approach is the most efficient in terms of database access. The application need not concern itself with version checking or with reattaching detached instances.

    // foo is an instance loaded earlier by the Session
    session.Reconnect();
    transaction = session.BeginTransaction();
    foo.Property = "bar";
    session.Flush();
    transaction.Commit();
    session.Disconnect();
    The foo object still knows which ISession it was loaded it. As soon as the ISession has an ADO.NET connection, we commit the changes to the object.

    This pattern is problematic if our ISession is too big to be stored during user think time, e.g. an HttpSession should be kept as small as possible. As the ISession is also the (mandatory) first-level cache and contains all loaded objects, we can propably use this strategy only for a few request/response cycles. This is indeed recommended, as the ISession will soon also have stale data.

    10.4.2. Many sessions with automatic versioning

    Each interaction with the persistent store occurs in a new ISession. However, the same persistent instances are reused for each interaction with the database. The application manipulates the state of detached instances originally loaded in another ISession and then "reassociates" them using ISession.Update() or ISession.SaveOrUpdate().

    // foo is an instance loaded by a previous Session
    foo.Property = "bar";
    session = factory.OpenSession();
    transaction = session.BeginTransaction();
    session.SaveOrUpdate(foo);
    session.Flush();
    transaction.Commit();
    session.Close();
    You may also call Lock() instead of Update() and use LockMode.Read (performing a version check, bypassing all caches) if you are sure that the object has not been modified.

    10.4.3. Application version checking

    Each interaction with the database occurs in a new ISession that reloads all persistent instances from the database before manipulating them. This approach forces the application to carry out its own version checking to ensure application transaction isolation. (Of course, NHibernate will still update version numbers for you.) This approach is the least efficient in terms of database access.

    // foo is an instance loaded by a previous Session
    session = factory.OpenSession();
    transaction = session.BeginTransaction();
    int oldVersion = foo.Version;
    session.Load( foo, foo.Key );
    if ( oldVersion != foo.Version ) throw new StaleObjectStateException();
    foo.Property = "bar";
    session.Flush();
    transaction.Commit();
    session.close();
    Of course, if you are operating in a low-data-concurrency environment and don't require version checking, you may use this approach and just skip the version check.

    -------------------

    Now if you look at the above it is clear that I am using the third scenario hence why I as having the issues, if I kept the session around for the life of the transaction i.e store in cache when you display the page and then use this session again for the save, the version would have been handled automagically same being if you had the full object and call an Update with it on a new Session.

    But in my case I do not have the full object and am only mapping over a sub set of parameters from my DTO, so I need to first load the item by ID from the session and then map across my parameters, finally calling an update, this is where the issue is. To fix this the solution which I found on the Java forums and adopted was to create an interceptor which would handle the OnFlushDirty method. In here I will compare the Version of the entity being flushed to the Version of the item in the database, this will do what I would like and allows me to set the Version manually.

    The main issue is that there is 1 extra DB call to get the version, but in my case this is minimal. The code for the interceptor is below, it doesn't seem to have any issues as yet but will undergo more testing as time goes by. The solution/ideas came from here: http://forum.hibernate.org/viewtopic.php?t=977889, and was changed to work.


    public class NHInterceptor : EmptyInterceptor {
    private ISession _session;

    public override void SetSession(ISession session) {
    this._session = session;

    base.SetSession(session);
    }

    public override bool OnFlushDirty(object entity, object id, object[] currentState, object[] previousState, string[] propertyNames, NHibernate.Type.IType[] types) {
    ISessionImplementor sessimpl = _session.GetSessionImplementation();
    IEntityPersister persister = sessimpl.GetEntityPersister(entity);
    EntityMode mode = _session.GetSessionImplementation().EntityMode;

    if(persister.IsVersioned) {
    object version = persister.GetVersion(entity, mode);
    object currentVersion = persister.GetCurrentVersion(id, sessimpl);

    if (!persister.VersionType.IsEqual(currentVersion, version))
    throw new StaleObjectStateException(persister.EntityName, id);
    }

    return base.OnFlushDirty(entity, id, currentState, previousState, propertyNames, types);
    }

    }



    Maybe you find this useful.


    Thanks
    Stefan