I have a table mapping with nullable FK constraint. In my fluent mapping I am doing something like so:
public enum PlayerPosition
{
None = 0,
Forward = 1
//etc
}
Entity
public virtual PlayerPosition? Position { get; set; }
Map(x => x.Position).Column("PlayerPositionId").CustomType< PlayerPosition>();
What I would like to happen is when PlayerPosition is set to "None" Nhibernate will insert null. I am not sure how to make that happen.
i would go for IUserType:
public virtual PlayerPosition Position { get; set; }
Map(x => x.Position).Column("PlayerPositionId").CustomType<PlayerPositionUserType>();
class PlayerPositionUserType : IUserType
{
public object NullSafeGet(IDBReader reader, string[] names, object owner)
{
int? positionvalue = NHibernateUtil.Int32.NullSafeGet(reader, names[0]);
return (positionvalue.HasValue) ? (PlayerPosition)positionvalue : PlayerPosition.None;
}
public void NullSafeSet(IDBCommand cmd, object value, int index)
{
var position = (PlayerPosition)value;
if (position == PlayerPosition.None)
NHibernateUtil.Int32.NullSafeSet(cmd, null, index);
else
NHibernateUtil.Int32.NullSafeSet(cmd, (int)position, index);
}
public Type ReturnType
{
get { return typeof(PlayerPosition); }
}
public SqlType[] SqlTypes
{
get { return new [] { SqlTypeFactory.Int32 } }
}
}
Related
The Setup
I am using FluentNHibernate 1.4.0 for my NHibernate 3.3.3-SP1 mappings in a .NET4.0 library. I'm using the "table-per-inheritance" approach for my type hierarchy as follows:
-- Different process types potentially use
-- different types of reference values
CREATE TABLE ProcessTypes
(Id INT PRIMARY KEY)
-- Contains reference values for value comparisons
CREATE TABLE ProcessReferenceValues
(Id INT PRIMARY KEY IDENTITY(1,1),
ProcessTypeId INT FOREIGN KEY REFERENCES ProcessTypes(Id),
FloatReferencesValue FLOAT NULL,
IntReferenceValue INT NULL)
// POCOs
class ProcessReferenceValues
{
public virtual int Id { get; set; }
public virtual ProcessTypes ProcessType { get; set; }
public virtual float? FloatReferenceValue { get; set; }
public virtual int? IntReferenceValue { get; set; }
}
class IntProcessReferenceValues : ProcessReferenceValues { }
class FloatProcessReferenceValues : ProcessReferenceValues { }
enum ProcessTypeName : int
{
IntProcess = 1,
FloatProcess = 2
}
class ProcessTypes
{
public virtual int Id { get; set; }
public virtual ProcessTypeName Name { get; set; }
}
// FluentNHibernate Mappings
class ProcessReferenceValuesMap
: FluentNHibernate.Mapping.ClassMap<ProcessReferenceValues>
{
public ProcessReferenceValuesMap()
{
string processTypeId = "ProcessTypeId";
this.Id(x => x.Id);
this.Map(x => x.FloatReferenceValue).Nullable();
this.Map(x => x.IntReferenceValue).Nullable();
// Here is the tricky bit
this.References(x => x.ProcessType, processTypeId);
this.DiscriminateSubClassesOnColumn(processTypeId);
}
}
class IntProcessReferenceValuesMap
: FluentNHibernate.Mapping.SubclassMap<IntProcessReferenceValues>
{
public IntProcessReferenceValuesMap()
{
this.DiscriminatorValue((int)ProcessTypeName.IntProcess);
}
}
class FloatProcessReferenceValuesMap
: FluentNHibernate.Mapping.SubclassMap<FloatProcessReferenceValues>
{
public FloatProcessReferenceValuesMap()
{
this.DiscriminatorValue((int)ProcessTypeName.FloatProcess);
}
}
class ProcessPeriodTypesMap : FluentNHibernate.Mapping.ClassMap<ProcessPeriodTypes>
{
public ProcessPeriodTypesMap()
{
this.ReadOnly();
this.Id(x => x.Id, "id");
this.Map(x => x.Name, "id").ReadOnly().CustomType<PeriodTypeName>();
}
}
The Problem
While reading from the database works like a charm - the appropriate sub classes are selected correctly - saving a new process reference value gives me an exception:
// Reading
var processType =
(from type in session.Query<ProcessTypes>()
where type.Name == ProcessTypeName.IntProcess
select type).FirstOrDefault(); // OK, finds the IntProcess
var referenceValues =
(from val in session.Query<ProcessReferenceValues>()
select val).ToList(); // OK, finds the appropriate subclasses
// Inserting
var processType = new ProcessTypes
{
Id = (int)ProcessTypeName.IntProcess
};
var referenceValue = new ProcessReferenceValues
{
FloatReferenceValue = 0.7f,
IntReferenceValue = null,
ProcessType = processType // Needs the appropriate ProcessType
};
session.Save(referenceValue); // <- BOOM!
Error dehydrating property value for ProcessReferenceValues.ProcessType
Invalid Index 2 for OleDbParameterCollection with Count=2.
bei System.Data.OleDb.OleDbParameterCollection.RangeCheck(Int32 index)
bei System.Data.OleDb.OleDbParameterCollection.GetParameter(Int32 index)
bei System.Data.Common.DbParameterCollection.System.Collections.IList.get_Item(Int32 index)
bei NHibernate.Type.Int32Type.Set(IDbCommand rs, Object value, Int32 index) in p:\nhibernate-core\src\NHibernate\Type\Int32Type.cs:Zeile 60.
bei NHibernate.Type.NullableType.NullSafeSet(IDbCommand cmd, Object value, Int32 index) in p:\nhibernate-core\src\NHibernate\Type\NullableType.cs:Zeile 182.
bei NHibernate.Type.NullableType.NullSafeSet(IDbCommand st, Object value, Int32 index, Boolean[] settable, ISessionImplementor session) in p:\nhibernate-core\src\NHibernate\Type\NullableType.cs:Zeile 122.
bei NHibernate.Type.ManyToOneType.NullSafeSet(IDbCommand st, Object value, Int32 index, Boolean[] settable, ISessionImplementor session) in p:\nhibernate-core\src\NHibernate\Type\ManyToOneType.cs:Zeile 50.
bei NHibernate.Persister.Entity.AbstractEntityPersister.Dehydrate(Object id, Object[] fields, Object rowId, Boolean[] includeProperty, Boolean[][] includeColumns, Int32 table, IDbCommand statement, ISessionImplementor session, Int32 index) in p:\nhibernate-core\src\NHibernate\Persister\Entity\AbstractEntityPersister.cs:Zeile 2410.
As always in hard times I summoned the gooragle and the problem seems to be that the this.DiscriminateSubClassesOnColumn(processTypeId) adds a conflict with the this.References(x => x.ProcessType, processTypeId) mapping. When I remove the former the insert is successful but I want the subclass mapping AND I also need to be able to set the ProcessReferenceValues.ProcessType when adding new instances of ProcessReferenceValues to distinguish the subclasses.
The Question
Is it possible to discriminate subclasses on a column and at the same time referencing that same column on the same type?
Help very much appreciated, there's got to be a way to do this ...
thx in advance!
classes and mappings without the problem
// POCOs
class ProcessValue
{
public virtual int Id { get; set; }
public abstract ProcessValueType Type { get; }
}
class IntProcessValue : ProcessValue
{
public virtual int? Value { get; set; }
public override ProcessValueType Type { get { return ProcessValueType.Int; } }
}
class FloatProcessValue : ProcessValue
{
public virtual float? Value { get; set; }
public override ProcessValueType Type { get { return ProcessValueType.Float; } }
}
enum ProcessValueType : int
{
Int = 1,
Float = 2
}
// FluentNHibernate Mappings
class ProcessValueMap : ClassMap<ProcessValue>
{
public ProcessValueMap()
{
string processTypeId = "ProcessValueTypeId";
Id(x => x.Id);
// just so you can query by type enum also. Querying by clr Type is already implemented
Map(x => x.Type, processTypeId).CustomType<ProcessValueType>().ReadOnly().Access.None();
DiscriminateSubClassesOnColumn(processTypeId);
}
}
class IntProcessValueMap : SubclassMap<IntProcessValue>
{
public IntProcessValueMap()
{
Map(x => x.Value).Nullable();
DiscriminatorValue((int)ProcessValueType.Int);
}
}
class FloatProcessValueMap : SubclassMap<FloatProcessValue>
{
public FloatProcessValueMap()
{
Map(x => x.Value).Nullable();
DiscriminatorValue((int)ProcessValueType.Float);
}
}
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 the following classes and mappings:
public enum VariationType
{
Base = 1,
RiderMain = 2,
RiderSpouse = 3,
RiderChild = 4,
Family = 5,
FamilyBase = 6
}
public class PlanParameter
{
private IDictionary<VariationType, PlanParameterDefaultValue> _defaultValues;
public PlanParameter()
{
ParameterContext = new Parameter();
}
public virtual Parameter ParameterContext { get; set; }
public virtual object DefaultValue { get; set; }
public virtual string DefaultValueString
{
get
{
return DefaultValue == null ? null : DefaultValue.ToString();
}
set
{
DefaultValue = value == null ? null : Convert.ChangeType(value, ParameterContext.Type);
}
}
public virtual IDictionary<VariationType, PlanParameterDefaultValue> DefaultValues
{
get
{
if (_defaultValues == null)
_defaultValues = new Dictionary<VariationType, PlanParameterDefaultValue>();
return _defaultValues;
}
}
}
class PlanParameterMap : ClassMap<PlanParameter>
{
public PlanParameterMap()
{
Id().GeneratedBy.Identity().Column("ID");
References(x => x.ParameterContext,"ParameterID");
Map(x => x.DefaultValueString);
HasMany(x=> x.DefaultValues)
.Access.CamelCaseField(Prefix.Underscore)
.KeyColumn("PlanParameterID").Inverse()
.AsMap("Variation")
.Cascade.AllDeleteOrphan();
}
}
public class PlanParameterDefaultValue
{
public virtual PlanParameter PlanParameter { get; set; }
public virtual object DefaultValue { get; set; }
public virtual string DefaultValueString
{
get
{
return DefaultValue == null ? null : DefaultValue.ToString();
}
set
{
DefaultValue = value == null ? null : Convert.ChangeType(value, PlanParameter.ParameterContext.Type);
}
}
}
class PlanParameterDefaultValueMap : ClassMap<PlanParameterDefaultValue>
{
public PlanParameterDefaultValueMap()
{
Id().GeneratedBy.Identity().Column("ID");
Map(x => x.DefaultValueString);
References(x => x.PlanParameter).Column("PlanParameterID");
}
}
My problem is very specific to the mapping of the
IDictionary<VariationType, PlanParameterDefaultValue> DefaultValues
The enum for some reason will not save, all that's saved in it's column is null
my only solution so far was to add a VariationType Property to the Entity and map it with a lambda formula, but i really don't need the VariationType in the Entity
Am i doing something wrong?
Thanks very much
The Inverse() tells NHibernate that the "mapped-to"-entity will take care of saving the key, and that the collection owner should not persist the Key. Try removing that flag.
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.
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.