I have the following table:
CREATE TABLE [dbo].[Data] (
[Id] UNIQUEIDENTIFIER NOT NULL,
[Data] XML NOT NULL,
);
I need to map it to the object:
class Data
{
public virtual Guid Id {get; set;}
public virtual StronglyTypedData Data {get; set;}
}
Where, StronglyTypedData is something like:
class StronglyTypedData
{
public string Name {get; set;}
public int Number {get; set;}
}
By default, XML columns are mapped to XmlDocument properties, but I would like XML serialization/deserialization to StronglyTypedData property to happen instead at mapping time.
What do I need to do to accomplish this?
You need to write an IUserType
that takes care of the conversion.
You could start from XmlDocType, which is the one actually converting from raw XML to a XmlDocument.
I was going to make this a comment on Diego's post, but it was too long and I wanted syntax highlighting. I modified the XmlDocType that Diego posted so that it would use xml serialization to and from a strongly typed object.
I made my own generic IUserType to handle the strong typing:
//you'll need these at the top of your file
//using System;
//using System.Collections.Generic;
//using System.Linq;
//using System.Text;
//using NHibernate.UserTypes;
//using NHibernate.SqlTypes;
//using System.Data;
//using System.Xml;
//using NHibernate.Type;
[Serializable]
public class XmlType<T> : MutableType
{
public XmlType()
: base(new XmlSqlType())
{
}
public XmlType(SqlType sqlType)
: base(sqlType)
{
}
public override string Name
{
get { return "XmlOfT"; }
}
public override System.Type ReturnedClass
{
get { return typeof(T); }
}
public override void Set(IDbCommand cmd, object value, int index)
{
((IDataParameter)cmd.Parameters[index]).Value = XmlUtil.ConvertToXml(value);
}
public override object Get(IDataReader rs, int index)
{
// according to documentation, GetValue should return a string, at list for MsSQL
// hopefully all DataProvider has the same behaviour
string xmlString = Convert.ToString(rs.GetValue(index));
return FromStringValue(xmlString);
}
public override object Get(IDataReader rs, string name)
{
return Get(rs, rs.GetOrdinal(name));
}
public override string ToString(object val)
{
return val == null ? null : XmlUtil.ConvertToXml(val);
}
public override object FromStringValue(string xml)
{
if (xml != null)
{
return XmlUtil.FromXml<T>(xml);
}
return null;
}
public override object DeepCopyNotNull(object value)
{
var original = (T)value;
var copy = XmlUtil.FromXml<T>(XmlUtil.ConvertToXml(original));
return copy;
}
public override bool IsEqual(object x, object y)
{
if (x == null && y == null)
{
return true;
}
if (x == null || y == null)
{
return false;
}
return XmlUtil.ConvertToXml(x) == XmlUtil.ConvertToXml(y);
}
}
//the methods from this class are also available at: http://blog.nitriq.com/PutDownTheXmlNodeAndStepAwayFromTheStringBuilder.aspx
public static class XmlUtil
{
public static string ConvertToXml(object item)
{
XmlSerializer xmlser = new XmlSerializer(item.GetType());
using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
{
xmlser.Serialize(ms, item);
UTF8Encoding textconverter = new UTF8Encoding();
return textconverter.GetString(ms.ToArray());
}
}
public static T FromXml<T>(string xml)
{
XmlSerializer xmlser = new XmlSerializer(typeof(T));
using (System.IO.StringReader sr = new System.IO.StringReader(xml))
{
return (T)xmlser.Deserialize(sr);
}
}
}
Then, finally, you can use Fluent.NHibernate to map your column like this:
public partial class MyTableEntityMap: ClassMap<MyTableEntity>
{
public MyTableEntityMap()
{
Table("MyTable");
//...
Map(x => x.MyStronglyTypedProperty).Column("SomeXmlTypeSqlColumn").CustomType(typeof(XmlType<TypeOfMyProperty>));
}
}