2012-01-09 21 views
4

まず、この記事の長さについて少しお詫びしましょう。IUserTypeのNHibernate QueryOver

私は、2文字の "ステータス"フィールドを取得し、そこからブール値を返すために、NHibernate 3.2を使用してIUserTypeを記述する必要があったレガシーデータベースを扱うシナリオを持っています。ステータスフィールドには、次の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"; 
    } 
} 

しかし、これは動作しません。このユニットテストは、不正確なカウントで失敗します。

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

しかし、私は、それは代わりに、これを生成する必要があります:それは、次のSQLを生成しますので、それが失敗した理由がある

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

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

いくつかのデバッグ後、私はNullSafeSetことを見ました()メソッドは、クエリが生成される前に呼び出されます。そのため、このメソッドでは、cmd.CommandTextプロのSQLを操作するためのいくつかのハックコードを記述することでこの問題を回避することができましたペティ。

... 
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に変更することはできません。そうしないと、このシナリオを回避できます。

最後に、この前書きの後、私の質問は、このIUserTypeのカスタムSQL基準ステートメントを生成するためにNHibernateにどこに伝えることができますか?

ありがとうございました!

答えて

2

解決済み!

質問を投稿した後、IUserType実装で生成されたSQLをハッキングする必要のないソリューションを思いつきました。実際、このソリューションではIUserTypeはまったく必要ありません。

ここに私がしたことがあります。

まず、IsActive列を変更して、nullチェックを処理する式を使用するようにしました。これは、今度はNHibernateがIsActiveプロパティを扱うたびに、nullを処理するためにSQL式を注入するので、QueryOverの問題が修正されました。

このアプローチの欠点は、式を入れた後、すべての保存テストが失敗したことです。数式プロパティは効果的にReadOnlyプロパティであることが分かります。

この問題を回避するために、データベースからステータス値を保持するために、エンティティに保護されたプロパティを追加しました。

次に、IsActiveプロパティを変更して、保護されたステータスプロパティを ""または "DI"に設定しました。最後に、FluentMappingを変更して、NHibernateがそれを追跡できるように、保護されたStatusプロパティを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"); 
    } 
} 
関連する問題