2012-01-09 73 views
4

首先,讓我爲這篇文章的篇幅道歉,但主要是代碼,所以我希望你們都支持我!關於IUserType的NHibernate QueryOver

我有一個處理遺留數據庫的場景,我需要使用NHibernate 3.2編寫一個IUserType以獲取2個字符的「狀態」字段並從中返回一個布爾值。狀態字段可以包含3個可能的值:

* 'DI'  // 'Disabled', return false 
* ' '  // blank or NULL, return true 
* NULL  

這是我簡化了的內容。

表定義:

CREATE TABLE [dbo].[Client](
    [clnID] [int] IDENTITY(1,1) NOT NULL, 
    [clnStatus] [char](2) NULL, 
    [clnComment] [varchar](250) NULL, 
    [clnDescription] [varchar](150) NULL, 
    [Version] [int] NOT NULL 
) 

流利的映射:

public class ClientMapping : CoreEntityMapping<Client> 
{ 
    public ClientMapping() 
    { 
     SchemaAction.All().Table("Client"); 
     LazyLoad(); 

     Id(x => x.Id, "clnId").GeneratedBy.Identity(); 
     Version(x => x.Version).Column("Version").Generated.Never().UnsavedValue("0").Not.Nullable(); 
     OptimisticLock.Version(); 

     Map(x => x.Comment, "clnComment").Length(250).Nullable(); 
     Map(x => x.Description, "clnDescription").Length(250).Nullable(); 
     Map(x => x.IsActive, "clnStatus").Nullable().CustomType<StatusToBoolType>(); 
    } 
} 

我IUserType實現:

public class StatusToBoolType : IUserType 
{ 
    public bool IsMutable { get { return false; } } 
    public Type ReturnedType { get { return typeof(bool); } } 
    public SqlType[] SqlTypes { get { return new[] { NHibernateUtil.String.SqlType }; } } 

    public object DeepCopy(object value) 
    { 
     return value; 
    } 
    public object Replace(object original, object target, object owner) 
    { 
     return original; 
    } 
    public object Assemble(object cached, object owner) 
    { 
     return cached; 
    } 
    public object Disassemble(object value) 
    { 
     return value; 
    } 

    public new bool Equals(object x, object y) 
    { 
     if (ReferenceEquals(x, y)) return true; 
     if (x == null || y == null) return false; 
      return x.Equals(y); 
    } 
    public int GetHashCode(object x) 
    { 
     return x == null ? typeof(bool).GetHashCode() + 473 : x.GetHashCode(); 
    } 

    public object NullSafeGet(IDataReader rs, string[] names, object owner) 
    { 
     var obj = NHibernateUtil.String.NullSafeGet(rs, names[0]); 
     if (obj == null) return true; 

     var status = (string)obj; 
     if (status == " ") return true; 
     if (status == "DI") return false; 
     throw new Exception(string.Format("Expected data to be either empty or 'DI' but was '{0}'.", status)); 
    } 

    public void NullSafeSet(IDbCommand cmd, object value, int index) 
    { 
     var parameter = ((IDataParameter) cmd.Parameters[index]); 
     var active = value == null || (bool) value; 
     if (active) 
      parameter.Value = " "; 
     else 
      parameter.Value = "DI"; 
    } 
} 

但是這不起作用。該單元測試失敗,計數不準確。

[TestMethod] 
public void GetAllActiveClientsTest() 
{ 
    //ACT 
    var count = Session.QueryOver<Client>() 
     .Where(x => x.IsActive) 
     .SelectList(l => l.SelectCount(x => x.Id)) 
     .FutureValue<int>().Value; 

    //ASSERT 
    Assert.AreNotEqual(0, count); 
    Assert.AreEqual(1721, count); 
} 

失敗的原因是因爲它會生成以下SQL:

SELECT count(this_.clnID) as y0_ FROM Client this_ WHERE this_.clnstatus = @p0; 
/* @p0 = ' ' [Type: String (0)] */ 

但我需要它,而不是產生這樣的:

