|
Working with XML Fields in NHibernateProposed solution involves three steps:
Some additional features:
ClassesXmlData.cs - main class of the solution. To be used as the type for a XML-valued property.
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using sdf.XPath;
namespace sdf.Persist
{
public class XmlData
{
private string _stringData = null;
XmlDocument _doc = null;
private XmlElement _xmlData = null;
private XmlNamespaceManager _nsmgr = null;
public XmlData()
{
}
public XmlData( XmlNamespaceManager nsmgr )
{
_nsmgr = nsmgr;
}
[XmlIgnore]
public string String
{
get
{
if( _stringData == null )
XmlToString();
return _stringData;
}
set
{
_stringData = value;
_xmlData = null;
Unsubscribe();
}
}
[XmlAnyElement, SkipNavigableRoot]
public XmlElement Xml
{
get
{
if( _xmlData == null )
StringToXml();
return _xmlData;
}
}
[XmlIgnore]
public XmlDocument Doc
{
get { return _doc; }
}
public void StringToXml()
{
// Unsubscribe from events
Unsubscribe();
// Create new document
if( _nsmgr != null )
_doc = new XmlDocument( _nsmgr.NameTable );
else
_doc = new XmlDocument();
_xmlData = _doc.CreateElement( "xml" );
if( _stringData != null && _stringData != string.Empty )
{
// Load XML from string
XmlParserContext context = new XmlParserContext( null, _nsmgr, null, XmlSpace.Default );
XmlTextReader reader = new XmlTextReader( _stringData, XmlNodeType.Element, context );
do
{
XmlNode nextChild = _doc.ReadNode( reader );
if( nextChild != null )
_xmlData.AppendChild( nextChild );
else
break;
}
while( true );
}
// Subscibe document change events
Subscribe();
}
public void XmlToString()
{
if( _xmlData != null )
{
StringWriter sw = new StringWriter();
SkipNsXmlTextWriter xtw = new SkipNsXmlTextWriter( sw, _nsmgr );
_xmlData.WriteContentTo( xtw );
xtw.Close();
_stringData = sw.ToString();
}
else
_stringData = string.Empty;
}
[XmlIgnore]
public XmlNamespaceManager NamespaceManager
{
get { return _nsmgr; }
set { _nsmgr = value; }
}
public string ValuleOf( string xpath )
{
return ValuleOf( xpath, NamespaceManager, "" );
}
public string ValuleOf( string xpath, XmlNamespaceManager nsmgr )
{
return ValuleOf( xpath, nsmgr, "" );
}
protected string ValuleOf( string xpath, XmlNamespaceManager nsmgr, string defaultValue )
{
if ( Xml.FirstChild == null )
return defaultValue;
XmlNode node = Xml.SelectSingleNode( xpath, nsmgr );
if ( node == null )
return defaultValue;
if ( node is XmlElement )
return node.InnerText;
if ( node is XmlAttribute )
return node.Value;
return node.Value;
}
public static explicit operator string( XmlData data )
{
return data.String;
}
public static explicit operator XmlElement( XmlData data )
{
return data.Xml;
}
private void Subscribe()
{
_doc.NodeChanged += new XmlNodeChangedEventHandler( OnDocumentChange );
_doc.NodeInserted += new XmlNodeChangedEventHandler( OnDocumentChange );
_doc.NodeRemoved += new XmlNodeChangedEventHandler( OnDocumentChange );
}
private void Unsubscribe()
{
if( _doc != null )
{
_doc.NodeChanged -= new XmlNodeChangedEventHandler( OnDocumentChange );
_doc.NodeInserted -= new XmlNodeChangedEventHandler( OnDocumentChange );
_doc.NodeRemoved -= new XmlNodeChangedEventHandler( OnDocumentChange );
}
}
private void OnDocumentChange( object sender, XmlNodeChangedEventArgs e )
{
_stringData = null;
}
}
}
XmlDataType.cs - custom NHibernate type that interfaces XmlData instance and a database.
using System;
using System.Data;
using log4net;
using NHibernate;
using NHibernate.SqlTypes;
namespace sdf.Persist.Hibernate
{
public class XmlDataType : IUserType
{
private static readonly ILog log = LogManager.GetLogger( typeof( XmlDataType ) );
private static SqlType[] _sqlTypes = new SqlType[] { new StringClobSqlType() };
public object NullSafeGet( IDataReader rs, string[] names, object owner )
{
string text;
object value = rs.GetValue( rs.GetOrdinal( names[0] ) );
if( value == DBNull.Value )
return null;
text = (string)value;
XmlData data = new XmlData();
data.String = text;
return data;
}
public void NullSafeSet( IDbCommand cmd, object value, int index )
{
if( value != null )
{
string str = ((XmlData)value).String;
if( str.Length > 0)
{
((IDataParameter)cmd.Parameters[index]).Value = str;
return;
}
}
((IDataParameter)cmd.Parameters[index]).Value = DBNull.Value;
}
public object DeepCopy( object value )
{
if( value == null )
return null;
XmlData data = (XmlData)value;
XmlData copy = new XmlData( data.NamespaceManager );
copy.String = data.String;
return copy;
}
public SqlType[] SqlTypes
{
get { return _sqlTypes; }
}
public Type ReturnedType
{
get { return typeof( XmlData ); }
}
public bool IsMutable
{
get { return true; }
}
bool IUserType.Equals( object x, object y )
{
if( x == null && y == null )
// Both of them are null.
return true;
XmlData d1 = x as XmlData;
XmlData d2 = y as XmlData;
if( d1 == null || d2 == null )
// 1. Both are XmlData, but not both null.
// 2. One of given files is not of the right type.
return false;
return d1.String.CompareTo( d2.String ) == 0;
}
}
}
SkipNsXmlTextWriter.cs - service type that allows for skipping unnecessary namespaces while converting XML node to string.
using System.IO;
using System.Xml;
namespace sdf.Persist
{
/// <summary>
/// Writes XML to the given <see cref="TextWriter"/>, omitting declarations
/// of namespaces presented in specified <see cref="XmlNamespaceManager"/>.
/// </summary>
public class SkipNsXmlTextWriter : XmlTextWriter
{
private XmlNamespaceManager _nsmgr;
private bool _catchNs;
private string _prefix;
private string _ns;
private string _forbiddenNs;
public SkipNsXmlTextWriter( TextWriter writer, XmlNamespaceManager nsmgr ):
base( writer )
{
_nsmgr = nsmgr;
_catchNs = false;
_prefix = null;
_ns = null;
}
public override void WriteStartAttribute( string prefix, string localName, string ns )
{
if( _nsmgr != null )
{
if( prefix == "xmlns" )
{
if( localName != string.Empty && localName != null )
{
_forbiddenNs = _nsmgr.LookupNamespace( localName );
if( _forbiddenNs != null )
{
_catchNs = true;
_prefix = localName;
_ns = null;
return;
}
}
}
else if( ( prefix == null || prefix == string.Empty ) && localName == "xmlns" )
{
_catchNs = true;
_prefix = null;
_ns = null;
return;
}
}
base.WriteStartAttribute( prefix, localName, ns );
}
public override void WriteEndAttribute()
{
if( _catchNs )
{
_catchNs = false;
_ns = _nsmgr.NameTable.Get( _ns );
if( _prefix != null )
{
if( _forbiddenNs == _ns )
// If xmlns:ns="the-ns" where namespace is given
return;
}
else if( _nsmgr.LookupPrefix( _ns ) != null )
// If xmlns="the-ns" whrer namespace is given
return;
base.WriteStartAttribute( "xmlns", _prefix, "http://www.w3.org/2000/xmlns/" );
base.WriteString( _ns );
}
base.WriteEndAttribute();
}
public override void WriteString( string text )
{
if( _catchNs )
{
if( _ns == null )
_ns = text;
else
_ns += text;
return;
}
base.WriteString( text );
}
public override void WriteStartElement( string prefix, string localName, string ns )
{
if( _nsmgr != null && ns != string.Empty && ns != null )
{
if( prefix != string.Empty && prefix != null )
{
if( _nsmgr.LookupNamespace( prefix ) == ns )
{
base.WriteStartElement( null, prefix + ":" + localName, null );
return;
}
}
else
{
string givenPrefix = _nsmgr.LookupPrefix( ns );
if( givenPrefix != null )
{
base.WriteStartElement( null, givenPrefix + ":" + localName, null );
return;
}
}
}
base.WriteStartElement( prefix, localName, ns );
}
}
}
ExampleIn your class define the property of XmlData type:
public class MyClass
{
private XmlData _data;
...
public XmlData Data
{
get
{
// Create empty data if accessed first time
// (for example, to use in newly created objects)
if( _data == null )
_data = new XmlData();
return _data;
}
set { _data = value; }
}
...
}
Map it with custom converter type specified:
<class name="myns.MyClass, myasm">
...
<property name="OtherData" column="otherData"
access="field.camelcase-underscore" type="sdf.Persist.Hibernate.XmlDataType, sdf" />
...
</class>
|
||||||||