Nhibernate ValueType Collection as delimited string in DB - nhibernate

I have a legacy db that I am mapping with Nhibernate.
And in several locations a list och strigs or domain objects are mapped as a delimited string in the database. Either 'string|string|string' in the value type cases and like 'domainID|domainID|domainID' in the references type cases.
I know I can create a dummy property on the class and map to that fields but I would like to do it in a more clean way, like when mapping Enums as their string representation with the EnumStringType class.
Is a IUserType the way to go here?
Thanks in advance
/Johan

I am using this:
public class DelimitedList : IUserType
{
private const string delimiter = "|";
public new bool Equals(object x, object y)
{
return object.Equals(x, y);
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var r = rs[names[0]];
return r == DBNull.Value
? new List<string>()
: ((string)r).SplitAndTrim(new [] { delimiter });
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
object paramVal = DBNull.Value;
if (value != null)
{
paramVal = ((IEnumerable<string>)value).Join(delimiter);
}
var parameter = (IDataParameter)cmd.Parameters[index];
parameter.Value = paramVal;
}
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 SqlType[] SqlTypes
{
get { return new SqlType[] { new StringSqlType() }; }
}
public Type ReturnedType
{
get { return typeof(IList<string>); }
}
public bool IsMutable
{
get { return false; }
}
}
SplitAndTrim is my own extension of string. Then in the class (using ActiveRecord for mapping):
[Property(ColumnType = "My.Common.Repository.UserTypes.DelimitedList, My.Common.Repository")]
public virtual IList<string> FooBar { get; set; }

Related

NHibernate - Is it possible to map columns into collection composite objects