SELECT count(this_.clnID) as y0_ FROM Client this_ WHERE (this_.clnstatus = @p0 <b> OR this_.clnstatus IS NULL);</b> 

經過一番調試,我看到的是,NullSafeSet ()方法在生成查詢之前被調用,所以我能夠通過在該方法中編寫一些黑客代碼來操縱cmd.CommandText pro中的SQL perty。

... 
public void NullSafeSet(IDbCommand cmd, object value, int index) 
{ 
    var parameter = ((IDataParameter) cmd.Parameters[index]); 
    var active = value == null || (bool) value; 
    if (active) 
    { 
     parameter.Value = " "; 

     if (cmd.CommandText.ToUpper().StartsWith("SELECT") == false) return; 
     var paramindex = cmd.CommandText.IndexOf(parameter.ParameterName); 
     if (paramindex > 0) 
     { 
      // Purpose: change [columnName] = @p0 ==> ([columnName] = @p0 OR [columnName] IS NULL) 
      paramindex += parameter.ParameterName.Length; 
      var before = cmd.CommandText.Substring(0, paramindex); 
      var after = cmd.CommandText.Substring(paramindex); 

      //look at the text before the '= @p0' and find the column name... 
      var columnSection = before.Split(new[] {"= " + parameter.ParameterName}, StringSplitOptions.RemoveEmptyEntries).Reverse().First(); 
      var column = columnSection.Substring(columnSection.Trim().LastIndexOf(' ')).Replace("(", ""); 
      var myCommand = string.Format("({0} = {1} OR {0} IS NULL)", column.Trim(), parameter.ParameterName); 

      paramindex -= (parameter.ParameterName.Length + column.Length + 1); 
      var orig = before.Substring(0, paramindex); 
      cmd.CommandText = orig + myCommand + after; 
     } 
    } 
    else 
     parameter.Value = "DI"; 
} 

但是這是NHibernate!像這樣篡改sql語句不可能是解決這個問題的正確方法嗎?對?

因爲它是一個共享的遺留數據庫,所以我不能將表格模式更改爲NOT NULL,否則我會這樣做,並避免這種情況。

所以最後,在所有這個前奏我的問題是這樣的,我可以在哪裏告訴NHibernate生成一個自定義的SQL條件語句爲這個IUserType?

謝謝大家提前!

回答

2

解決了!

當我發佈我的問題後,我回到了繪圖板,我想出了一個解決方案,不需要在IUserType實現中黑客生成SQL。事實上,這個解決方案根本不需要IUserType!

這是我做的。

首先,我更改了IsActive列以使用公式來處理空檢查。這解決了我與QueryOver失敗的問題,因爲現在每次NHibernate處理IsActive屬性時,它會注入我的sql公式來處理null。

這種方法的缺點是,在我把公式存入公式後,所有的保存測試都失敗了。事實證明,公式屬性是有效的ReadOnly屬性。

所以爲了解決這個問題,我在實體中添加了一個受保護的屬性來保存來自數據庫的狀態值。

接下來,我更改了IsActive屬性以將受保護的狀態屬性設置爲「」或「DI」。最後,我改變了FluentMapping以顯示受保護的狀態屬性爲NHibernate,以便NHibernate可以跟蹤它。既然NHibernate知道Status,它可以將它包含在INSERT/UPDATE語句中。

我會在下面列出我的解決方案以防其他人感興趣。

Client類

public class Client 
{ 
    ... 

    protected virtual string Status { get; set; } 
    private bool _isActive; 
    public virtual bool IsActive 
    { 
     get { return _isActive; } 
     set 
     { 
      _isActive = value; 
      Status = (_isActive) ? " " : "DI"; 
     } 
    } 
} 

更改流利的映射

public class ClientMapping : CoreEntityMapping<Client> 
{ 
    public ClientMapping() 
    { 
     .... 

     Map(Reveal.Member<E>("Status"), colName).Length(2); 
     Map(x => x.IsActive).Formula("case when clnStatus is null then ' ' else clnStatus end"); 
    } 
}