NHibernate linq query with IUserType - c#

In my project I use a IUserType (BooleanM1) that handles boolean values and writes -1 for true and 0 for false values to the database. So far everything works well. The mapping looks like
this:
<property name="Active" column="ACTIVE" type="Core.Persistence.NH.Types.BooleanM1,Core.Test"/>
So if I do a query like the following
var pList = Session.Query<Test>().Where( c => c.Active ).ToList();
an exception is thrown:
NHibernate.QueryException: Unable to render boolean literal value [.Where[Core.Test.Domain.Test]
(NHibernate.Linq.NhQueryable`1[Core.Test.Domain.Test], Quote((c, ) => (c.Active)), )]
---> System.InvalidCastException: Das Objekt des Typs "NHibernate.Type.CustomType" kann nicht
in Typ "NHibernate.Type.BooleanType" umgewandelt werden.
The BooleanM1 implementation is like this:
{
public class BooleanM1 : IUserType
{
public bool IsMutable
{
get { return false; }
}
public Type ReturnedType
{
get { return typeof(bool); }
}
public SqlType[] SqlTypes
{
get { return new[]{NHibernateUtil.Int16.SqlType}; }
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var obj = NHibernateUtil.String.NullSafeGet(rs, names[0]);
if(obj == null ) return false;
return ((string)obj == "-1" || (string)obj == "1") ? true : false;
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
if(value == null)
{
((IDataParameter) cmd.Parameters[index]).Value = DBNull.Value;
}
else
{
((IDataParameter) cmd.Parameters[index]).Value = (bool)value ? -1 : 0;
}
}
public object DeepCopy(object value)
{
return value;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return cached;
}
public object Disassemble(object value)
{
return value;
}
public new bool Equals(object x, object y)
{
if( ReferenceEquals(x,y) ) return true;
if( x == null || y == null ) return false;
return x.Equals(y);
}
public int GetHashCode(object x)
{
return x == null ? typeof(bool).GetHashCode() + 473 : x.GetHashCode();
}
}
Is this an known bug in the linq provider or is there something wrong with my UserType? Any help is appreciated.

I had a similar issue with a UserType that does pretty much the same thing. I found that explicitely stating the equality in my queries resolved the problem.
Try going from:
var pList = Session.Query<Test>().Where( c => c.Active ).ToList();
to:
var pList = Session.Query<Test>().Where( c => c.Active == true ).ToList();
For some reason NHibernate's Linq provider is then able to figure it out.

Have you solved this yet?
Not sure if it is the same issue but I had something similar where the database field was nullable and the custom type specifies that you are returning a "bool" -> It may need to be changed to "bool? for the return type"
public Type ReturnedType
{
get { return typeof(bool?); }
}

Fixed in version 4.1.0.
The user type should implement IEnhancedUserType to work correctly with the fix.
https://nhibernate.jira.com/browse/NH-2839

Related

NHibernate IUserType and custom functions for HQL and Linq

In short I wanted to create a custom IUserType to represent IPAddress from .NET (as an inet type in postgresql) and to be able to query it with custom functions via HQL and Linq. I have trouble with implementing a custom function for Linq.
What I have thus far:
A) I am able to map it:
public class SomeEntityMapper : ClassMap<SomeEntity> {
public SomeEntityMapper() {
...
Map(x => x.IpAddressField)
.CustomSqlType("inet")
.CustomType<IPAddressUserType>()
...
}
}
IUserType implementation below:
[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) I am able to query it using HQL and a custom function (inet_equals) I implemented.
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)));
}
Custom Postgresql Dialect extending the HQL functions implementation below:
public class CustomPostgresqlDialect : PostgreSQL83Dialect
{
public CustomPostgresqlDialect()
{
RegisterFunction("inet_equals", new SQLFunctionTemplate(NHibernateUtil.Boolean, "(?1::inet = ?2::inet)"));
}
}
C) I want to be able to query it using LINQ like so:
using(var session = _factory.OpenSession()) {
var q = session.Query<SomeEntity>()
.Where(s => s.IpAddressField.InetEquals(IPAddress.Parse("4.3.2.1")));
}
Custom LINQ Generator for NHibernate provider below:
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());
}
}
Unfortunately when using LINQ I get this exception:
HibernateException: Could not determine a type for class: System.Net.IPAddress
Interestingly, this is the exact same error I get if I omit the NHibernateUtil.Custom(typeof(IPAddressUserType)) parameter inside the HQL query.
Which leads me to believe I am on the right track, but I can't figure out what I may be missing exactly. I assume I need to inform NHibernate inside the Generator that this is a custom UserType (just like I did with the HQL query via the NHibernateUtil.Custom(typeof(IPAddressUserType)) parameter.
I found the solution.
To hint NHibernate to use the proper UserType use: MappedAs extension method, like so:
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))
);
}
The year is 2019 and I'm still stuck on version 3.3.2.GA of NHibernate and the MappedAs extension method exists only on version 4.x onwards.
My scenario was the need to pass a large string as a parameter to a supported method in an HqlGeneratorForMethod.
I created the following class to store any large string to use as a parameter of any method throughout the application:
public class StringClob
{
public StringClob()
{
}
public StringClob(string value)
{
Value = value;
}
public virtual string Value { get; protected set; }
}
In order to link the NHibernateUtil.StringClob type with the string value I had the idea to create a mapping for my class without informing table mapping just the property (I use FluentNHibernate to map class):
public class StringClobMap : ClassMap<StringClob>
{
public StringClobMap()
{
Id(x => x.Value, "VALUE").CustomType("StringClob").CustomSqlType("VARCHAR(MAX)").Length(int.MaxValue / 2);
}
}
And now hypothetically following the example of #krdx the usage would look like this:
using(var session = _factory.OpenSession())
{
var q = session.Query<SomeEntity>()
.Where(s => s.IpAddressField.InetEquals(new StringClob("<large_string_here>")));
// ...
}
Therefore by passing the StringClob class as a parameter, NHibernate gets the custom type defined in the mapping.
I hope I helped those who still use NHibernate 3.3.2.GA.

How to determine if a type is of type HashSet and how to cast it?

I have someone on GitHub asking for the ability to compare HashSets for my project on GitHub: https://github.com/GregFinzer/Compare-Net-Objects/. I need to be able to determine if a type is a hash set and then get the enumerator. I am not sure what to cast it to. Here is what I have for an IList:
private bool IsIList(Type type)
{
return (typeof(IList).IsAssignableFrom(type));
}
private void CompareIList(object object1, object object2, string breadCrumb)
{
IList ilist1 = object1 as IList;
IList ilist2 = object2 as IList;
if (ilist1 == null) //This should never happen, null check happens one level up
throw new ArgumentNullException("object1");
if (ilist2 == null) //This should never happen, null check happens one level up
throw new ArgumentNullException("object2");
try
{
_parents.Add(object1);
_parents.Add(object2);
//Objects must be the same length
if (ilist1.Count != ilist2.Count)
{
Differences.Add(string.Format("object1{0}.Count != object2{0}.Count ({1},{2})", breadCrumb,
ilist1.Count, ilist2.Count));
if (Differences.Count >= MaxDifferences)
return;
}
IEnumerator enumerator1 = ilist1.GetEnumerator();
IEnumerator enumerator2 = ilist2.GetEnumerator();
int count = 0;
while (enumerator1.MoveNext() && enumerator2.MoveNext())
{
string currentBreadCrumb = AddBreadCrumb(breadCrumb, string.Empty, string.Empty, count);
Compare(enumerator1.Current, enumerator2.Current, currentBreadCrumb);
if (Differences.Count >= MaxDifferences)
return;
count++;
}
}
finally
{
_parents.Remove(object1);
_parents.Remove(object2);
}
}
I believe it is enough works directly with the ISet<T>, the ICollection<T> or the IEnumerable<T> generic interfaces instead of the HashSet<T>. You can detect these types using the following approach:
// ...
Type t = typeof(HashSet<int>);
bool test1 = GenericClassifier.IsICollection(t); // true
bool test2 = GenericClassifier.IsIEnumerable(t); // true
bool test3 = GenericClassifier.IsISet(t); // true
}
//
public static class GenericClassifier {
public static bool IsICollection(Type type) {
return Array.Exists(type.GetInterfaces(), IsGenericCollectionType);
}
public static bool IsIEnumerable(Type type) {
return Array.Exists(type.GetInterfaces(), IsGenericEnumerableType);
}
public static bool IsISet(Type type) {
return Array.Exists(type.GetInterfaces(), IsGenericSetType);
}
static bool IsGenericCollectionType(Type type) {
return type.IsGenericType && (typeof(ICollection<>) == type.GetGenericTypeDefinition());
}
static bool IsGenericEnumerableType(Type type) {
return type.IsGenericType && (typeof(IEnumerable<>) == type.GetGenericTypeDefinition());
}
static bool IsGenericSetType(Type type) {
return type.IsGenericType && (typeof(ISet<>) == type.GetGenericTypeDefinition());
}
}
You need to loop over GetInterfaces() and check whether it implements an interface where IsGenericType is true and GetGenericTypeDefinition() == typeof(ISet<>)
Accepted answer does not distinguish from Dictionary type and possibly other subclasses of ICollection and IEnumerable. This works better:
Type t1 = typeof(HashSet<int>);
bool test1 = t1.IsGenericType &&
t1.GetGenericTypeDefinition() == typeof(HashSet<>); // true
Type t2 = typeof(Dictionary<int, string>);
bool test2 = t2.IsGenericType &&
t2.GetGenericTypeDefinition() == typeof(HashSet<>); // false
Type t3 = typeof(int);
bool test3 = t3.IsGenericType &&
t3.GetGenericTypeDefinition() == typeof(HashSet<>); // false
This is built in to HashSets... Use the method SymmetricExceptWith. There are other built in comparisons as well. See: http://msdn.microsoft.com/en-us/library/bb336848.aspx

fluent nhibernate error when converting 't' 'f' to boolean "String was not recognized as a valid Boolean."

I've got a problem when getting a record from the database with a boolean column. I can't change the database structure.
The database type is Character(1) (PostgreSQL) where they used 't' for true and 'f' for false. I have used the PostgreSQLDialect.
I've tried to put this in the hibernate-configuration
<property name="query.substitutions">1 't',0 'f'</property>
I've tried to override in the dialect
public override string ToBooleanValueString(bool value)
{
return value ? "t" : "f";
}
The mapping is:
Map(x => x.IsTemplate).Column("template_p");
Still not working,
Any help?
You may have to create your own user type here. Here is an example of creating your own:
http://lostechies.com/rayhouston/2008/03/23/mapping-strings-to-booleans-using-nhibernate-s-iusertype/
Your mapping would then become something like this:
Map(x => x.IsTemplate).Column("template_p").CustomType<MyCustomType>();
Edit:
You might also be able to use the standard YesNo type by doing something with your query-substitiutions. I haven't tested this but maybe something like this:
<property name="query.substitutions">yes 't', no 'f'</property>
Your mapping would look pretty much the same as I stated above except you would use the YesNo type.
Indeed Cole W, i made this customtype and it works like a charm
(source link: https://lostechies.com/rayhouston/2008/03/23/mapping-strings-to-booleans-using-nhibernate-s-iusertype/)
public class TFType : IUserType
{
public bool IsMutable
{
get { return false; }
}
public Type ReturnedType
{
get { return typeof(TFType); }
}
public SqlType[] SqlTypes
{
get { return new[]{NHibernateUtil.String.SqlType}; }
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var obj = NHibernateUtil.String.NullSafeGet(rs, names[0]);
if(obj == null ) return null;
var truefalse = (string) obj;
if( truefalse != "t" && truefalse != "f" )
throw new Exception(string.Format("Expected data to be 't' or 'f' but was '{0}'.", truefalse));
return truefalse == "t";
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
if(value == null)
{
((IDataParameter) cmd.Parameters[index]).Value = DBNull.Value;
}
else
{
var yes = (bool) value;
((IDataParameter)cmd.Parameters[index]).Value = yes ? "t" : "f";
}
}
public object DeepCopy(object value)
{
return value;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return cached;
}
public object Disassemble(object value)
{
return value;
}
public new bool Equals(object x, object y)
{
if( ReferenceEquals(x,y) ) return true;
if( x == null || y == null ) return false;
return x.Equals(y);
}
public int GetHashCode(object x)
{
return x == null ? typeof(bool).GetHashCode() + 473 : x.GetHashCode();
}
}
Haven't tried, but maybe this could work:
Map(x => x.IsTemplate).Formula("case when template_p = 't' then 1 else 0 end")
We are using postgres 8.3-8.4 with boolean values without any problems. However, when using ANTLR3 based HQL parser it started to get picky about boolean substitution, so we made our own dialect with the following override:
public override string ToBooleanValueString( bool value )
{
return value ? "true" : "false";
}
However, we used NHibernate.Dialect.PostgreSQL82Dialect, not the generic one. What postgres version are you running?
EDIT: Oh, sorry, I didn't follow that you hade a character(1) column. maybe this would help instead (with the quotes)?
return value ? "'t'" : "'f'";

How to map a oracle decimal date into a datetime property with Nhibernate

I am working with an oracle legacy database.
All the dates are saved in decimal fields with the following format YYYYMMDD.
Is there a way to transform this kind of type in a datetime (c#), maybe in the mapping file?
Just write a custom IEnhancedUserType that converts to and from the string representation.
That way it will be transparent to your application.
Recently I faced with the same problem and ended up with writing the following method:
public static DateTime? ConvertFromOracleDate(object oracleDate)
{
if (oracleDate == null)
throw new ArgumentNullException("oracleDate");
if (!(oracleDate is string))
throw new ArgumentException("oracleDate");
var sDate = (string) oracleDate;
if (sDate.Equals(String.Empty))
return null;
if (sDate.Length != 8)
throw new ArgumentOutOfRangeException("oracleDate");
sDate = sDate.Insert(6, "/");
sDate = sDate.Insert(4, "/");
var ci = new CultureInfo("en-US");
return Convert.ToDateTime(sDate, ci);
}
Hope this helps.
Ended up writing a custom IUserType.
For those who are interested here is the code. I haven't tried the writing process cause my table is read-only at the moment:
public class DecimalToDateUserType : IUserType
{
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
int? result = (int)NHibernateUtil.Int32.NullSafeGet(rs, names[0]);
if ((result != null)&&(result.Value>0))
{
var sDate = result.Value.ToString();
sDate = sDate.Insert(6, "/").Insert(4, "/");
var ci = new CultureInfo("en-US");
return Convert.ToDateTime(sDate, ci);
}
return (DateTime.MinValue);
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
if (value == null)
{
NHibernateUtil.Int32.NullSafeSet(cmd, null, index);
return;
}
if ((DateTime)value == DateTime.MinValue)
{
value = 0;
NHibernateUtil.Int32.NullSafeSet(cmd, value, index);
return;
}
int convertedValue = 0;
int.TryParse(((DateTime)value).ToString("yyyyMMdd"), out convertedValue);
value = convertedValue;
NHibernateUtil.Int32.NullSafeSet(cmd, value, index);
}
public object DeepCopy(object value)
{
if (value == null) return null;
return (value);
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return DeepCopy(cached);
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
public SqlType[] SqlTypes
{
get
{
SqlType[] types = new SqlType[1];
types[0] = new SqlType(DbType.Decimal);
return types;
}
}
public Type ReturnedType
{
get { return typeof(DateTime); }
}
public bool IsMutable
{
get { return false; }
}
public new bool Equals(object x, object y)
{
if (x == null || y == null) return false;
return x.Equals(y);
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
}
You can also do the transformation in your SQL query using
TO_DATE(TO_CHAR(semi_date_field),'yyyymmdd'))
Then you'll select a column with Oracle-datatype DATE. C# will undoubtedly treat those as datetimes.
Regards,
Rob.

How to define a CHAR (ORACLE) column in Nhibernate?

I am starting to work with Nhibernate and Oracle for a project.
The database is Oracle 9.2 and I can't change the schema or anything.
I am using NH3.0 and the Oracle.DataAccess.dll ver 2.111.7.20.
So far I've mapped a couple of table and have done some queries. Everything works pretty well.
I've bumped into a problem now and I don't know how to fix it.
The company who's designed the database thought it was a good idea to create all the alphanumeric fields as CHAR instead of VARCHAR or VARCHAR2.
I've mapped all these columns as String and my classes have String fields defined.
Later on I was trying to load an entity by its primary key, defined as CHAR(10) in Oracle.
The key I was trying to load is only 7 characters long
EG: 'CI00252'
Apparently my entity can't be loaded.
Profiling the query with NHProf I can see that my query is fine and if I try to execute it in Oracle Sql-Developer I get the resultset.
I can only manage to make it work if I pad my string like this 'CI00252 '.
Considering that most of the fields defined on the database are CHAR, it is impossible for me to pad everything before executing the query.
What can I do to fix this problem?
PS: I've seen some other people with the same problem here but I couldn't find any appropriate answer.
UPDATE:
I was reading a blog and this guy had a similar problem with another data-type.I've tried to adpapt the code
public SqlType[] SqlTypes
{
get
{
SqlType[] types = new SqlType[1];
types[0] = new SqlType(DbType.StringFixedLength);
return types;
}
}
**
and, apparently, everything is working but ... I don't know why.
using System;
using System.Data;
using NHibernate;
using NHibernate.SqlTypes;
using NHibernate.UserTypes;
namespace ConsoleOracleNhibernate.OracleTypes
{
public class CharUserType : IUserType
{
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
string resultString = (string)NHibernateUtil.String.NullSafeGet(rs, names[0]);
if (resultString != null)
return resultString.Trim();
return null;
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
if (value == null)
{
NHibernateUtil.String.NullSafeSet(cmd, null, index);
return;
}
value = ((String)value).Trim();
NHibernateUtil.String.NullSafeSet(cmd, value, index);
}
public object DeepCopy(object value)
{
if (value == null) return null;
return string.Copy((String)value);
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return DeepCopy(cached);
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
public SqlType[] SqlTypes
{
get
{
SqlType[] types = new SqlType[1];
types[0] = new SqlType(DbType.StringFixedLength);
return types;
}
}
public Type ReturnedType
{
get { return typeof(String); }
}
public bool IsMutable
{
get { return false; }
}
public new bool Equals(object x, object y)
{
if (x == null || y == null) return false;
return x.Equals(y);
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
}
}
and my mapping:
<key-property name="CustomerCode" column="ANCOCO" type="ConsoleOracleNhibernate.OracleTypes.CharUserType, ConsoleOracleNhibernate" length="10"></key-property>
Is there anyone here who can try to help me to understand what's happening?
My answer is in the update of the question.

Categories

Resources