views:

558

answers:

3

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?

A: 
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).

eyston
Ok, fair enough... I'm following so far. But how does your 'dt' instance get back to the domain model?
codekaizen
dt = component, just casted to DateTime. I could replace it everywhere with ((DateTime)component). The component is what is actually given back to the domain model.
eyston
I'm not sure if there is documentation of this stuff. I don't even remember it and had to look at the context in my code. I think I had to look at the example usually provided with NHibernate (twostrings I think?) and learn from that (and obviously forget it once I was done!)
eyston
I'm just baffled on how you could get this to work, since DateTime is a ValueType. As soon as you unbox it to the local 'dt' you are making a copy of it, and all changes are lost as soon as the method exits. That's the crux of my problem with the method SetPropertyValue. There's no way to get a ValueType out of it.
codekaizen
+1  A: 

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.

Keith Klein
+2  A: 

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
Stefan Steinegger