简体   繁体   English

NHibernate QueryOver上一个IUserType

[英]NHibernate QueryOver on an IUserType

First let me apologize a bit for the length of this post, it's mostly code though so I hope you all bear with me! 首先,让我为这篇文章的长度道歉,尽管它主要是代码,所以我希望大家都支持我!

I have a scenario in dealing with a legacy database, where I needed to write an IUserType using NHibernate 3.2 to take a 2 character "status" field and return a Boolean value from it. 我有一个处理旧数据库的场景,我需要使用NHibernate 3.2编写一个IUserType,以获取2个字符的“状态”字段并从中返回一个布尔值。 The status field can hold 3 possible values: 状态字段可以包含3个可能的值:

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

Here is what I have simplified. 这是我简化的内容。

Table Definition: 表定义:

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
)

Fluent Mapping: 流利的映射:

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

My IUserType Implementation: 我的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";
    }
}

However this doesn't work. 但是,这不起作用。 This unit test fails with an inaccurate count. 此单元测试失败,计数不正确。

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

The reason it fails is because it generates the following SQL: 失败的原因是因为它生成以下SQL:

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

But I need it to generate this instead: 但是我需要它来生成它:

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

After some debugging I saw that the NullSafeSet() method in my StatusToBoolType class is invoked before the query is generated, so I was able to get around this by writing some hackish code in that method to manipulate the SQL in the cmd.CommandText property. 经过一些调试后,我看到在生成查询之前已调用了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";
}

But this is NHibernate!!! 但这是NHibernate !!! Hacking the sql statement like this can't possibly be the correct way to handle this? 这样破解sql语句可能不是解决此问题的正确方法吗? Right? 对?

Because it is a shared legacy database, I can't change the table schema to NOT NULL otherwise I would have just done that, and avoided this scenario. 因为它是一个共享的旧数据库,所以我不能将表模式更改为NOT NULL,否则我会这样做,并且避免了这种情况。

So finally after all this prelude my question is simply this, where can I tell NHibernate to generate a custom SQL criteria statement for this IUserType? 所以最后,这毕竟是我的前奏简单的问题,我该在哪里告诉NHibernate为此IUserType生成自定义SQL条件语句?

Thank you all in advance! 谢谢大家!

Solved it! 解决了!

After I posted my question I went back to the drawing board, and I came up with a solution that doesn't require hacking the generated SQL in the IUserType implementation. 发布问题后,我回到了绘图板上,提出了一种解决方案,该解决方案不需要在IUserType实现中修改生成的SQL。 In fact this solution doesn't need the IUserType at all! 实际上,此解决方案根本不需要IUserType!

Here is what I did. 这是我所做的。

First, I changed the IsActive column to use a formula to handle the null checking. 首先,我将IsActive列更改为使用公式来处理null检查。 This fixed my issue with the QueryOver failing, because now everytime NHibernate deals with IsActive property it injects my sql formula to handle null. 这解决了QueryOver失败的问题,因为现在NHibernate每次处理IsActive属性时,都会注入我的sql公式来处理null。

The downside to this approach was that after I put in the formula all of my save tests failed. 这种方法的缺点是,在我输入公式后,所有保存测试都失败了。 It turns out that formula properties are effectively ReadOnly properties. 事实证明,公式属性实际上是ReadOnly属性。

So to get around this issue, I added a protected property to the entity to hold the status value from the database. 因此,为了解决此问题,我向实体添加了一个受保护的属性,以保存数据库中的状态值。

Next, I changed the IsActive property to set the protected status property to " " or "DI". 接下来,我更改了IsActive属性,将受保护的状态属性设置为“”或“ DI”。 And finally I changed the FluentMapping to Reveal the protected Status property to NHibernate so that NHibernate can track it. 最后,我将FluentMapping更改为将受保护的Status属性显示为NHibernate,以便NHibernate可以跟踪它。 Now that NHibernate is aware of Status it can include it on its INSERT/UPDATE statements. 现在,NHibernate知道Status了,可以将其包含在INSERT / UPDATE语句中。

I am going to include my solution below in case anyone else is interested. 如果其他人有兴趣,我将在下面提供我的解决方案。

Client class 客户类

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

Changes to Fluent Mapping 流利映射的更改

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM