How do I map an Enumeration class without the discriminator being passed into the constructor? - nhibernate

Based on Jimmy's Enumeration classes idea I am wanting to see if I can avoid using the constructor to instantiate my type (which I assume is happening with the discriminator-value) but rather use a "factory method"-esque way of getting my instance mapped from the db.
Here is my type:
public class Impact : Enumeration
{
public static readonly Impact Carbon
= new Impact(1, "Carbon dioxide equivalent", CommonUnit.CO2e);
public static readonly Impact Energy
= new Impact(2, "Energy", CommonUnit.MJ);
public static readonly Impact Cost
= new Impact(3, "Cost", CommonUnit.Dollars);
public Impact(int index, string name, CommonUnit unit)
: base(index, name)
{
this.Unit = unit;
}
public CommonUnit Unit { get; private set; }
}
And here is the definition for Enumeration:
public class Enumeration : ValueObject
{
public Enumeration(int index, string displayName)
{
this.Index = index;
this.DisplayName = displayName;
}
public int Index { get; private set; }
public string DisplayName { get; private set; }
public override string ToString()
{
return this.DisplayName;
}
public static IEnumerable<T> GetAllFor<T>() where T : Enumeration
{
foreach (var publicStatic in typeof(T).GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly))
{
Enumeration item = null;
item = (Enumeration)publicStatic.GetValue(null);
yield return item as T;
}
}
public static T With<T>(int index) where T : Enumeration
{
return GetAllFor<T>().SingleOrDefault(i => i.Index == index);
}
}
ValueObject simply covers off Equality functionality.
Elsewhere I use the static methods to get items from this enum (kinda like how you could use the core Enumeration static methods):
impact = Impact.With<Impact>(index.ImpactId.Value);
This is pretty handy but I want to know if I can get NHibernate to do this too when rehydrating objects.
Can it be done and how?

With an NHibernate Custom Type:
public class EnumerationType<T> : PrimitiveType where T : Enumeration
{
public EnumerationType()
: base(new SqlType(DbType.Int32))
{
}
public override object Get(IDataReader rs, int index)
{
object o = rs[index];
var value = Convert.ToInt32(o);
return Enumeration.With<T>(value);
}
public override object Get(IDataReader rs, string name)
{
int ordinal = rs.GetOrdinal(name);
return Get(rs, ordinal);
}
public override Type ReturnedClass
{
get { return typeof(T); }
}
public override object FromStringValue(string xml)
{
return int.Parse(xml);
}
public override string Name
{
get { return "Enumeration"; }
}
public override void Set(IDbCommand cmd, object value, int index)
{
var parameter = (IDataParameter)cmd.Parameters[index];
var val = (Enumeration)value;
parameter.Value = val.Value;
}
public override string ObjectToSQLString(object value, Dialect dialect)
{
return value.ToString();
}
public override Type PrimitiveClass
{
get { return typeof(int); }
}
public override object DefaultValue
{
get { return 0; }
}
}
If you're doing an HBM.xml-based mapping, you can set the custom type like this:
<property name="Impact" column="Impact" type="Namespace.To.EnumerationType`1[[Impact, AssemblyWithDomainEnum]], AssemblyWithNHibCustomType"/>
Alternatively, if you're using Fluent NHibernate, you can create a convention to map all enumeration types without having to configure each one individually:
public class EnumerationTypeConvention : IPropertyConvention, IPropertyConventionAcceptance
{
private static readonly Type _openType = typeof(EnumerationType<>);
public void Apply(IPropertyInstance instance)
{
var closedType = _openType.MakeGenericType(instance.Property.PropertyType);
instance.CustomType(closedType);
}
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(x => typeof(Enumeration).IsAssignableFrom(x.Property.PropertyType));
}
}
And then add that convention however you like in your Fluent NHibernate configuration.

