NHibernate 3.2 By Code ClassMapping for Version Property

What is the correct way to map an timestamp column in an SQL Server 2008 database using NHibernate's new code based mappings?

I have the property in my class defined as byte[] and I'm using the following mapping in my ClassMapping file:

Version(x => x.RowVersion, mapping =>
   mapping.Generated(VersionGeneration.Always));

However, NHibernate is expecting an integer based on this mapping (throws an exception on inserts). If I explicitly specify the mapping type as byte[], I get an exception stating: "System.ArgumentOutOfRangeException: Expected type implementing IUserVersionType Parameter name: persistentType".

What is the proper way to map an auto updating timestamp column with the new NHibernate code based mappings?

---EDIT

I think I have it narrowed down that I need to set the Type on the mapping to BinaryType (an NHibernate Type that implements IVersionType), but BinaryType doesn't have a public constructor...I think I'm out of ideas.

Answers


We also are using byte[] Version { get; } for version implementation.

Here is out mappings:

Version(x => x.Version)
            .Nullable()
            .CustomSqlType("timestamp")
            .Generated.Always()
            ;

Also for more details please see this link


If you grab the NHib source code, there's a class in a test project that will help you with what you need: NHibernate.Test.VersionTest.Db.MsSQL.BinaryTimestamp. Basically, you have to give it a custom type it can use to convert the value. Be default NHib expects the value to be an int (look at seciont 5.1.7 of the nhib docs). If you use an int/bigint as your version column, you will not need a custom type.

The custom class (lifted from NHib source code):

public class BinaryTimestamp : IUserVersionType
{
    #region IUserVersionType Members

    public object Next(object current, ISessionImplementor session)
    {
        return current;
    }

    public object Seed(ISessionImplementor session)
    {
        return new byte[8];
    }

    public object Assemble(object cached, object owner)
    {
        return DeepCopy(cached);
    }

    public object DeepCopy(object value)
    {
        return value;
    }

    public object Disassemble(object value)
    {
        return DeepCopy(value);
    }

    public int GetHashCode(object x)
    {
        return x.GetHashCode();
    }

    public bool IsMutable
    {
        get { return false; }
    }

    public object NullSafeGet(IDataReader rs, string[] names, object owner)
    {
        return rs.GetValue(rs.GetOrdinal(names[0]));
    }

    public void NullSafeSet(IDbCommand cmd, object value, int index)
    {
        NHibernateUtil.Binary.NullSafeSet(cmd, value, index);
    }

    public object Replace(object original, object target, object owner)
    {
        return original;
    }

    public System.Type ReturnedType
    {
        get { return typeof(byte[]); }
    }

    public SqlType[] SqlTypes
    {
        get { return new[] { new SqlType(DbType.Binary, 8) }; }
    }

    public int Compare(object x, object y)
    {
        var xbytes = (byte[])x;
        var ybytes = (byte[])y;
        return CompareValues(xbytes, ybytes);
    }

    bool IUserType.Equals(object x, object y)
    {
        return (x == y);
    }

    #endregion

    private static int CompareValues(byte[] x, byte[] y)
    {
        if (x.Length < y.Length)
        {
            return -1;
        }
        if (x.Length > y.Length)
        {
            return 1;
        }
        for (int i = 0; i < x.Length; i++)
        {
            if (x[i] < y[i])
            {
                return -1;
            }
            if (x[i] > y[i])
            {
                return 1;
            }
        }
        return 0;
    }

    public static bool Equals(byte[] x, byte[] y)
    {
        return CompareValues(x, y) == 0;
    }
}

A sample mapping using that class:

public class Car
{
    public virtual long CarId { get; set; }
    public virtual string Name { get; set; }
    public virtual byte[] LastModified { get; set; }

    public override string ToString()
    {
        return string.Format("Id: {0}, Name: {1}, Last Modified: {2}", CarId, Name, LastModified);
    }
}

public class CarMap : ClassMapping<Car>
{
    public CarMap()
    {
        Table("Cars");

        Id(car => car.CarId, mapper => mapper.Generator(Generators.Identity));
        Property(car => car.Name);
        Version(car => car.LastModified, mapper =>
                                             {
                                                 mapper.Generated(VersionGeneration.Always);
                                                 mapper.Type<BinaryTimestamp>();
                                             });
    }
}

In the case of a ConventionModelMapper I ended up using the following..

ConventionModelMapper mapper = new ConventionModelMapper();

//...other mappings

mapper.Class<Entity>(map => map.Version(e => e.Revision, m =>
                {
                    m.Generated(VersionGeneration.Always);
                    m.UnsavedValue(null);
                    m.Type(new BinaryBlobType());
                    m.Column(c =>
                        {
                            c.SqlType("timestamp");
                            c.NotNullable(false);
                        });
                }));

Need Your Help

Capture Video Length with FFMPEG and Paperclip

amazon-s3 ffmpeg paperclip

I am trying to get the video length during the video upload. We are using SWFUpload to upload the file. Then paperclip to store it on S3. Our current process is loading the file into memory and ...

Retry a Bash command with timeout

bash loops timeout

How to retry a bash command until its status is ok or until a timeout is reached?

recurse through a javascript object adding extra array members if (array.length < 2)

javascript arrays object recursion

With a nested object such as the 'root' object below; How can I add a 'dummy' object to those arrays where the length of the array is less than 2.