Using NHibernate is it possible to map columns in a table to a collection of objects.
For example if I have a very badly designed database table with columns as such:
ClientID
ClientName
First_AmountPaid
Second_AmountPaid
Third_AmountPaid
Fourth_AmountPaid
Is it possible to map this to the following class structure where First_AmountPaid through to Fourth_AmountPaid have their own class implementation?
public class Client
{
public int ClientId { get; set; }
public string ClientName { get; set; }
public IList<AmountPaid> Amounts { get; set; }
}
public class AmountPaid
{
public decimal Amount { get; set; }
}
public class FirstAmountPaid : AmountPaid{ }
public class SecondAmountPaid : AmountPaid{ }
public class ThirdAmountPaid : AmountPaid{ }
public class FourthAmountPaid : AmountPaid{ }
Therefore giving a more meaningful code structure.
Thank you
i'm not sure why there are subclasses when the listposition already defines the order of the amounts
Map(x => x.Amounts)
.Columns.Add("First_AmountPaid", "Second_AmountPaid", "Third_AmountPaid", "Fourth_AmountPaid")
.CustomType<AmountPaidType>();
class AmountPaid : IUserType
{
public object Assemble(object cached, object owner)
{
return cached;
}
public object DeepCopy(object value)
{
return ((IList<AmountPaid>)x).Select(a => a.Clone()).ToList();
}
public object Disassemble(object value)
{
return value;
}
bool IUserType.Equals(object x, object y)
{
// assuming AmountPaid implements Equals
return ((IList<AmountPaid>)x).SequenceEquals((IList<AmountPaid>)y);
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public bool IsMutable
{
get { return true; }
}
public void NullSafeSet(cmd, value, index)
{
var list = (IList<AmountPaid>)value;
NHibernateUtil.Double.NullSafeSet(cmd, list[0].Amount, index);
NHibernateUtil.Double.NullSafeSet(cmd, list[1].Amount, index + 1);
NHibernateUtil.Double.NullSafeSet(cmd, list[2].Amount, index + 2);
NHibernateUtil.Double.NullSafeSet(cmd, list[3].Amount, index + 3);
}
public object NullSafeGet(rs, names, owner)
{
var list = new List<AmountPaid>();
foreach (var name in names)
{
list.Add(new AmountPaid((double)NHibernateUtil.Double.Get(rs, name)));
}
return list;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public Type ReturnedType
{
get { return typeof(IList<AmountPaid>); }
}
public SqlType[] SqlTypes
{
get { return new[] { SqlTypeFactory.Double, SqlTypeFactory.Double, SqlTypeFactory.Double, SqlTypeFactory.Double }; }
}
}

NHibernate: IUserType not working

I have this class that implements IUserType:
public class StringToIntType : IUserType
{
/// <summary>
/// mutable object = an object whose state CAN be modified after it is created
/// </summary>
public bool IsMutable
{
get { return false; }
}
public Type ReturnedType
{
get { return typeof(StringToIntType); }
}
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 s = (string)obj;
int i;
if (Int32.TryParse(s, out i))
return i;
return -1;
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
if (value == null)
{
((IDataParameter)cmd.Parameters[index]).Value = DBNull.Value;
}
else
{
var i = (int)value;
((IDataParameter)cmd.Parameters[index]).Value = i.ToString();
}
}
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(int).GetHashCode() + 473 : x.GetHashCode();
}
}
My mapping:
public BarausLangMap()
{
Table("BARAUSLANG");
Id(x => x.ula).CustomType<StringToIntType>();
Map(x => x.bezeichnung);
Map(x => x.sprache);
Map(x => x.la);
Where("la = 'SPE'");
}
My properties:
public virtual int ula { get; set; }
public virtual String bezeichnung { get; set; }
public virtual Int32? sprache { get; set; }
public virtual String la { get; set; }
Problem: When I do
var b = session.Get<BarausLang>(5);
It says
{NHibernate.TypeMismatchException: Provided id of the wrong type.
Expected: MobileServiceServer.Models.StringToIntType, got System.Int32
What is the problem? I thought nHibernate would call StringToIntType implicitely to convert from int to string and vice versa. I think that is the whole point. I thought StringToIntType was only for the mapping? How should I use it then?
You are correct, ReturnedType should return the type that NullSafeGet will return. The example code you linked to is incorrect, ReturnedType should return typeof(bool).
Also, getting the Equals method right is very important and I recommend a small change to your code:
public new bool Equals(object x, object y)
{
if (ReferenceEquals(x, y)) return true;
var xString = x as string;
var yString = y as string;
if (xString == null || yString == null) return false;
return xString.Equals(yString);
}
I MAY have found the problem:
public Type ReturnedType
above returns StringToIntType, and i THINK it should be int.
However, on:
http://lostechies.com/rayhouston/2008/03/23/mapping-strings-to-booleans-using-nhibernate-s-iusertype/
the method returns the type that implements IUserType.
Please confirm.

How do I map boolean fields in Fluent NHibernate to DB2 iSeries

I have the following code:
private static ISessionFactory CreateSessionFactory()
{
ISessionFactory factory = null;
var cfg = new Configuration();
// Do this to map bool true/false to DB2 char('0') or char('1')
var props = new Dictionary<string, string>();
props.Add("query.substitutions","true=1;false=0");
cfg.AddProperties(props);
cfg.DataBaseIntegration(x =>
{
x.ConnectionString = CONNECTION_STRING;
x.Dialect<DB2400Dialect>();
x.Driver<DB2400Driver>();
});
factory = Fluently.Configure(cfg)
.Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly()))
.BuildSessionFactory();
return factory;
}
In my POCO, I have the property:
public virtual bool QCCount { get; set; }
In my mapping, I have
Map(x => x.QCCount, "QCNT36");
In DB2, there are no bit fields, only char(1) with '0' or '1'.
As I understand it, the props.Add("query.substitutions","true=1;false=0"); should map these 0's and 1's to boolean POCO objects, however, it doesn't seem to be working.
Do I need to add something to the mapping of the field to tell it to use this?
I found a solution that seems to work.
http://lostechies.com/rayhouston/2008/03/23/mapping-strings-to-booleans-using-nhibernate-s-iusertype/
I changed the 'Y', 'N' to '0' and '1', then map the column and it's processing fine.
Code:
public class CharBoolType : IUserType
{
public bool IsMutable
{
get { return false; }
}
public Type ReturnedType
{
get { return typeof(CharBooleanType); }
}
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 != "1" && trueFalse != "0")
throw new Exception(string.Format("Expected data to be '0' or '1' but was '{0}'.", trueFalse));
return trueFalse == "1";
}
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();
}
}
Mapping:
Map(x => x.QCCount, "QCNT36").CustomType<CharBoolType>();
It seems the NHibernate DB2 dialect maps boolean to SMALLINT (https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/Dialect/DB2Dialect.cs):
RegisterColumnType(DbType.Boolean, "SMALLINT");
query.substitutions is for automatically replacing some tokens in your HQL queries with other tokens, and I don't think it affects reading.
If you want to use SMALLINT for boolean columns, you must inherit usertype class from BooleanType
Code:
public class IntBoolUserType : BooleanType, IUserType
{
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.GetHashCode();
}
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 override SqlType SqlType
{
get
{
return new SqlType(DbType.Int32);
}
}
public new SqlType[] SqlTypes
{
get { return new SqlType[] { SqlType }; }
}
public Type ReturnedType
{
get { return typeof(bool); }
}
public new void NullSafeSet(IDbCommand cmd, object value, int index)
{
var val = !((bool)value) ? 0 : 1;
NHibernateUtil.Int32.NullSafeSet(cmd, val, index);
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
return NHibernateUtil.Boolean.NullSafeGet(rs, names[0]);
}
public override void Set(IDbCommand cmd, object value, int index)
{
var val = !((bool)value) ? 0 : 1;
((IDataParameter)cmd.Parameters[index]).Value = val;
}
}
Mapping:
Property(x => x.IsDeleted, map => { map.NotNullable(true); map.Type<IntBoolUserType>(); });