This seemed to work too, but perhaps Jimmy's way seems easier:
public class ImpactEnumType : IUserType
{
public SqlType[] SqlTypes
{
get
{
//We store our Impact in a single column in the database that can contain a int (for the index value)
SqlType[] types = new SqlType[1];
types[0] = new SqlType(DbType.Int32);
return types;
}
}
public Type ReturnedType
{
get { return typeof(Impact); }
}
public bool Equals(object x, object y)
{
// Impact is derived from ValueObject which implements Equals
return x.Equals(y);
}
public int GetHashCode(object x)
{
// as above
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
//We get the string from the database using the NullSafeGet used to get ints
int impactIndex = (int)NHibernateUtil.Int32.NullSafeGet(rs, names[0]);
// then pull the instance from the Enumeration type using the static helpers
return Impact.With<Impact>(impactIndex);
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
//Set the value using the NullSafeSet implementation for int from NHibernateUtil
if (value == null)
{
NHibernateUtil.Int32.NullSafeSet(cmd, null, index);
return;
}
value = (value as Impact).Index;
NHibernateUtil.Int32.NullSafeSet(cmd, value, index);
}
public object DeepCopy(object value)
{
//We deep copy the Impact by creating a new instance with the same contents
if (value == null) return null;
return Impact.With<Impact>((value as Impact).Index);
}
public bool IsMutable
{
get { return false; }
}
public object Replace(object original, object target, object owner)
{
//As our object is immutable we can just return the original
return original;
}
public object Assemble(object cached, object owner)
{
//Used for casching, as our object is immutable we can just return it as is
return cached;
}
public object Disassemble(object value)
{
//Used for casching, as our object is immutable we can just return it as is
return value;
}
}
My HBM XML:
<property name="Impact" column="ImpactIndex" type="namespace.childnamespace.ImpactEnumType, namespace.childnamespace" />

Related

Map SQLDecimal property in NHibernate

I'm trying to read a decimal (38,16) from a SQL Server DB and struggling. After much reading around I'm trying to implement a custom type for SQL Decimal with the following code:
public class BigDecimal : IUserType
{
public 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)
{
int index = rs.GetOrdinal(names[0]);
object result = rs.GetValue(index);
return result;
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
//Not got here yet
}
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[] {SqlTypeFactory.Decimal}; } }
public Type ReturnedType { get { return typeof (SqlDecimal); } }
public bool IsMutable { get { return false; } }
}
but the output of rs.GetValue is a decimal not at SQLDecimal which causes an OverflowException.
The class looks like this:
public class Billy
{
public BigDecimal TheNumber {get;set;}
}
and the mapping looks like this:
public class BillyMap : ClassMap<Billy>
{
public BillyMap()
{
Map(b=>b.TheNumber).CustomType<BigDecimal>();
}
}
Please can someone tell me where I'm going wrong.
I think you need to cast the reader to SqlDataReader so you can access either GetSqlDecimal() or GetSqlValue(), as GetValue() will convert to a basic .Net Framework type. From 'https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldatareader.getsqlvalue%28v=vs.110%29.aspx':
GetSqlValue returns data using the native SQL Server types. To retrieve data using the .NET Framework types, see GetValue.
In the end I made a something which performs a convert in the SQL and made it a Property Part and just use it on all the mapping files:
private const string DECIMAL_CONVERSION = "(CONVERT(decimal(28,6), [{0}]))";
private static string MapDecimalProperty(string fieldName)
{
return string.Format(DECIMAL_CONVERSION, fieldName.Trim('[',']'));
}
public static PropertyPart LongDecimal(this PropertyPart part, string fieldName)
{
return part.Formula(MapDecimalProperty(fieldName));
}
the on the mapping:
Map(ep => ep.BigDecimalField).EDWDecimal("[BigDecimalField]");
this works for now. I've informed the data architecture team that this is happening and they don't think that it will be a problem with any current data and will consider it for future developments.

NHibernate mapping by code: Mapping legacy non-int ID key to an int property in my entity getting "Could not determine type for" exception

