2012-10-18 60 views
5

我正在嘗試爲Noda Time LocalTime類型創建一個NHibernate IUserType,該類型在邏輯上映射到Sql Server 2008/2012中的time類型。我能夠從數據庫中保存和加載值。但是,我不能寫涉及本地時間比較查詢,如_session.Query<SchedulingTemplate>().Where(x => x.Start < end && x.End >= start)給出了錯誤SqlException (0x80131904): The data types time and datetime are incompatible in the less than operator.如何在Sql Server 2008/2012中創建一個NHibnerate IUserType?

從我的用戶類型的相關代碼:

public Type ReturnedType 
{ 
    get { return typeof(LocalTime); } 
} 

public override object NullSafeGet(IDataReader rs, string[] names, object owner) 
{ 
    var dbValue = NHibernateUtil.Time.NullSafeGet(rs, names); 
    if(dbValue == null) 
     return null; 

    return LocalDateTime.FromDateTime((DateTime)dbValue).TimeOfDay; 
} 

public override void NullSafeSet(IDbCommand cmd, object value, int index) 
{ 
    if(value == null) 
     NHibernateUtil.Time.NullSafeSet(cmd, null, index); 
    else 
     NHibernateUtil.Time.NullSafeSet(cmd, ((LocalTime)value).LocalDateTime.ToDateTimeUnspecified(), index); 
} 

public override SqlType[] SqlTypes 
{ 
    get { return new[] { SqlTypeFactory.Time }; } 
} 

的問題是,儘管上面的代碼表明數據庫類型是時間,它生成下面的查詢(每個SQL探查):

exec sp_executesql N'select [...] from [SchedulingTemplate] scheduling0_ where scheduling0_.Start<@p0 and scheduling0_.[End]>[email protected]',N'@p0 datetime,@p1 datetime',@p0='1753-01-01 20:00:00',@p1='1753-01-01 06:00:00'

(注意我爲簡潔起見省略了選擇列表)

請注意,參數的類型和值被視爲datetime

這似乎與兩個已關閉的NH錯誤https://nhibernate.jira.com/browse/NH-2661https://nhibernate.jira.com/browse/NH-2660非常相似。

我試圖使用NHibernateUtil.TimeAsTimeSpan,那似乎也沒有工作。它生成完全相同的查詢,讓我感到驚訝。我想也許NH-2661中描述的問題也存在於用戶類型中,並且不是固定的?

我使用NHibernate v3.3.1.400和野田時間1.0.0-β2

+1

在解決這個問題之前,您必須實現您自己的SqlType。複製TimeType中的代碼並修復它。 – Firo

回答

4

繼@ FIRO的建議,我從時間SQLTYPE工作以及與此想出了:

using NHibernate; 
using NHibernate.Dialect; 
using NHibernate.SqlTypes; 
using NHibernate.Type; 
using NodaTime; 
using NodaTime.Text; 
using System; 
using System.Data; 
using System.Data.SqlClient; 

[Serializable] 
public class LocalTimeType : PrimitiveType, IIdentifierType 
{ 
    private readonly LocalTimePattern _timePattern = LocalTimePattern.CreateWithInvariantCulture("h:mm:ss tt"); 

    public LocalTimeType() : base(SqlTypeFactory.Time) { } 

    public override string Name 
    { 
     get { return "LocalTime"; } 
    } 

    public override object Get(IDataReader rs, int index) 
    { 
     try 
     { 
      if (rs[index] is TimeSpan) //For those dialects where DbType.Time means TimeSpan. 
      { 
       var time = (TimeSpan)rs[index]; 
       return LocalTime.Midnight + Period.FromTicks(time.Ticks); 
      } 

      var dbValue = Convert.ToDateTime(rs[index]); 
      return LocalDateTime.FromDateTime(dbValue).TimeOfDay; 
     } 
     catch (Exception ex) 
     { 
      throw new FormatException(string.Format("Input string '{0}' was not in the correct format.", rs[index]), ex); 
     } 
    } 

    public override object Get(IDataReader rs, string name) 
    { 
     return Get(rs, rs.GetOrdinal(name)); 
    } 

    public override Type ReturnedClass 
    { 
     get { return typeof(LocalTime); } 
    } 

    public override void Set(IDbCommand st, object value, int index) 
    { 
     var parameter = ((SqlParameter)st.Parameters[index]); 
     parameter.SqlDbType = SqlDbType.Time; // HACK work around bad behavior, M$ says not ideal, but as intended, NH says this is a bug in MS may work around eventually 
     parameter.Value = new TimeSpan(((LocalTime)value).TickOfDay); 
    } 

    public override bool IsEqual(object x, object y) 
    { 
     return Equals(x, y); 
    } 

    public override int GetHashCode(object x, EntityMode entityMode) 
    { 
     return x.GetHashCode(); 
    } 

    public override string ToString(object val) 
    { 
     return _timePattern.Format((LocalTime)val); 
    } 

    public object StringToObject(string xml) 
    { 
     return string.IsNullOrEmpty(xml) ? null : FromStringValue(xml); 
    } 

    public override object FromStringValue(string xml) 
    { 
     return _timePattern.Parse(xml).Value; 
    } 

    public override Type PrimitiveClass 
    { 
     get { return typeof(LocalTime); } 
    } 

    public override object DefaultValue 
    { 
     get { return new LocalTime(); } 
    } 

    public override string ObjectToSQLString(object value, Dialect dialect) 
    { 
     return "'" + _timePattern.Format((LocalTime)value) + "'"; 
    } 
} 

的鍵碼爲在Set方法,其中,是說:

var parameter = ((SqlParameter)st.Parameters[index]); 
parameter.SqlDbType = SqlDbType.Time; 

這是必需的,因爲MS數據提供者需要設置DbTypeDbType.Time表示底層類型應該是DateTime。你必須設置SqlDbType到它的工作時間。

+0

從'TimeSpan'轉換到'LocalTime'應該比它更清潔。我爲此提出了問題148。從'DateTime'轉換爲'LocalTime' *比顯示簡單 - 你只需要通過'LocalDateTime':'LocalTime time = LocalDateTime.FromDateTime(dateTime).TimeOfDay;' –

+0

謝謝@JonSkeet我已經改變了使用'LocalDateTime.FromDateTime' –