NHibernate IUserType 和 HQL 和 Linq 的自定义函数
NHibernate IUserType and custom functions for HQL and Linq
简而言之,我想创建一个自定义 IUserType
来表示 .NET 中的 IPAddress
(作为 postgresql
中的 inet
类型)并能够查询它通过 HQL
和 Linq
具有自定义函数。我在为 Linq
.
实现自定义函数时遇到问题
我目前拥有的:
A) 我可以映射它:
public class SomeEntityMapper : ClassMap<SomeEntity> {
public SomeEntityMapper() {
...
Map(x => x.IpAddressField)
.CustomSqlType("inet")
.CustomType<IPAddressUserType>()
...
}
}
下面的 IUserType 实现:
[Serializable]
public class IPAddressUserType : IUserType
{
public new bool Equals(object x, object y)
{
if (x == null && y == null)
return true;
if (x == null || y == null)
return false;
return x.Equals(y);
}
public int GetHashCode(object x)
{
if (x == null)
return 0;
return x.GetHashCode();
}
public object NullSafeGet(DbDataReader rs, string[] names, ISessionImplementor session, object owner)
{
if (names.Length == 0)
throw new InvalidOperationException("Expected at least 1 column");
if (rs.IsDBNull(rs.GetOrdinal(names[0])))
return null;
object value = rs[names[0]];
return value;
}
public void NullSafeSet(DbCommand cmd, object value, int index, ISessionImplementor session)
{
NpgsqlParameter parameter = (NpgsqlParameter) cmd.Parameters[index];
parameter.NpgsqlDbType = NpgsqlTypes.NpgsqlDbType.Inet;
if (value == null)
{
parameter.Value = DBNull.Value;
}
else
{
parameter.Value = value;
}
}
public object DeepCopy(object value)
{
if (value == null)
return null;
IPAddress copy = IPAddress.Parse(value.ToString());
return copy;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
if (cached == null)
return null;
if (IPAddress.TryParse((string)cached, out var address))
{
return address;
}
return null;
}
public object Disassemble(object value)
{
if (value == null)
return null;
return value.ToString();
}
public SqlType[] SqlTypes => new SqlType[] { new NpgsqlSqlType(DbType.String, NpgsqlTypes.NpgsqlDbType.Inet), };
public Type ReturnedType => typeof(IPAddress);
public bool IsMutable => false;
}
public class NpgsqlSqlType : SqlType
{
public NpgsqlDbType NpgDbType { get; }
public NpgsqlSqlType(DbType dbType, NpgsqlDbType npgDbType)
: base(dbType)
{
NpgDbType = npgDbType;
}
public NpgsqlSqlType(DbType dbType, NpgsqlDbType npgDbType, int length)
: base(dbType, length)
{
NpgDbType = npgDbType;
}
public NpgsqlSqlType(DbType dbType, NpgsqlDbType npgDbType, byte precision, byte scale)
: base(dbType, precision, scale)
{
NpgDbType = npgDbType;
}
}
}
B) 我可以使用 HQL 和我实现的自定义函数 (inet_equals
) 查询它。
using(var session = _factory.OpenSession()) {
var q = session.CreateQuery("from SomeEntity as s WHERE inet_equals(s.IpAddressField, :ip)");
q.SetParameter("ip", IPAddress.Parse("4.3.2.1"), NHibernateUtil.Custom(typeof(IPAddressUserType)));
}
自定义 Postgresql 方言扩展了下面的 HQL 函数实现:
public class CustomPostgresqlDialect : PostgreSQL83Dialect
{
public CustomPostgresqlDialect()
{
RegisterFunction("inet_equals", new SQLFunctionTemplate(NHibernateUtil.Boolean, "(?1::inet = ?2::inet)"));
}
}
C) 我希望能够像这样使用 LINQ 查询它:
using(var session = _factory.OpenSession()) {
var q = session.Query<SomeEntity>()
.Where(s => s.IpAddressField.InetEquals(IPAddress.Parse("4.3.2.1")));
}
以下 NHibernate 提供程序的自定义 LINQ 生成器:
public static class InetExtensions
{
public static bool InetEquals(this IPAddress value, IPAddress other)
{
throw new NotSupportedException();
}
}
public class InetGenerator : BaseHqlGeneratorForMethod
{
public InetGenerator()
{
SupportedMethods = new[]
{
ReflectHelper.GetMethodDefinition(() => InetExtensions.InetEquals(default(IPAddress), default(IPAddress)))
};
}
public override HqlTreeNode BuildHql(MethodInfo method, System.Linq.Expressions.Expression targetObject, ReadOnlyCollection<System.Linq.Expressions.Expression> arguments,
HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
HqlExpression lhs = visitor.Visit(arguments[0]).AsExpression();
HqlExpression rhs = visitor.Visit(arguments[1]).AsExpression();
return treeBuilder.BooleanMethodCall(
"inet_equals",
new[]
{
lhs,
rhs
}
);
}
}
public class ExtendedLinqToHqlGeneratorsRegistry :
DefaultLinqToHqlGeneratorsRegistry
{
public ExtendedLinqToHqlGeneratorsRegistry()
: base()
{
this.Merge(new InetGenerator());
}
}
不幸的是,当使用 LINQ
时,我得到了这个异常:
HibernateException: Could not determine a type for class: System.Net.IPAddress
有趣的是,如果我在 HQL
查询中省略 NHibernateUtil.Custom(typeof(IPAddressUserType))
参数,这与我得到的错误完全相同。
这让我相信我走在正确的轨道上,但我无法弄清楚我可能遗漏了什么。我假设我需要通知生成器内部的 NHibernate 这是一个自定义 UserType
(就像我通过 NHibernateUtil.Custom(typeof(IPAddressUserType))
参数对 HQL
查询所做的那样。
我找到了解决方案。
要提示 NHibernate 使用正确的 UserType,请使用:MappedAs
扩展方法,如下所示:
using(var session = _factory.OpenSession()) {
var q = session.Query<SomeEntity>()
.Where(s => s.IpAddressField.InetEquals(
IPAddress.Parse("4.3.2.1").MappedAs(NHibernateUtil.Custom(typeof(IPAddressUserType))
);
}
今年是 2019 年,我仍然停留在版本 3.3.2.GA 的 NHibernate 并且 MappedAs
扩展方法仅存在于版本 4.x以后。
我的场景是需要将一个大字符串作为参数传递给 HqlGeneratorForMethod
中受支持的方法。
我创建了以下 class 来存储任何大字符串以用作整个应用程序中任何方法的参数:
public class StringClob
{
public StringClob()
{
}
public StringClob(string value)
{
Value = value;
}
public virtual string Value { get; protected set; }
}
为了 link 具有 string
值的 NHibernateUtil.StringClob
类型,我想在不通知 table 的情况下为我的 class 创建一个映射仅映射 属性(我使用 FluentNHibernate 映射 class):
public class StringClobMap : ClassMap<StringClob>
{
public StringClobMap()
{
Id(x => x.Value, "VALUE").CustomType("StringClob").CustomSqlType("VARCHAR(MAX)").Length(int.MaxValue / 2);
}
}
现在假设按照@krdx 的示例,用法如下所示:
using(var session = _factory.OpenSession())
{
var q = session.Query<SomeEntity>()
.Where(s => s.IpAddressField.InetEquals(new StringClob("<large_string_here>")));
// ...
}
因此,通过将 StringClob
class 作为参数传递,NHibernate 获得映射中定义的自定义类型。
我希望我帮助那些仍在使用 NHibernate 3.3.2.GA.
的人
简而言之,我想创建一个自定义 IUserType
来表示 .NET 中的 IPAddress
(作为 postgresql
中的 inet
类型)并能够查询它通过 HQL
和 Linq
具有自定义函数。我在为 Linq
.
我目前拥有的:
A) 我可以映射它:
public class SomeEntityMapper : ClassMap<SomeEntity> {
public SomeEntityMapper() {
...
Map(x => x.IpAddressField)
.CustomSqlType("inet")
.CustomType<IPAddressUserType>()
...
}
}
下面的 IUserType 实现:
[Serializable]
public class IPAddressUserType : IUserType
{
public new bool Equals(object x, object y)
{
if (x == null && y == null)
return true;
if (x == null || y == null)
return false;
return x.Equals(y);
}
public int GetHashCode(object x)
{
if (x == null)
return 0;
return x.GetHashCode();
}
public object NullSafeGet(DbDataReader rs, string[] names, ISessionImplementor session, object owner)
{
if (names.Length == 0)
throw new InvalidOperationException("Expected at least 1 column");
if (rs.IsDBNull(rs.GetOrdinal(names[0])))
return null;
object value = rs[names[0]];
return value;
}
public void NullSafeSet(DbCommand cmd, object value, int index, ISessionImplementor session)
{
NpgsqlParameter parameter = (NpgsqlParameter) cmd.Parameters[index];
parameter.NpgsqlDbType = NpgsqlTypes.NpgsqlDbType.Inet;
if (value == null)
{
parameter.Value = DBNull.Value;
}
else
{
parameter.Value = value;
}
}
public object DeepCopy(object value)
{
if (value == null)
return null;
IPAddress copy = IPAddress.Parse(value.ToString());
return copy;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
if (cached == null)
return null;
if (IPAddress.TryParse((string)cached, out var address))
{
return address;
}
return null;
}
public object Disassemble(object value)
{
if (value == null)
return null;
return value.ToString();
}
public SqlType[] SqlTypes => new SqlType[] { new NpgsqlSqlType(DbType.String, NpgsqlTypes.NpgsqlDbType.Inet), };
public Type ReturnedType => typeof(IPAddress);
public bool IsMutable => false;
}
public class NpgsqlSqlType : SqlType
{
public NpgsqlDbType NpgDbType { get; }
public NpgsqlSqlType(DbType dbType, NpgsqlDbType npgDbType)
: base(dbType)
{
NpgDbType = npgDbType;
}
public NpgsqlSqlType(DbType dbType, NpgsqlDbType npgDbType, int length)
: base(dbType, length)
{
NpgDbType = npgDbType;
}
public NpgsqlSqlType(DbType dbType, NpgsqlDbType npgDbType, byte precision, byte scale)
: base(dbType, precision, scale)
{
NpgDbType = npgDbType;
}
}
}
B) 我可以使用 HQL 和我实现的自定义函数 (inet_equals
) 查询它。
using(var session = _factory.OpenSession()) {
var q = session.CreateQuery("from SomeEntity as s WHERE inet_equals(s.IpAddressField, :ip)");
q.SetParameter("ip", IPAddress.Parse("4.3.2.1"), NHibernateUtil.Custom(typeof(IPAddressUserType)));
}
自定义 Postgresql 方言扩展了下面的 HQL 函数实现:
public class CustomPostgresqlDialect : PostgreSQL83Dialect
{
public CustomPostgresqlDialect()
{
RegisterFunction("inet_equals", new SQLFunctionTemplate(NHibernateUtil.Boolean, "(?1::inet = ?2::inet)"));
}
}
C) 我希望能够像这样使用 LINQ 查询它:
using(var session = _factory.OpenSession()) {
var q = session.Query<SomeEntity>()
.Where(s => s.IpAddressField.InetEquals(IPAddress.Parse("4.3.2.1")));
}
以下 NHibernate 提供程序的自定义 LINQ 生成器:
public static class InetExtensions
{
public static bool InetEquals(this IPAddress value, IPAddress other)
{
throw new NotSupportedException();
}
}
public class InetGenerator : BaseHqlGeneratorForMethod
{
public InetGenerator()
{
SupportedMethods = new[]
{
ReflectHelper.GetMethodDefinition(() => InetExtensions.InetEquals(default(IPAddress), default(IPAddress)))
};
}
public override HqlTreeNode BuildHql(MethodInfo method, System.Linq.Expressions.Expression targetObject, ReadOnlyCollection<System.Linq.Expressions.Expression> arguments,
HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
HqlExpression lhs = visitor.Visit(arguments[0]).AsExpression();
HqlExpression rhs = visitor.Visit(arguments[1]).AsExpression();
return treeBuilder.BooleanMethodCall(
"inet_equals",
new[]
{
lhs,
rhs
}
);
}
}
public class ExtendedLinqToHqlGeneratorsRegistry :
DefaultLinqToHqlGeneratorsRegistry
{
public ExtendedLinqToHqlGeneratorsRegistry()
: base()
{
this.Merge(new InetGenerator());
}
}
不幸的是,当使用 LINQ
时,我得到了这个异常:
HibernateException: Could not determine a type for class: System.Net.IPAddress
有趣的是,如果我在 HQL
查询中省略 NHibernateUtil.Custom(typeof(IPAddressUserType))
参数,这与我得到的错误完全相同。
这让我相信我走在正确的轨道上,但我无法弄清楚我可能遗漏了什么。我假设我需要通知生成器内部的 NHibernate 这是一个自定义 UserType
(就像我通过 NHibernateUtil.Custom(typeof(IPAddressUserType))
参数对 HQL
查询所做的那样。
我找到了解决方案。
要提示 NHibernate 使用正确的 UserType,请使用:MappedAs
扩展方法,如下所示:
using(var session = _factory.OpenSession()) {
var q = session.Query<SomeEntity>()
.Where(s => s.IpAddressField.InetEquals(
IPAddress.Parse("4.3.2.1").MappedAs(NHibernateUtil.Custom(typeof(IPAddressUserType))
);
}
今年是 2019 年,我仍然停留在版本 3.3.2.GA 的 NHibernate 并且 MappedAs
扩展方法仅存在于版本 4.x以后。
我的场景是需要将一个大字符串作为参数传递给 HqlGeneratorForMethod
中受支持的方法。
我创建了以下 class 来存储任何大字符串以用作整个应用程序中任何方法的参数:
public class StringClob
{
public StringClob()
{
}
public StringClob(string value)
{
Value = value;
}
public virtual string Value { get; protected set; }
}
为了 link 具有 string
值的 NHibernateUtil.StringClob
类型,我想在不通知 table 的情况下为我的 class 创建一个映射仅映射 属性(我使用 FluentNHibernate 映射 class):
public class StringClobMap : ClassMap<StringClob>
{
public StringClobMap()
{
Id(x => x.Value, "VALUE").CustomType("StringClob").CustomSqlType("VARCHAR(MAX)").Length(int.MaxValue / 2);
}
}
现在假设按照@krdx 的示例,用法如下所示:
using(var session = _factory.OpenSession())
{
var q = session.Query<SomeEntity>()
.Where(s => s.IpAddressField.InetEquals(new StringClob("<large_string_here>")));
// ...
}
因此,通过将 StringClob
class 作为参数传递,NHibernate 获得映射中定义的自定义类型。
我希望我帮助那些仍在使用 NHibernate 3.3.2.GA.
的人