2012-10-18 8 views
5

Próbuję utworzyć NHibernate IUserType dla typu Noda Time LocalTime, który logicznie mapowałby do typu time w Sql Server 2008/2012. Jestem w stanie uzyskać zapisanie wartości i załadowanie z bazy danych. Jednak nie mogę pisać zapytań obejmujących porównanie czasów lokalnych jak _session.Query<SchedulingTemplate>().Where(x => x.Start < end && x.End >= start) daje błąd SqlException (0x80131904): The data types time and datetime are incompatible in the less than operator.Jak utworzyć typ IUserType NHibnerate dla czasu w Sql Server 2008/2012?

Odpowiedni kod z mojego typu użytkownika jest:

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 }; } 
} 

Problemem jest to, że pomimo powyższego kodu wskazując bazę typ to czas, generuje następujące zapytanie (za SQL Profiler):

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'

(nota pominąłem listy wyboru dla zwięzłość)

Należy zauważyć, że typ i wartość parametrów są traktowane jako datetime.

Wygląda to bardzo podobnie do dwóch błędów NH, które zostały zamknięte https://nhibernate.jira.com/browse/NH-2661 i https://nhibernate.jira.com/browse/NH-2660.

Próbowałem użyć NHibernateUtil.TimeAsTimeSpan i to też nie działa. Wygenerował dokładnie to samo zapytanie, które mnie zaskoczyło. Myślę, że może problem opisany w NH-2661 istnieje również dla typów użytkowników i nie został na to ustalony?

Używam NHibernate v3.3.1.400 i Noda Czas 1.0.0-beta2

+1

dopóki to nie zostanie naprawione, musisz zaimplementować własny SqlType. Skopiuj kod z TimeType i napraw go. – Firo

Odpowiedz

4

Following @ poradę FIRO za pracowałem z typ SQLTYPE czasu i podszedł z tym:

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) + "'"; 
    } 
} 

The Kod klucz jest w metodzie Set w którym jest napisane:

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

ta jest potrzebna, ponieważ dostawcy danych MS zajmuje ustawienie DbType do DbType.Time oznacza typ bazowy powinien być DateTime. Użytkownik musi musi ustawić czas na jego działanie.

+0

Konwersja z 'TimeSpan' na' LocalTime' powinna być znacznie czystsza niż jest. Podałem w związku z tym numer 148. Konwersja z 'DateTime' na' LocalTime' * jest * prostsza niż pokazano - wystarczy przejść przez 'LocalDateTime':' LocalTime time = LocalDateTime.FromDateTime (dateTime) .TimeOfDay; ' –

+0

Dzięki @JonSkeet Dokonałem zmiany używać 'LocalDateTime.FromDateTime' –

Powiązane problemy