首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >用于HQL和Linq的NHibernate IUserType和自定义函数

用于HQL和Linq的NHibernate IUserType和自定义函数
EN

Stack Overflow用户
提问于 2018-10-25 22:19:53
回答 2查看 797关注 0票数 0

简而言之,我希望创建一个自定义IUserType来表示来自.NET的IPAddress (作为postgresql中的inet类型),并能够通过HQLLinq使用自定义函数对其进行查询。我在为Linq实现自定义函数时遇到了问题。

到目前为止,我所拥有的是:

A)我能够映射它:

代码语言:javascript
复制
public class SomeEntityMapper : ClassMap<SomeEntity> {
   public SomeEntityMapper() {
       ...
       Map(x => x.IpAddressField)
                 .CustomSqlType("inet")
                 .CustomType<IPAddressUserType>()
       ...
   }
}

IUserType实现如下:

代码语言:javascript
复制
[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)来查询它。

代码语言:javascript
复制
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函数实现如下:

代码语言:javascript
复制
 public class CustomPostgresqlDialect : PostgreSQL83Dialect
    {
        public CustomPostgresqlDialect()
        {
            RegisterFunction("inet_equals", new SQLFunctionTemplate(NHibernateUtil.Boolean, "(?1::inet = ?2::inet)"));
        }
    }

C)我希望能够像这样使用LINQ查询它:

代码语言:javascript
复制
using(var session = _factory.OpenSession()) {
   var q = session.Query<SomeEntity>()
           .Where(s => s.IpAddressField.InetEquals(IPAddress.Parse("4.3.2.1")));
   } 

下面是用于NHibernate提供程序的自定义LINQ生成器:

代码语言:javascript
复制
 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查询一样)。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-10-26 05:58:13

我找到了解决方案。

要提示NHibernate使用正确的UserType,请使用:MappedAs扩展方法,如下所示:

代码语言:javascript
复制
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))
            );

}

票数 3
EN

Stack Overflow用户

发布于 2019-12-05 21:20:51

今年是2019年,我仍然坚持使用NHibernate的3.3.2.GA版本,而MappedAs扩展方法只存在于4.x版本上。

我的场景是需要将一个大字符串作为参数传递给HqlGeneratorForMethod中受支持的方法。

我创建了以下类来存储任何大型字符串,以便在整个应用程序中用作任何方法的参数:

代码语言:javascript
复制
public class StringClob
{
    public StringClob()
    {
    }

    public StringClob(string value)
    {
        Value = value;
    }

    public virtual string Value { get; protected set; }
}

为了将NHibernateUtil.StringClob类型与string值链接起来,我想为我的类创建一个映射,而不只通知表映射属性(我使用FluentNHibernate来映射类):

代码语言:javascript
复制
public class StringClobMap : ClassMap<StringClob>
{
    public StringClobMap()
    {
        Id(x => x.Value, "VALUE").CustomType("StringClob").CustomSqlType("VARCHAR(MAX)").Length(int.MaxValue / 2);
    }
}

现在假设遵循@krdx的示例,用法如下所示:

代码语言:javascript
复制
using(var session = _factory.OpenSession()) 
{
    var q = session.Query<SomeEntity>()
                   .Where(s => s.IpAddressField.InetEquals(new StringClob("<large_string_here>")));

    // ...
}

因此,通过将StringClob类作为参数传递,NHibernate将获得映射中定义的自定义类型。

我希望我能帮助那些仍在使用NHibernate 3.3.2.GA的用户。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52991582

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档