简体   繁体   English

NHibernate QueryOver与IUserType失败

[英]NHibernate QueryOver with IUserType fails

I have a custom user type with NHibernate and the custom type works fine for saving and updating. 我在NHibernate中有一个自定义用户类型,该自定义类型可以很好地保存和更新。 However an error occurs when using QueryOver on this custom user type. 但是,在此自定义用户类型上使用QueryOver时会发生错误。 I get the error message: could not resolve property: SpecialType.Code of: NHibernateComplexIUserTypeExample.Person . 我收到错误消息: could not resolve property: SpecialType.Code of: NHibernateComplexIUserTypeExample.Person

I know that I can map the SpecialType class using Component() instead of Map() with a custom type, but there are other considerations outside of this example that make that inappropriate. 我知道我可以使用Component()而不是具有自定义类型的Map()映射SpecialType类,但是在此示例之外还有其他考虑因素使之不合适。 If possible I would like to resolve this while keeping it as an IUserType. 如果可能的话,我想解决这个问题,同时将其保留为IUserType。

Here is my sample code that can cause this error. 这是可能导致此错误的示例代码。

The error occurs in Program.cs on the line with the QueryOver<> . 该错误在QueryOver<>行上的Program.cs发生。

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

Database Table Definition 数据库表定义

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]

A quick (possibly not ideal) solution: 快速(可能不理想)的解决方案:

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

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

This is probably not ideal though, since you have to fill some fake value in for the description. 但是,这可能并不理想,因为您必须在描述中填写一些虚假值。 NHibernate should generate the correct SQL though. NHibernate应该生成正确的SQL。

Another solution that's a little more involved is to modify NullSafeSet to allow strings and SpecialType s to be handled by the custom type: 另一个涉及更多的解决方案是修改NullSafeSet以允许字符串 SpecialType由自定义类型处理:

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

And then modify your query to use Restrictions.Where , which is a little more verbose: 然后修改查询以使用Restrictions.Where ,这有点冗长:

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

One way to clean the above up would be to implement an explicit operator for SpecialType that allows the cast from string to SpecialType : 清理上面内容的一种方法是为SpecialType实现一个explicit运算符,该运算符允许将stringstringSpecialType

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

Now you can shorten your QueryOver code: 现在,您可以缩短QueryOver代码:

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

However the huge downside to this is that people will be able to explicitly cast string s to SpecialType s in your application--probably not something you want. 但是,这样做的巨大缺点是,人们将能够在应用程序中将string s显式转换为SpecialType可能不是您想要的。

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

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