Question

I have a domain model object which has properties of type System.DateTimeOffset. I'm using a database which doesn't support this type natively, so I'm planning to store it using a column of type 'datetime' and one of type 'smallint'.

I've dug around on how to map this using NHibernate components, and found that it could work using an ICompositeUserType instance. However, upon implementing the interface, I've come across the method called "SetPropertyValue" which ostensibly sets a property within the type. Since DateTimeOffset is a System.ValueType, just setting a property like this won't work since it is immutable (at least, without using some reflection or unsafe code, which I'd like to avoid). Since the instance parameter on SetPropertyValue isn't 'ref' how does one use ValueType instances as components in NHibernate?

Was it helpful?

Solution

Make the user type immutable by returning false in IsMutable and just throw an exception in SetPropertyValue.

I have something similar, but with an own datatype instead of DateTimeOffset. I just adapted the code for you. It stores the date as UTC time and the offset as TimeSpan (stores Ticks. Of course you don't need this resolution. But, you should not store whole hours for time zones, there are time zones offsets with fractions of hours!! And TimeSpan is build in an works out of the box.)

public class DateTimeOffsetUserType : ICompositeUserType
{
    public bool IsMutable
    {
        get { return false; }
    }

    public void SetPropertyValue(object component, int property, object value)
    {
        throw new InvalidOperationException("Immutable, SetPropertyValue is not allowed");
    }

    public object NullSafeGet(System.Data.IDataReader dr, string[] names, NHibernate.Engine.ISessionImplementor session, object owner)
    {
        if (dr == null)
        {
            return null;
        }

        DateTime? utcTime;
        TimeSpan? offset;

        utcTime = (DateTime?)PropertyTypes[0].NullSafeGet(dr, names[0], session, owner);
        offset = (TimeSpan?)PropertyTypes[1].NullSafeGet(dr, names[1], session, owner);

        if (utcTime == null || offset == null) return null;
        return new DateTimeOffset(utcTime.Value, offset.Value);
    }

    public void NullSafeSet(System.Data.IDbCommand cmd, object value, int index, NHibernate.Engine.ISessionImplementor session)
    {
        if (value == null)
        {
            NHibernateUtil.Timestamp.NullSafeSet(cmd, null, index);
            NHibernateUtil.TimeSpan.NullSafeSet(cmd, null, index + 1);
        }
        else
        {
            DateTimeOffset dateTimeOffset = (DateTimeOffset)value;

            PropertyTypes[0].NullSafeSet(cmd, dateTimeOffset.UtcDateTime, index, session);
            PropertyTypes[1].NullSafeSet(cmd, dateTimeOffset.Offset, index + 1, session);
        }

    }

    // other methods

OTHER TIPS

It's my understanding that if the type you're mapping with and ICompositeUserType is immutable, SetPropertyValue() should do nothing at all, or even throw an exception, since nhibernate shouldn't be calling it.

public void SetPropertyValue(object component, int property, object value)

I have code that implements DateTime from two int fields.

The code essentially is a switch on property (0 being date, 1 being time, in my case) and at the end of each case statement the component object is reassigned a new DateTime instance.

property = 0 (Date):

// code to calculate year, month, day from object value
DateTime dt = (DateTime)component;
dt = new DateTime(year, month, day, dt.Hour, dt.Minute, dt.Second);

property = 1 (Time):

// code to calculate hours, minutes, seconds from object value
DateTime dt = (DateTime)component;
dt = new DateTime(dt.Year, dt.Month, dt.Day, hours, minutes, seconds);

No idea if this is good / bad, but it works for me (aka I have no problem changing what component points to, ref or not).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top