IUserType 上的 NHibernate QueryOver

标签 nhibernate queryover iusertype

首先让我为这篇文章的长度道歉,虽然它主要是代码,所以我希望大家多多包涵!

我有一个处理遗留数据库的场景,我需要使用 NHibernate 3.2 编写一个 IUserType 来获取一个 2 个字符的“状态”字段并从中返回一个 bool 值。状态字段可以包含 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>

经过一些调试后,我看到在生成查询之前调用了我的 StatusToBoolType 类中的 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,否则我会这样做,并避免这种情况。

最后,在所有这些前奏之后,我的问题很简单,我在哪里可以告诉 NHibernate 为这个 IUserType 生成自定义 SQL 条件语句?

谢谢大家!

最佳答案

解决了!

在我发布我的问题后,我回到了绘图板上,我想出了一个不需要在 IUserType 实现中破解生成的 SQL 的解决方案。事实上,这个解决方案根本不需要 IUserType!

这就是我所做的。

首先,我将 IsActive 列更改为使用公式来处理空值检查。这解决了我的 QueryOver 失败的问题,因为现在 NHibernate 每次处理 IsActive 属性时,它都会注入(inject)我的 sql 公式来处理 null。

这种方法的缺点是,在我输入公式后,我所有的保存测试都失败了。事实证明,公式属性实际上是只读属性。

所以为了解决这个问题,我向实体添加了一个 protected 属性来保存数据库中的状态值。

接下来,我更改了 IsActive 属性,将 protected 状态属性设置为“”或“DI”。最后,我将 FluentMapping 更改为将 protected Status 属性更改为 NHibernate,以便 NHibernate 可以跟踪它。现在 NHibernate 已经知道了 Status,它可以将它包含在它的 INSERT/UPDATE 语句中。

我将在下面包含我的解决方案,以防其他人感兴趣。

客户类

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

Fluent 映射的更改
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");
    }
}

关于IUserType 上的 NHibernate QueryOver,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8796879/

相关文章:

NHibernate 使用 SqlFunction 选择列,按相同条件分组

c# - NHibernate 按代码映射和 IUsertype 不工作

asp.net-mvc-3 - Azure云服务部署错误: "The instance of SQL Server you attempted to connect to does not support encryption. "

nhibernate - 将 NHibernate ICompositeUserType 与值类型结合使用

c# - 带有 NHibernate Criteria 的 HAVING 子句中的多个条件?

c# - OracleDataClientBatchingBatcherFactory 引发空引用异常

hibernate - 如何使用注释创建 hibernate 复合键

NHibernate QueryOver : Get a row count with group by in a subquery