How can I map a UInt64 field to a decimal(20, 0) column using Fluent nHibernate?

I'm using
MS SQL Server 2008R2
Fluent nHibernate 1.3
nHibernate 3.2
I have a UInt64 field in my domain that potentially takes up the entire UInt64 range. Since MS-SQL doesn't support unsigned integers we decided to store it in a decimal(20,0) column (at least for now). I've tried using CustomSqlType("decimal(20, 0)") on that column in my mappings but I'm still getting an error when I try to access that field that says the database doesn't support UInt64.
How can I map a UInt64 field to a decimal(20, 0) column using Fluent nHibernate?
I've tried
CustomSqlType("Decimal").Precision(20).Scale(0)
CustomSqlType("decimal(20, 0)")
CustomType()
and the permutations of CustomSqlType and CustomType.
Edit
The error it gives is "No mapping exists from DbType UInt64 to a known SqlDbType."
Edit 2
This works for the write side but breaks with an Invalid cast when values are read back out
.CustomSqlType("decimal").Precision(20).Scale(0)
.CustomType<NHibernate.Type.DoubleType>()
i would go with a user type
class UInt64UserType : ImmutableUserType
{
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var value = NHibernateUtil.Decimal.NullSafeGet(rs, names[0]);
return (value == null) ? 0 : Convert.ToUInt64(value);
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
decimal d = Convert.ToDecimal(value);
NHibernateUtil.Decimal.NullSafeSet(cmd, d, index);
}
public Type ReturnedType
{
get { return typeof(UInt64); }
}
public SqlType[] SqlTypes
{
get { return new[] { SqlTypeFactory.Decimal }; }
}
}
.CustomType<UInt64UserType>()
Edit: sorry forgot to note. i have a lot of usertypes so i implemented my own baseclasses
public abstract class ImmutableUserType : UserTypeBase
{
public override bool IsMutable
{
get { return false; }
}
public override object DeepCopy(object value)
{
return value;
}
public override object Replace(object original, object target, object owner)
{
return original;
}
public override object Assemble(object cached, object owner)
{
return cached;
}
public override object Disassemble(object value)
{
return value;
}
}
public abstract class UserTypeBase : IUserType
{
public new virtual bool Equals(object x, object y)
{
return EqualsHelper.Equals(x, y);
}
public virtual int GetHashCode(object x)
{
return (x == null) ? 0 : x.GetHashCode();
}
public abstract object Assemble(object cached, object owner);
public abstract object DeepCopy(object value);
public abstract object Disassemble(object value);
public abstract bool IsMutable { get; }
public abstract object NullSafeGet(System.Data.IDataReader rs, string[] names, object owner);
public abstract void NullSafeSet(System.Data.IDbCommand cmd, object value, int index);
public abstract object Replace(object original, object target, object owner);
public abstract Type ReturnedType { get; }
public abstract SqlType[] SqlTypes { get; }
}