I have a legacy database that uses zero-padded numbers stored as VARCHAR(8) for the primary key values in the tables. (For example, "00032982")
I want to map these to Int32 properties in my entity objects instead of string.
My first attempt was to create a class that implemented IUserType. But I'm mapping an Id, not a Property, and the mapping-by-code wouldn't compile: (Maybe I'm not doing that part right?)
Argument 1: cannot convert from 'TestQWebApi.Infrastructure.StringIntUserType' to 'NHibernate.Type.IIdentifierType'
I looked at IIdentifierType and it has over 40 members. I looked all over the place and it seemed like creating a custom type that subclasses NHibernate.Type.ImmutableType would be the solution. (...maybe this was my first mistake?)
So, I made a new class based on ImmutableType that zero-pads it in the Set() method:
public class LegacyIntKeyType : ImmutableType, ILiteralType, IDiscriminatorType
{
public LegacyIntKeyType() : base(SqlTypeFactory.GetString(8))
{
}
public override string Name
{
get { return "LegacyIntKeyType"; }
}
public override object Get(IDataReader rs, int index)
{
try
{
return Convert.ToInt32(rs[index]);
}
catch (Exception ex)
{
throw new FormatException(string.Format("Input string '{0}' was not in the correct format.", rs[index]), ex);
}
}
public override object Get(IDataReader rs, string name)
{
try
{
return Convert.ToInt32(rs[name]);
}
catch (Exception ex)
{
throw new FormatException(string.Format("Input string '{0}' was not in the correct format.", rs[name]), ex);
}
}
public override System.Type ReturnedClass
{
get { return typeof(Int32); }
}
public override void Set(IDbCommand rs, object value, int index)
{
((IDataParameter)rs.Parameters[index]).Value = string.Format("{0:d8}", value.ToString());
}
public object StringToObject(string xml)
{
return FromStringValue(xml);
}
public override object FromStringValue(string xml)
{
return Int32.Parse(xml);
}
public string ObjectToSQLString(object value, Dialect dialect)
{
return value.ToString();
}
public override string ToString(object value)
{
return value.ToString();
}
}
In my base entity class, I have it defined as an Int32:
public abstract class Entity
{
public virtual Int32 Id { get; protected set; }
}
Then I tried to map it:
public class ProcedureMap : ClassMapping<Procedure>
{
public ProcedureMap()
{
Table("procfile");
Id(x => x.Id, m =>
{
m.Column("key_proced");
m.Type(new LegacyIntKeyType());
});
Property(x => x.Name, m => m.Column("procname"));
}
}
Then I get this exception...which I have the feeling is probably indicating something else. Here's the stack trace:
[MappingException: Could not determine type for: TestQWebApi.Models.LegacyIntKeyType, TestQWebApi, for columns: NHibernate.Mapping.Column(key_proced)]
NHibernate.Mapping.SimpleValue.get_Type() +317
NHibernate.Cfg.XmlHbmBinding.ClassIdBinder.CreateIdentifierProperty(HbmId idSchema, PersistentClass rootClass, SimpleValue id) +301
NHibernate.Cfg.XmlHbmBinding.ClassIdBinder.BindId(HbmId idSchema, PersistentClass rootClass, Table table) +396
NHibernate.Cfg.XmlHbmBinding.RootClassBinder.Bind(HbmClass classSchema, IDictionary`2 inheritedMetas) +987
NHibernate.Cfg.XmlHbmBinding.MappingRootBinder.AddRootClasses(HbmClass rootClass, IDictionary`2 inheritedMetas) +104
NHibernate.Cfg.XmlHbmBinding.MappingRootBinder.AddEntitiesMappings(HbmMapping mappingSchema, IDictionary`2 inheritedMetas) +165
NHibernate.Cfg.XmlHbmBinding.MappingRootBinder.Bind(HbmMapping mappingSchema) +117
NHibernate.Cfg.Configuration.AddDeserializedMapping(HbmMapping mappingDocument, String documentFileName) +244
Thanks in advance!
you have to implement NHibernates IUserType
class MyIdUserType : IUserType
{
public object Assemble(object cached, object owner)
{
return DeepCopy(cached);
}
public object DeepCopy(object value)
{
return value;
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
bool IUserType.Equals(object x, object y)
{
return Object.Equals(x, y);
}
public virtual int GetHashCode(object x)
{
return (x != null) ? x.GetHashCode() : 0;
}
public bool IsMutable { get { return false; } }
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
return Int32.Parse((string)NHibernateUtil.String.Get(rs, names[0]));
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
NHibernateUtil.String.Set(cmd, string.Format("{0:d8}", value), index);
}
public object Replace(object original, object target, object owner)
{
return original;
}
public Type ReturnedType { get { return typeof(Int32); } }
public SqlType[] SqlTypes { get { return new []{ SqlTypeFactory.GetString(8) }; } }
}

Converting string to double in fluent-nhibernate mapping

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"); }
}
}

Mapping an IUserType to a component property in fluent NHibernate

I'm trying to implement an IUserType for states and country codes that will allow me to access both the two-letter code (what's stored in the database) as well as the full name. I'm following the example in the NHibernate 3.0 Cookbook (p. 225), but my problem is that my StreetAddress class is currently mapped as a component in my automapping configuration:
public override bool IsComponent(Type type)
{
return type == typeof(StreetAddress);
}
With this class identified as a component, I don't know how I can use an IUserType for the component class's property, since that class isn't explicitly mapped. There's nowhere that I could tell fluent NHibernate to use the IUserType specification.
#Firo was close, but there turned out to be a much easier solution. There were two steps here. First, I had to tell Fluent NHibernate not to map the State and Country classes, which reside in my domain layer:
public override bool ShouldMap(Type type)
{
return type.Name != "State" && type.Name != "Country";
}
Next, I simply had to create the conventions for the IUserType classes. This turned out to be easier than #Firo suggested:
public class CountryUserTypeConvention : UserTypeConvention<CountryType>
{
}
public class StateUserTypeConvention : UserTypeConvention<StateType>
{
}
The definition of those IUserTypes was pulled out of the cookbook referenced in the original question, but in case you don't want to read it:
public class CountryType : GenericWellKnownInstanceType<Country, string>
{
// The StateType is pretty much the same thing, only it uses "StateCode" instead of "CountryCode"
private static readonly SqlType[] sqlTypes =
new[] {SqlTypeFactory.GetString(2)};
public CountryType()
: base(new Countries(),
(entity, id) => entity.CountryCode == id,
entity => entity.CountryCode)
{
}
public override SqlType[] SqlTypes
{
get { return sqlTypes; }
}
}
And that derives from GenericWellKnownInstanceType:
[Serializable]
public abstract class GenericWellKnownInstanceType<T, TId> :
IUserType where T : class
{
private Func<T, TId, bool> findPredicate;
private Func<T, TId> idGetter;
private IEnumerable<T> repository;
protected GenericWellKnownInstanceType(
IEnumerable<T> repository,
Func<T, TId, bool> findPredicate,
Func<T, TId> idGetter)
{
this.repository = repository;
this.findPredicate = findPredicate;
this.idGetter = idGetter;
}
public Type ReturnedType
{
get { return typeof (T); }
}
public bool IsMutable
{
get { return false; }
}
public new bool Equals(object x, object y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (ReferenceEquals(null, x) ||
ReferenceEquals(null, y))
{
return false;
}
return x.Equals(y);
}
public int GetHashCode(object x)
{
return (x == null) ? 0 : x.GetHashCode();
}
public object NullSafeGet(IDataReader rs,
string[] names, object owner)
{
int index0 = rs.GetOrdinal(names[0]);
if (rs.IsDBNull(index0))
{
return null;
}
var value = (TId) rs.GetValue(index0);
return repository.FirstOrDefault(x =>
findPredicate(x, value));
}
public void NullSafeSet(IDbCommand cmd,
object value, int index)
{
if (value == null)
{
((IDbDataParameter) cmd.Parameters[index])
.Value = DBNull.Value;
}
else
{
((IDbDataParameter) cmd.Parameters[index])
.Value = idGetter((T) 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;
}
/// <summary>
/// The SQL types for the columns
/// mapped by this type.
/// </summary>
public abstract SqlType[] SqlTypes { get; }
}
The repositories for these classes are just a pair of ReadOnlyCollection of the State and Country objects. Again, from the cookbook:
public class States : ReadOnlyCollection<State>
{
// Truncated in the interest of brevity
public static State Arizona = new State("AZ", "Arizona");
public static State Florida = new State("FL", "Florida");
public static State California = new State("CA", "California");
public static State Colorado = new State("CO", "Colorado");
public static State Oklahoma = new State("OK", "Oklahoma");
public static State NewMexico = new State("NM", "New Mexico");
public static State Nevada = new State("NV", "Nevada");
public static State Texas = new State("TX", "Texas");
public static State Utah = new State("UT", "Utah");
public States() : base(new State[]
{
Arizona, Florida, California, Colorado,
Oklahoma, NewMexico, Nevada, Texas, Utah
}
)
{
}
}
Hopefully this helps someone out there.
i couldnt test it, but it should be possible using a convention
public class ComponentConvention : IComponentConvention, IComponentConventionAcceptance
{
public void Accept(IAcceptanceCriteria<IComponentInspector> criteria)
{
criteria.Expect(x => x.Type == typeof(StreetAddress);
}
public void Apply(IComponentInstance instance)
{
instance.Properties.First(p => p.Name == "CountrCode").CustomType<MyUserType>();
}
}

NHibernate: Using a custom type as a primary key

I have a legacy schema that contains tables with primary keys of type binary(16) -- its a MD5 hash of the other columns. NHibernate does not work with byte[] as a key since it does not implement Equals so I wrapped this in a custom type and provided NHibernate with an implementation of IUserType. Notice that MD5Hash is a struct and not a class.
public struct MD5Hash : IComparable, IComparable<MD5Hash>, IEquatable<MD5Hash> {
private readonly byte[] contents;
...
}
Everything worked fine until I created a many-to-one mapping to a type that uses MD5Hash as its key.
public class Referenced : IEquatable<Referenced> {
...
public virtual MD5Hash Id { get; set; }
public virtual string Name { get; set; } // must NOT be null
...
}
public class Referencer : IEquatable<Referencer> {
...
public virtual MD5Hash Id { get; set; }
public virtual Referenced Other { get; set } // may be null
...
}
When I attempt to load objects of type Referencer, NHibernate does not see a null value for the key when the row contains a NULL value so it attempts to instantiate an object of type
Referenced, assign it to Referencer, and update Referencer in the database. Since Referenced has a property, Name, which maps to a non-nullable column, NHibernate raises an exception. What I want is for NHibernate to set the Other property to null.
I could change the definition of MD5Hash to be a class instead of a struct but I have an unknown number of places in the code that probably assumes MD5Hash can never be null so I am looking for another solution.
The code for the custom type...
internal class MD5HashType : IUserType {
public SqlType[] SqlTypes {
get { return new[] { new SqlType(DbType.Binary, 16) }; }
}
public Type ReturnedType {
get { return typeof(MD5Hash); }
}
public new bool Equals(object x, object y) {
return Object.Equals(x, y);
}
public int GetHashCode(object x) {
return (null == x) ? 0 : x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner) {
var val = NHibernateUtil.Binary.NullSafeGet(rs, names[0]);
return (null == val || DBNull.Value == val) ? MD5Hash.Empty : new MD5Hash((byte[])val);
}
public void NullSafeSet(IDbCommand cmd, object value, int index) {
var val = (MD5Hash.Empty == ((MD5Hash)value)) ? null : ((MD5Hash)value).ToByteArray();
NHibernateUtil.Binary.NullSafeSet(cmd, val, index);
}
public object DeepCopy(object value) {
return value;
}
public bool IsMutable {
get { return false; }
}
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;
}
}
The problem appears to be that NHibernate can't tell that MD5Hash.Empty means no value. Have you tried creating custom event listeners such as the following to handle this? Something like:
public class CustomLoadListener : DefaultLoadEventListener {
public override void OnLoad(LoadEvent #event, LoadType loadType) {
if(#event.EntityId is MD5Hash) {
var id = (MD5Hash) #event.EntityId;
if(id == MD5Hash.Empty) {
#event.Result = new Referenced { Id = MD5Hash.Empty };
return;
}
}
base.OnLoad(#event, loadType);
}
}
public class CustomSaveOrUpdateListener : DefaultSaveOrUpdateEventListener {
public override void OnSaveOrUpdate(SaveOrUpdateEvent #event) {
var entity = #event.Entity as Referenced;
if(entity != null && entity.Id == MD5Hash.Empty) {
return;
}
base.OnSaveOrUpdate(#event);
}
}
You would then have to configure these listeners in your session factory via hibernate.cfg.xml:
<session-factory>
<!-- various properties -->
<listener type="load" class="NhHacking.CustomLoadListener, NhHacking"/>
<listener type="save-update" class="NhHacking.CustomSaveOrUpdateListener, NhHacking"/>
</session-factory>
If someone has a better idea of how to accomplish this, I would love to hear it.