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>();
Related
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 }; }
}
}
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.
I am using Nhibernate last version in my MVC project
I want to make a query on an object type names "Person" but person has many properties. In one of my queries I'd better not to have most of these properties.
Here is Person:
public partial class Person
{
virtual public User User { get; set; }
virtual public string FirstName { get; set; }
virtual public string LastName { get; set; }
virtual public int Gender { get; set; }
virtual public Contact Contact { get; set; }
virtual public BirthCertificate PersonBirthCertificate { get; set; }
}
In the query I just want first name and last name of the person.
So I decided to use ResultTransformer and implement it like this:
public class PersonResultTransformer : IResultTransformer
{
private Type result;
private PropertyInfo[] properties;
#region IResultTransformer Members
public IList TransformList(IList collection)
{
return collection;
}
public PersonResultTransformer (Type result, params string[] names)
{
this.result = result;
List<PropertyInfo> props = new List<PropertyInfo>();
foreach (string name in names)
{
props.Add(result.GetProperty(name));
}
properties = props.ToArray();
}
public object TransformTuple(object[] tuple, string[] aliases)
{
object instance = Activator.CreateInstance(result);
for (int i = 0; i < tuple.Length; i++)
{
var t = properties[i].Name;
var value = tuple[i].GetType().GetProperty(t).GetValue(tuple[i], null);
instance.GetType().GetProperty(t).SetValue(instance, value, null);
}
return instance;
}
#endregion
}
and for getting want I want with linq query :
var person = Repository<Person>.Find(p => p.LastName.Equals("Nadal")
, new PersonResultTransformer (typeof(Person), "FirstName", "LastName"));
////////////////
public IQueryable<T> Find(Expression<Func<T, bool>> expression, IResultTransformer transformer)
{
return Session.CreateCriteria<T>().SetResultTransformer(transformer).List<T>()
.AsQueryable().Where(expression);
}
Is it OK? can it be more customized? do I use it correctly?
It seems that it has a big problem: it gets all persons's first name and last name and then select from them, those having last name="nadal", and the performance is not good at all
first you have to delegate to a query which understands expressions
using NHibernate.Linq;
public IQueryable<T> Find(Expression<Func<T, bool>> expression)
{
return Session.Query<T>().Where(expression);
}
then you can
var personDTOs = Repository<Person>.Find(p => p.LastName.Equals("Nadal"))
.Select(person => new { person.FirstName, person.LastName });
you can shorten the resulttransformer a lot, but you cant use it with Linq2NHibernate.
public class PersonResultTransformer : IResultTransformer
{
private Type result;
private List<PropertyInfo> properties = new List<PropertyInfo>();
public IList TransformList(IList collection)
{
return collection;
}
public PersonResultTransformer (Type result, params string[] names)
{
this.result = result;
foreach (string name in names)
{
properties.Add(result.GetProperty(name));
}
}
public object TransformTuple(object[] tuple, string[] aliases)
{
object instance = Activator.CreateInstance(result);
for (int i = 0; i < tuple.Length; i++)
{
properties[i].SetValue(instance, tuple[i], null);
}
return instance;
}
}
You use Session.CreateCriteria<T>() but you do not add any criteria to it. So, the query is executed without criteria and then transformed and filtered.
You have to find a way to add your specifications to the Find method in a way that Nhibernate's ICriteria can handle, or use Linq to Nhibernate (beware of some issues though!).
I could be wrong - but I think you're overdoing this.
You could more easily utilize lazy loading to not fetch the properties you don't need.
Say I have a class like this:
public class MyClass
{
public int Id { get; set; }
public DateTime Date { get; set; }
public string String1 { get; set; }
public string String2 { get; set; }
public string String3 { get; set; }
public string String4 { get; set; }
}
Is it possible to get NHibernate to store it in the following schema?
CREATE TABLE [dbo].[MyClass](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Xml] [varchar](max) NOT NULL,
)
Where the Id maps to Id, but then all other fields get serialized into XML (or otherwise)? I don't mind if these other fields have to go on a child object like the below, if that helps:
public class MyClass
{
public int Id { get; set; }
public AllOtherOptions Options { get; set; }
}
public class AllOtherOptions
{
public DateTime Date { get; set; }
public string String1 { get; set; }
public string String2 { get; set; }
public string String3 { get; set; }
public string String4 { get; set; }
}
I am thinking about doing something similar for an upcoming project. The project requires collecting a lot of data but only a few elements need to be stored in a relational database. I haven't started experimenting but these are my thoughts so far.
You can map an XML data type by creating a type that implements IUserType. If the child class (AllOtherOptions) is serializable, you should be able to map the XML field as a private member in MyClass and serialize/deserialize AllOtherOptions as needed. You could either dynamically maintain the XML field (sounds like a lot of work) or create an interceptor to do it. My thinking is that MyClass would implement an interface such as
public interface IXmlObjectContainer
{
void SerializeChildObjects();
void DeSerializeChildObjects();
}
and the interceptor would call those methods as needed. That's a proof of concept idea. I would probably refine that by exposing pairs of xml fields and serializable objects to remove the work of serializing from IXmlObjectContainer implementers. Or maybe handle serialization through the XML field's get/set accessors.
More info:
Working with XML Fields in NHibernate
Another XML implementation of IUserType
I had the same idea to save object in XML column. My idea was other. I took code from links and changed it to generic IUserType implementation. So any field/prop which is [Serializable] can be saved in XML column.
public class XmlUserType<T> : IUserType where T : class
{
public new bool Equals(object x, object y)
{
return x == y;
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
if (names.Length != 1)
throw new InvalidOperationException("names array has more than one element. can't handle this!");
var val = rs[names[0]] as string;
if (string.IsNullOrWhiteSpace(val) == false)
{
return KRD.Common.GenericXmlSerialization.Deserialize<T>(val);
}
return null;
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var parameter = (DbParameter)cmd.Parameters[index];
T toSave = value as T;
if (toSave != null)
{
parameter.Value = KRD.Common.GenericXmlSerialization.Serialize(toSave);
}
else
{
parameter.Value = DBNull.Value;
}
}
public object DeepCopy(object value)
{
T toCopy = value as T;
if (toCopy == null)
return null;
string serialized = KRD.Common.GenericXmlSerialization.Serialize(toCopy);
return KRD.Common.GenericXmlSerialization.Deserialize<T>(serialized);
}
public object Replace(object original, object target, object owner)
{
throw new NotImplementedException();
}
public object Assemble(object cached, object owner)
{
var str = cached as string;
if (string.IsNullOrWhiteSpace(str) == false)
{
return null;
}
return KRD.Common.GenericXmlSerialization.Deserialize<T>(str);
}
public object Disassemble(object value)
{
var toCache = value as T;
if (toCache != null)
{
return KRD.Common.GenericXmlSerialization.Serialize(toCache);
}
return null;
}
public SqlType[] SqlTypes
{
get
{
return new SqlType[] { new SqlXmlType() };
}
}
public Type ReturnedType
{
get { return typeof(XmlDocument); }
}
public bool IsMutable
{
get { return true; }
}
}
public class SqlXmlType : SqlType
{
public SqlXmlType()
: base(DbType.Xml)
{
}
}
Usage with FluentNHibernate:
public class MainObject
{
public int Id { get; set; }
public ObjectAsXml Data { get; set; }
}
public class ObjectAsXml
{
public string Name { get; set; }
public int Date { get; set; }
public ObjectAsXml OtherObject { get; set; }
}
private class MainObjectMap : ClassMap<MainObject>
{
public MainObjectMap()
{
Id(id => id.Id);
Map(m => m.Data).CustomType<XmlUserType<ObjectAsXml>>().Nullable();
}
}
Maybe it will help somebody.
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; }