Querying UserType's in NHibernate

I have the following scenario:
Let's say that my "Product" table in this legacy database has a "Categories" column of type string. This column stores the category ID's separated by some sort of ascii character. For instance: "|1|" (for category 1), "|1|2|3|" (for categories 1, 2, and 3), etc.
Instead of exposing a string property for that, I want to expose an IEnumerable, so that users of my Product class don't have to worry about parsing those values.
I'm creating a SelectedCatories type that's simply an IEnumerable, and my Product class looks like this:
public class Product
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual bool Discontinued { get; set; }
public virtual SelectedCategories Categories { get; set; }
}
I then created a SelectedCategoriesUserType class like so:
public class SeletedCategoriesUserType : IUserType
{
static readonly SqlType[] _sqlTypes = {NHibernateUtil.String.SqlType};
public bool Equals(object x, object y)
{
// Fix this to check for Categories...
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
return x.Equals(y);
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
object obj = NHibernateUtil.String.NullSafeGet(rs, names[0]);
if (obj == null) return null;
string[] stringCategories = obj.ToString().Split(new[] {'|'}, StringSplitOptions.RemoveEmptyEntries);
var categories = new Categories();
return
new SelectedCategories(
stringCategories.Select(
stringCategory => categories.Single(cat => cat.Id == int.Parse(stringCategory)))
.ToList());
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
if (value == null)
{
((IDataParameter) cmd.Parameters[index]).Value = DBNull.Value;
}
else
{
var theCategories = (SelectedCategories) value;
var builder = new StringBuilder();
builder.Append("|");
theCategories.ForEach(i => builder.AppendFormat("{0}|", i.Id.ToString()));
((IDataParameter) cmd.Parameters[index]).Value = builder.ToString();
}
}
public object DeepCopy(object value)
{
return value;
}
public object Replace(object original, object target, object owner)
{
throw new NotImplementedException();
}
public object Assemble(object cached, object owner)
{
throw new NotImplementedException();
}
public object Disassemble(object value)
{
throw new NotImplementedException();
}
public SqlType[] SqlTypes
{
get { return _sqlTypes; }
}
public Type ReturnedType
{
get { return typeof (SelectedCategories); }
}
public bool IsMutable
{
get { return false; }
}
}
I then want to build a query that gives me back any product that belongs in a specific category (say, category 2), matching both "|2|", and "|1|2|3|".
Right now, my naive implementation that barely makes my test pass looks like this:
public IEnumerable<Product> GetByCategory(Category category)
{
using (ISession session = NHibernateHelper.OpenSession())
{
return session
.CreateSQLQuery("select * from product where categories LIKE :category")
.AddEntity(typeof(Product))
.SetString("category", string.Format("%|{0}|%", category.Id))
.List()
.Cast<Product>();
}
}
My question is: what's the proper way to right that query?
A different way to do that ICriteria query would be this...
return Session
.CreateCriteria(typeof(Product), "product")
.Add(Expression.Sql(
"{alias}.categories LIKE ?",
string.Format("%|{0}|%", category.Id),
NHibernateUtil.String))
.List<Product>();
However, you may want to think about setting up a many-to-many table between Product and Category and setting up a collection of Categories in the Product class. You can still keep your field of concatenated Category Ids (I assume it's needed for legacy purposes), but tie it to the collection with something like this.
public virtual ISet<Category> Categories { get; private set; }
public virtual string CategoriesString
{
get { return string.Join("|", Categories.Select(c => c.Id.ToString()).ToArray()); }
}
Doing something like this will let you set foreign keys on your tables, and make the queries a bit easier to construct.
return Session
.CreateCriteria(typeof(Product), "product")
.CreateCriteria("product.Categories", "category")
.Add(Restrictions.Eq("category.Id", category.Id))
.List<Product>();