[英]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
发生。
public class Person
{
public virtual int Id { get; protected set; }
public virtual string Name { get; set; }
public virtual SpecialType SpecialType { get; set; }
public Person()
{
}
}
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");
}
}
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();
}
}
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
}
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();
}
}
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
运算符,该运算符允许将string
从string
为SpecialType
:
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.