2012-01-09 72 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"); 
    } 
}