带有 IUserType 的 NHibernate QueryOver 失败

NHibernate QueryOver with IUserType fails

我有一个带有 NHibernate 的自定义用户类型,自定义类型可以很好地保存和更新。但是,在此自定义用户类型上使用 QueryOver 时会发生错误。我收到错误消息:could not resolve property: SpecialType.Code of: NHibernateComplexIUserTypeExample.Person.

我知道我可以使用具有自定义类型的 Component() 而不是 Map() 来映射 SpecialType class,但是在这个示例之外还有其他一些考虑因素使得这样做不合适。如果可能的话,我想在将其保留为 IUserType 的同时解决此问题。

这是我的示例代码,可能会导致此错误。

错误发生在 Program.csQueryOver<> 的行上。

Person.cs

public class Person
{
    public virtual int Id { get; protected set; }

    public virtual string Name { get; set; }

    public virtual SpecialType SpecialType { get; set; }

    public Person()
    {
    }
}

PersonMap.cs

public class PersonMap : ClassMap<Person>
{
    public PersonMap()
    {
        Id(x => x.Id);

        Map(x => x.Name).Not.Nullable();

        Map(x => x.SpecialType)
            .CustomType<SpecialTypeUserType>()
            .Not.Nullable()
            .Column("SpecialType_Code");
    }
}

Program.cs

class Program
{
    static void Main(string[] args)
    {
        // create db session
        var sessionFactory = Program.CreateSessionFactory();

        var session = sessionFactory.OpenSession();

        // query db using complex iusertype
        var results = session.QueryOver<Person>().Where(x => x.SpecialType.Code == "1").List();

        if (results != null)
        {
            foreach (var result in results)
            {
                Console.WriteLine("Person {0} has code {1}.", result.Name, result.SpecialType.Code);
            }
        }
    }

    public static ISessionFactory CreateSessionFactory()
    {
        return Fluently.Configure()
            .Database(
                MsSqlConfiguration
                .MsSql2008
                .ConnectionString("..."))
            .Mappings(
                m =>
                {
                    m.FluentMappings.AddFromAssemblyOf<Person>();
                })
            .BuildSessionFactory();
    }
}

SpecialTypeUserType.cs

public class SpecialTypeUserType : global::NHibernate.UserTypes.IUserType
{
    #region IUserType Members

    public object Assemble(object cached, object owner)
    {
        // used for caching, as our object is immutable we can just return it as is

        return cached;
    }

    public object DeepCopy(object value)
    {
        //? should we implement deep copy for this?

        return value;
    }

    public object Disassemble(object value)
    {
        // used for caching, as our object is immutable we can just return it as is

        return value;
    }

    public new bool Equals(object x, object y)
    {
        // implements equals itself so we use this implementation

        if (x == null)
        {
            return false;
        }
        else
        {
            return x.Equals(y);
        }
    }

    public int GetHashCode(object x)
    {
        if (x == null)
        {
            throw new ArgumentNullException("x");
        }

        // object itself implements GetHashCode so we use that

        return x.GetHashCode();
    }

    public bool IsMutable
    {
        get
        {
            return false;
        }
    }

    public object NullSafeGet(System.Data.IDataReader rs, string[] names, object owner)
    {
        if (names == null)
        {
            throw new ArgumentNullException("names");
        }

        // we get the string from the database using the NullSafeGet used to get strings 

        string codeString = (string)global::NHibernate.NHibernateUtil.String.NullSafeGet(rs, names[0]);

        SpecialType newSpecialType = new SpecialType(codeString, "Test...");

        return newSpecialType;
    }

    public void NullSafeSet(System.Data.IDbCommand cmd, object value, int index)
    {
        // set the value using the NullSafeSet implementation for string from NHibernateUtil

        if (value == null)
        {
            global::NHibernate.NHibernateUtil.String.NullSafeSet(cmd, null, index);

            return;
        }

        value = ((SpecialType)value).Code;

        global::NHibernate.NHibernateUtil.String.NullSafeSet(cmd, value, index);
    }

    public object Replace(object original, object target, object owner)
    {
        // as our object is immutable we can just return the original

        return original;
    }

    public Type ReturnedType
    {
        get
        {
            return typeof(SpecialType);
        }
    }

    public NHibernate.SqlTypes.SqlType[] SqlTypes
    {
        get
        {
            // we store our SpecialType.Code in a single column in the database that can contain a string

            global::NHibernate.SqlTypes.SqlType[] types = new global::NHibernate.SqlTypes.SqlType[1];

            types[0] = new global::NHibernate.SqlTypes.SqlType(System.Data.DbType.String);

            return types;
        }
    }

    #endregion
}

SpecialType.cs

public class SpecialType
{
    public string Code { get; private set; }

    public string Description { get; private set; }

    public SpecialType(string code, string description)
    {
        this.Code = code;
        this.Description = description;
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
        {
            return false;
        }

        SpecialType type = obj as SpecialType;

        if (type == null)
        {
            return false;
        }

        if (object.ReferenceEquals(this, type))
        {
            return true;
        }

        if (type.Code == null && this.Code != null)
        {
            return false;
        }
        else if (type.Code != null && this.Code == null)
        {
            return false;
        }
        else if (type.Code != null && this.Code != null)
        {
            if (!type.Code.Equals(this.Code, StringComparison.OrdinalIgnoreCase))
            {
                return false;
            }
        }

        return true;
    }

    public override int GetHashCode()
    {
        return this.Code.GetHashCode();
    }
}

数据库Table定义

CREATE TABLE [dbo].[Person](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](255) NOT NULL,
    [SpecialType_Code] [nvarchar](255) NOT NULL,
PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,     ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

一个快速(可能不理想)的解决方案:

var specialTypeToCompare = new SpecialType("1", "some_description");

var results = session.QueryOver<Person>()
    .Where(x => x.SpecialType.Code == specialTypeToCompare).List();

虽然这可能并不理想,因为您必须为描述填写一些假值。不过 NHibernate 应该生成正确的 SQL。

另一个稍微复杂一点的解决方案是修改 NullSafeSet 以允许字符串 SpecialTypes 由自定义类型处理:

public void NullSafeSet(System.Data.IDbCommand cmd, object value, int index)
{
    // set the value using the NullSafeSet implementation for string from NHibernateUtil


    if (value == null)
    {
        global::NHibernate.NHibernateUtil.String.NullSafeSet(cmd, null, index);

        return;
    }

    /* Allow for the possibility of a string */
    string valueToSet = null;

    if (value.GetType() == typeof(string))
    {
        valueToSet = (string)value;
    }
    else if (value.GetType() == typeof(SpecialType))
    {
        valueToSet = ((SpecialType)value).Code;
    }

    global::NHibernate.NHibernateUtil.String.NullSafeSet(cmd, valueToSet, index);
}

然后修改您的查询以使用 Restrictions.Where,这有点冗长:

var results = session.QueryOver<Person>()
    .Where(
        Restrictions.Eq(
            Projections.Property<Person>(p => p.SpecialType), "1"))
    .List();

清理上述问题的一种方法是为 SpecialType 实现一个 explicit 运算符,允许从 stringSpecialType 的转换:

public static explicit operator SpecialType(string s)
{
    return new SpecialType(s, null);
}

现在您可以缩短 QueryOver 代码:

var results = session.QueryOver<Person>()
    .Where(p => p.SpecialType == (SpecialType)"1")
    .List();

然而,这样做的巨大缺点是人们将能够在您的应用程序中将 string 显式转换为 SpecialType——可能不是您想要的。