I have a class that contains a property of type XmlElement.
The property is mapped as following:
<property name="XamlForm" column="XamlForm" type="KTN.Base.Data.Types.XmlType, KTN.Base.Data" />
The XmlType class:
[Serializable]
public class XmlType : IUserType
{
public new bool Equals(object x, object y)
{
XmlElement xdoc_x = (XmlElement)x;
XmlElement xdoc_y = (XmlElement)y;
if (xdoc_x == null && xdoc_y == null) return true;
if (xdoc_x == null || xdoc_y == null) return false;
return xdoc_y.OuterXml == xdoc_x.OuterXml;
}
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!");
XmlDocument document = new XmlDocument();
string val = rs[names[0]] as string;
if (val != null)
{
document.LoadXml(val);
return document.DocumentElement;
}
return null;
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
DbParameter parameter = (DbParameter)cmd.Parameters[index];
if (value == null)
{
parameter.Value = DBNull.Value;
return;
}
parameter.Value = ((XmlElement)value).OuterXml;
}
public object DeepCopy(object value)
{
if (value == null) return null;
XmlElement other = (XmlElement)value;
XmlDocument xdoc = new XmlDocument();
xdoc.LoadXml(other.OuterXml);
return xdoc.DocumentElement; /**/
}
public SqlType[] SqlTypes
{
get
{
return new SqlType[] { new SqlXmlType() };
}
}
public System.Type ReturnedType
{
get { return typeof(XmlDocument); }
}
public bool IsMutable
{
get { return true; }
}
public object Assemble(object cached, object owner)
{
return cached;
}
public object Disassemble(object value)
{
return value;
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object Replace(object original, object target, object owner)
{
// Changed return original to return target...
return target;
}
}
After invoke the function Merge from NHibernate, this property is null.
Any Idea?
Thanks in advance
NHibernate 3 has out of the box types for both XDocument and XmlDocument.
You shouldn't need to roll your own.
Related
It's generating "= null" instead of "is null" when querying a property using an IUserType. Gist of the biz rule is a null value in the database == OrderStatus.InProcess. Is there a work around or am I just doing it wrong?
Here is my IUserType implementation:
public class OrderStatusTypeMapper2 : IUserType
{
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var name = NHibernateUtil.String.NullSafeGet(rs, names[0]) as string;
if (string.IsNullOrEmpty(name))
return OrderStatus.InProcess;
return (OrderStatus)Enum.Parse(typeof(OrderStatus), name);
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
if (value == null)
{
NHibernateUtil.String.NullSafeSet(cmd, null, index);
return;
}
var status = (OrderStatus)value;
if (status == OrderStatus.InProcess)
{
NHibernateUtil.String.NullSafeSet(cmd, null, index);
return;
}
NHibernateUtil.String.NullSafeSet(cmd, status.ToString(), index);
}
public SqlType[] SqlTypes
{
get { return new[] { NHibernateUtil.String.SqlType }; }
}
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 DeepCopy(cached);
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
public Type ReturnedType
{
get { return typeof(OrderStatus); }
}
public bool IsMutable
{
get { return false; }
}
}
Mapped with:
Map(x => x.Status).CustomType<OrderStatusTypeMapper2>();
And then my usage:
var results = session.Query<Order>()
.Where(x => x.Status == OrderStatus.InProcess);
Console.WriteLine(results.Count());
Which generates:
select
cast(count(*) as INT) as col_0_0_
from
sales orderentit0_
where
orderentit0_.Status=#p0;
#p0 = NULL [Type: String (4000)]
And finally the desired (or similar) output:
select
cast(count(*) as INT) as col_0_0_
from
sales orderentit0_
where
orderentit0_.Status is null;
If what you're asking is, how do you check if the Status column is null, this should do the trick:
var results = session.QueryOver<Order>()
.WhereRestrictionOn(x => x.Status).IsNull;
Nhibernate won't pick up on the fact that your're using null and thus won't rewrite the sql, you have to explicitly use WhereRestrictionOn()and IsNull.
I have a sql table with a string column that could contain null, empty string or a double value.
I want to map this column to a C# property that is a double, defaulting to zero when the column is null or empty.
Can I do this with a fluent-nhibernate mapping?
I tried this:
Map(p => p.doubleProperty).CustomSqlType("varchar(20)").CustomType("double").Default("0");
and variations on this theme but I always get an error that the conversion failed.
For now I've gone with a custom type which allows me to use
Map(p=>p.doubleProperty).CustomType<DoubleString>();
Which will do for my current needs.
I'll leave the question open for now in case someone comes up with an easier solution.
Code for the DoubleString type is below.
public class DoubleString : 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 NullSafeGet(IDataReader rs, string[] names, object owner)
{
var valueToGet = NHibernateUtil.String.NullSafeGet(rs, names[0]) as string;
double returnValue = 0.0;
double.TryParse(valueToGet, out returnValue);
return returnValue;
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
object valueToSet = ((double)value).ToString();
NHibernateUtil.String.NullSafeSet(cmd, valueToSet, index);
}
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 DeepCopy(cached);
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
public SqlType[] SqlTypes
{
get
{
return new[] { new SqlType(DbType.String) };
}
}
public Type ReturnedType
{
get { return typeof(double); }
}
public bool IsMutable
{
get { return true; }
}
}
I would just map this to a string and in your entity have a property that is a double that does the conversion. Seems easier and cleaner than doing it in the mapping.
Maybe something like this:
public double Price
{
get
{
double price = -1.0;
Double.TryParse(stringProperty, out price);
return price;
}
set { stringProperty = value.ToString(); }
}
I solved kind of the same problem using two Properties which both referred to the same private variable. In my example the database contains a varchar which is NOT USED or ACTUAL, i therefor wanted a bool in my application.
private bool _myBool= true;
public virtual bool MyBool{ get { return _myBool; } set { _myBool= value; } }
public virtual string MyString
{
get { return _myBool ? "ACTUAL" : "NOT USED"; }
set { _myBool= value.Equals("NOT USED"); }
}
}
Searched various NHibernate lists and haven't come up with a definitive answer. The SQL2008 dialect doesn't appear to have support for the HierarchyID data type - new date and time types only.
Does anyone have a good implementation or an effective workaround? I'd really like to leverage HierarchyID in a new app of mine. Support for this interesting and powerful data type is sorely lacking in MS's own tools so I'm not shocked that NHibernate doesn't have support.
There are some approaches out there that I haven't delved into yet. Wondering if anyone has some experience in what works, what is more performant, etc.'
Full disclosure: I'm working with Castle ActiveRecord but this seems like an NHibernate issue.
I've given Needles' answer a test run. It's a very good answer but there are some changes needed to make it function (at least in .NET 4). Here's what I've come up with for my project:
Update: the following code can be download over at GitHub and will be updated there. NHiberntate.HierarchyId.UserType
SqlHierarchyId IUserType
namespace NHibernate.UserTypes
{
using SqlTypes;
using System;
using System.Data;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Types;
public class HierarchyId : IUserType
{
#region Properties
public SqlType[] SqlTypes
{
get { return new[] { NHibernateUtil.String.SqlType }; }
}
public Type ReturnedType
{
get { return typeof(SqlHierarchyId); }
}
public bool IsMutable
{
get { return true; }
}
#endregion Properties
#region Methods
new public 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 NullSafeGet(IDataReader rs, string[] names, object owner)
{
object prop1 = NHibernateUtil.String.NullSafeGet(rs, names[0]);
if (prop1 == null) return null;
return SqlHierarchyId.Parse(new SqlString(prop1.ToString()));
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
if (value == null)
((IDataParameter)cmd.Parameters[index]).Value = DBNull.Value;
else if (value is SqlHierarchyId)
((IDataParameter)cmd.Parameters[index]).Value = ((SqlHierarchyId)value).ToString();
}
public object DeepCopy(object value)
{
if (value == null) return null;
return SqlHierarchyId.Parse(((SqlHierarchyId)value).ToString());
}
public object Replace(object original, object target, object owner)
{
return DeepCopy(original);
}
public object Assemble(object cached, object owner)
{
return DeepCopy(cached);
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
#endregion Methods
}
}
Mapping
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DataLayer" namespace="NHibernate.Map">
<class name="NHibernate.Map.OrganizationUnit, DataLayer" table="`orgunit`">
<property name="HierarchyId" column="`ou_hid`" type="NHibernate.UserTypes.HierarchyId, DataLayer" />
...
</class>
</hibernate-mapping>
Object with HierarchyId
namespace NHibernate.Map
{
using Microsoft.SqlServer.Types;
public class OrganizationUnit
{
#region Fields
private SqlHierarchyId _hierarchyId;
...
#endregion Fields
#region Properties
public virtual SqlHierarchyId HierarchyId
{
get { return _hierarchyId; }
set { _hierarchyId = value; }
}
...
#endregion Properties
}
}
Disclaimer: Im not an NHibernate expert, however, we are using it with Fluent in an upcoming project which uses SQL Server 2008 R2 and Hierarchy IDs. The code below is what we are using currently on our dev environment and is not fully tested/refined. I copied the majority of the code from elsewhere (sorry I lost the link!)
You need to create a User Defined Type and then use it in your mappings. The mapping below is Fluent, Im not aware how to do it using ActiveRecord but Im guessing it should be similar!
User Defined Type
namespace YourNamespace {
public class SqlHierarchyIdUserType : IUserType {
public 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 NullSafeGet(IDataReader rs, string[] names, object owner) {
object prop1 = NHibernateUtil.String.NullSafeGet(rs, names[0]);
if(prop1 == null)
return null;
return SqlHierarchyId.Parse(new SqlString(prop1.ToString()));
}
public void NullSafeSet(IDbCommand cmd, object value, int index) {
if(value == null) {
((IDataParameter)cmd.Parameters[index]).Value = DBNull.Value;
} else {
if(value is SqlHierarchyId) {
SqlHierarchyId hId = (SqlHierarchyId)value;
((IDataParameter)cmd.Parameters[index]).Value = hId.ToString();
}
}
}
public object DeepCopy(object value) {
if(value == null)
return null;
var sourceTarget = (SqlHierarchyId)value;
SqlHierarchyId copy = SqlHierarchyId.Parse(sourceTarget.ToString());
return copy;
}
public object Replace(object original, object target, object owner) {
return DeepCopy(original);
}
public object Assemble(object cached, object owner) {
return DeepCopy(cached);
}
public object Disassemble(object value) {
return DeepCopy(value);
}
public SqlType[] SqlTypes {
get { return new[] { NHibernateUtil.String.SqlType }; }
}
public Type ReturnedType {
get { return typeof(SqlHierarchyId); }
}
public bool IsMutable {
get { return true; }
}
}
}
Fluent Mapping
Map(e => e.YourSqlHierarchyIdProperty)
.Column("YourSqlHierarchyIdFieldName")
.CustomType<SqlHierarchyIdUserType>();
Reading this post:
Castle ActiveRecord: Map to IUserType wihtin Class in C#
ActiveRecord uses a [Property] attribute to map User Defined Types. So for you it would look something like this:
public class YourDataObject {
[Property(ColumnType="YourNamespace.SqlHierarchyIdUserType, YourNamespace")
public virtual SqlHierarchyId YourSqlHierarchyIdProperty;
}
Hope it helps!
I'm dealing with a legacy database that has date and time fields as char(8) columns (formatted yyyyMMdd and HH:mm:ss, respectively) in some of the tables. How can i map the 2 char columns to a single .NET DateTime property? I have tried the following, but i get a "can't access setter" error of course because DateTime Date and TimeOfDay properties are read-only:
public class SweetPocoMannaFromHeaven
{
public virtual DateTime? FileCreationDateTime { get; set; }
}
.
mapping.Component<DateTime?>(x => x.FileCreationDateTime,
dt =>
{
dt.Map(x => x.Value.Date,
"file_creation_date");
dt.Map(x => x.Value.TimeOfDay,
"file_creation_time");
});
I have also tried defining a IUserType for DateTime, but i can't figure it out. I've done a ton of googling for an answer, but i can't figure it out still. What is my best option to handle this stupid legacy database convention? A code example would be helpful since there's not much out for documentation on some of these more obscure scenarios.
You need an ICompositeUserType to handle more than one column. You need to beef up the error checking, parsing formats, etc, but here is a starting point for you.
HTH,
Berryl
public class LegacyDateUserType : ICompositeUserType
{
public new bool Equals(object x, object y)
{
if (x == null || y == null) return false;
return ReferenceEquals(x, y) || x.Equals(y);
}
public int GetHashCode(object x) {
return x == null ? typeof (DateTime).GetHashCode() + 473 : x.GetHashCode();
}
public object NullSafeGet(IDataReader dr, string[] names, ISessionImplementor session, object owner)
{
if (dr == null) return null;
var datePortion = NHibernateUtil.String.NullSafeGet(dr, names[0], session, owner) as string;
var timePortion = NHibernateUtil.String.NullSafeGet(dr, names[1], session, owner) as string;
var date = DateTime.Parse(datePortion);
var time = DateTime.Parse(timePortion);
return date.AddTicks(time.Ticks);
}
///<summary>
/// Write an instance of the mapped class to a prepared statement. Implementors
/// should handle possibility of null values. A multi-column type should be written
/// to parameters starting from index.
///</summary>
public void NullSafeSet(IDbCommand cmd, object value, int index, ISessionImplementor session) {
if (value == null) {
// whatever
}
else {
var date = (DateTime) value;
var datePortion = date.ToString("your date format");
NHibernateUtil.String.NullSafeSet(cmd, datePortion, index, session);
var timePortion = date.ToString("your time format");
NHibernateUtil.String.NullSafeSet(cmd, timePortion, index + 1, session);
}
}
public object GetPropertyValue(object component, int property)
{
var date = (DateTime)component;
return property == 0 ? date.ToString("your date format") : date.ToString("your time format");
}
public void SetPropertyValue(object component, int property, object value)
{
throw new NotSupportedException("DateTime is an immutable object.");
}
public object DeepCopy(object value) { return value; }
public object Disassemble(object value, ISessionImplementor session) { return value; }
public object Assemble(object cached, ISessionImplementor session, object owner) { return cached; }
public object Replace(object original, object target, ISessionImplementor session, object owner) { return original; }
///<summary>Get the "property names" that may be used in a query.</summary>
public string[] PropertyNames { get { return new[] { "DATE_PORTION", "TIME_PORTION" }; } }
///<summary>Get the corresponding "property types"</summary>
public IType[] PropertyTypes { get { return new IType[] { NHibernateUtil.String, NHibernateUtil.String }; } }
///<summary>The class returned by NullSafeGet().</summary>
public Type ReturnedClass { get { return typeof(DateTime); } }
///<summary>Are objects of this type mutable?</summary>
public bool IsMutable { get { return false; } }
}
=== fluent mapping (assuming automapping w/override classes) ====
public void Override(AutoMapping<MyClass> m)
{
....
m.Map(x => x.MyDateTime).CustomType<LegacyDateUserType>();
}
I have a SQL Server DB with a recursive table:
MyTable:
ID : string PrimaryKey
Parent: string references MyTable - NOTNULL !!
and map with Fluent NHibernate to
class MyTable
{
public virtual string ID {get; set;}
public virtual MyTable Parent {get; set;}
}
My problem is that Parent should be null in my C# app if the column Parent is "" (empty string) in the database and vice versa. Unfortunately I can't change the column type to accept NULL!
I tried to use IEmptyInterceptor but I don't get it working.
Thanks in advance,
forki
You need to have an IUserType for the primary key column, which does the special NULL value handling.
public MyTableMap()
{
Id(x => x.EntryNo)
// Since the PK is a string, it must be assigned by the application.
.GeneratedBy.Assigned()
.SetAttribute("type", typeof(SpecialNullValueStringType).AssemblyQualifiedName);
References(x => x.Parent);
}
public class SpecialNullValueStringType : IUserType
{
#region IUserType Members
public bool IsMutable
{
get { return false; }
}
public Type ReturnedType
{
get { return typeof(string); }
}
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 value = (string) obj;
if (String.IsNullOrEmpty(value))
{
return null;
}
return value;
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
if (value == null)
{
((IDataParameter) cmd.Parameters[index]).Value = String.Empty;
}
else
{
((IDataParameter) cmd.Parameters[index]).Value = value;
}
}
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(string).GetHashCode() + 473 : x.GetHashCode();
}
#endregion
}
I found a (messy) way to get this working:
public class NullEventListener : IPreUpdateEventListener, IPreInsertEventListener, IPreLoadEventListener
{
#region IPreInsertEventListener Members
public bool OnPreInsert(PreInsertEvent preInsertEvent)
{
var instance = preInsertEvent.Entity as MyTable;
if (instance == null)
return false;
if (instance.Parent == null)
Set(preInsertEvent.Persister, preInsertEvent.State, "Parent", string.Empty);
return false;
}
#endregion
#region IPreLoadEventListener Members
public void OnPreLoad(PreLoadEvent preLoadEvent)
{
var instance = preLoadEvent.Entity as MyTable;
if (instance == null)
return;
try
{
// this is really messy!!
var parent = Get(preLoadEvent.Persister, preLoadEvent.State, "Parent") as MyTable;
if (parent == null || parent.ID == "")
throw new Exception("Set to null");
}
catch (Exception)
{
Set(preLoadEvent.Persister, preLoadEvent.State, "Parent", null);
}
return;
}
#endregion
#region IPreUpdateEventListener Members
public bool OnPreUpdate(PreUpdateEvent preUpdateEvent)
{
var instance = preUpdateEvent.Entity as MyTable;
if (instance == null)
return false;
if (instance.Parent == null)
Set(preUpdateEvent.Persister, preUpdateEvent.State, "Parent", string.Empty);
return false;
}
#endregion
private static void Set(IEntityPersister persister, object[] state, string propertyName, object value)
{
int index = Array.IndexOf(persister.PropertyNames, propertyName);
if (index == -1)
return;
state[index] = value;
}
private static object Get(IEntityPersister persister, object[] state, string propertyName)
{
int index = Array.IndexOf(persister.PropertyNames, propertyName);
if (index == -1)
return null;
return state[index];
}
}
Thanks and regards,
forki
I'd go for a IUserType which would convert empty string to nulls and vice versa. Two methods to pay attention to are NullSafeGet and NullSafeSet.
Not sure though how custom types integrate with Fluent NHibernate.
I tried to implement IUserType for my mapping:
public class MyCustomString : IUserType
{
public Type ReturnedType
{
get { return typeof (MyTable); }
}
public SqlType[] SqlTypes
{
get { return new[] {NHibernateUtil.String.SqlType}; }
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
object obj = NHibernateUtil.String.NullSafeGet(rs, names[0]);
if (obj == null) return null;
var s = (string) obj;
if (s == "")
return null;
using (ISession session = SessionHelper.OpenSession())
{
using (session.BeginTransaction())
{
return MyTable.Get(session, s);
}
}
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
((IDataParameter) cmd.Parameters[index]).Value = value == null ? 0 : ((MyTable) value).EntryNo;
}
...
}
and changed the mapping to
public MyTableMap()
{
Id(x => x.EntryNo);
Map(x => x.Parent).CustomTypeIs<MyCustomString>();
// References() doesn't allow CustomTypeIs()
// References(x => x.Parent).CustomTypeIs<MyCustomString>();
}
This seems to work for my root - but it always opens a session to get the right parent.
And it is not lazy - so it always retrieves all parents up to the root :-(
This can't be the right way. I don't want to open a new session - but otherwise I am returning a string and get a runtime type error.
Have you considered using the Null Object Pattern instead?