Increasing DateTime storage precision in Nhibernate (and Castle ActiveRecord)

I was a little bit upset when I discovered that persisting a DateTime object with NHibernate in SQL Server 2005 only stored values to second precision. This was unexpected as I thought that I would not lose that much (any?) information from my DateTime instance.

So I went to the internet with problem in hand and tried to find a nice little solution for me. It didn't happen definitively so I though I might write about my solution here. I think that may be what blogs are for.

Implementing NHibernate.UserTypes.IUserType

A custom user type seemed to be the best option so I politely introduced myself to IUserType. Implementing this interface enables NHibernate to perform custom persistence operations at my specification and I'll be damned if it isn't empowering to do so!

The code below has been snipped for brevity but the full implementation is available at the end of this article. Here are the juicy bits ....

public class PreciseDateTimeUserType : IUserType
    {
        
#region IUserType Members

        ......

        
public object NullSafeGet(IDataReader rs, string[] names, object owner)
        {
            
int ordinal rs.GetOrdinal(names[0]);
            if 
(rs.IsDBNull(ordinal))
            {
                
return new NullPreciseDateTime();
            
}
            
else
            
{
                
long value = rs.GetInt64(ordinal);
                return new 
PreciseDateTime(value);
            
}
        }

        
public void NullSafeSet(IDbCommand cmd, object valueint index)
        {
            
long val ((PreciseDateTime) value);
            
((IDbDataParameter) cmd.Parameters[index]).Value val;
        
}

        .....

        
#endregion
    
}

My PreciseDateTime class

The code above introduces PreciseDateTime - a class I have created to support this process. This class represents a DateTime by internally storing it as an Int64 in FileTimeUTC format. This is the key to providing the increased precision (just be aware that the values become unreadable in the database). The PreciseDateTime class looks like this ...

    public class PreciseDateTime
    {
        
protected static long NullValue = long.MinValue;
        private long 
_dateTimeStorage;

        public 
PreciseDateTime(long dateTimeLong)
        {
            _dateTimeStorage 
dateTimeLong;
        
}

        
public bool IsNull
        {
            
get return _dateTimeStorage == NullValue}
        }

        
public PreciseDateTime Clone()
        {
            
return new PreciseDateTime(this);
        
}

        
public static implicit operator long(PreciseDateTime preciseDateTime)
        {
            
if (preciseDateTime.IsNull)
            {
                
return NullValue;
            
}
            
else
            
{
                
return preciseDateTime._dateTimeStorage;
            
}
        }

        
public static implicit operator DateTime?(PreciseDateTime preciseDateTime)
        {
            
if (preciseDateTime.IsNull)
            {
                
return null;
            
}
            
else
            
{
                
return
                    
DateTime.FromFileTimeUtc(preciseDateTime._dateTimeStorage);
            
}
        }

        
public static implicit operator PreciseDateTime(DateTime? dateTime)
        {
            
return
                new 
PreciseDateTime((dateTime.HasValue)
                                        ? dateTime.Value.ToFileTimeUtc()
                                        : NullValue)
;
        
}
    }

Notice how cool implicit operators are in this instance. Basically my code takes any DateTime? representation and implicitly converts it to a PreciseDateTime object without my intervention. This makes for more readable code and prevents errors that could be introduced by manual conversions. Use it like this ...

MyObject.PreciseDateTime DateTime.Now;

Use with Castle ActiveRecord

To start using this implementation in Castle ActiveRecord you can simply do this (inserting your own namespace where appropriate) ...

        private PreciseDateTime _created;
        
        
[Property(ColumnType "Project.Domain.UserTypes.PreciseDateTimeUserType, Project.Domain")]
        
public virtual PreciseDateTime Created
        {
            
get return _created}
            
set { _created = value; }
        }

So I am now assured of not forgetting (again) how to implement custom user types for NHibernate and Castle ActiveRecord. If anything here is blatantly wrong please shout out - for everyone's sake.

If you crave more information about FileTimeUtc and what it represents go to the almighty MSDN

Full code for this article is available here : PreciseDateTimeUserType.cs (3.71 kb)

November 15, 2007 11:59 by steven.burman
E-mail | Permalink | Comments (3) | Comment RSSRSS comment feed

Related posts

Comments

November 21. 2007 21:30

Ken Egozi

Nice thing.

However, I'd suggest an improvement.

You can actually use DateTime (or better yet, DateTime?) as the storage type, and use datetime on the SQL Server side.

The problem in NH with DateTime is that it actually truncates the milliseconds on purpose. ADO.NET's Sql Server implementation can save and load proper DateTime instances from SQl Server (however only to 3.33 millisecond resolution, as that's the Sql Server limitation).

And to make things even simpler, you can ditch PreciseDateTime altogether and use DateTime type for the entity's property. Just use the IUserType implementation and you're good.

Ken Egozi

December 13. 2007 09:24

Justin

or you could just change it to Timestamp and it actually saves the milliseconds

thewebfarm.com/blog/archive/2005/06/29/283.aspx

Justin

June 29. 2008 05:30

Thierry

or you could just define them as Ticks in your mapping file which are stored as bigint (int64) and does excatly what you do with no code. This is also clean code no? I.e, no code to get it working.

PS for Justin : all the ways, Timestamp are rounded to 1/100 of seconds by NHibernate because SQL 2005 have a know limit of 3.33 milliseconds precision for native datetime.

Thierry

Add comment


(Will show your Gravatar icon)  

  Country flag

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]



Live preview

November 21. 2008 16:33