I have a database table (which I cannot change) which stores the date and time in separate fields but my class only has one DateTime property (Opened).
DateOpened 2011-05-10 00:00:00.000
TimeOpened 1899-12-30 09:53:00.000
In SQL I could just do
SELECT DateOpened + TimeOpened AS 'Opened'
How can I map this in Fluent NHibernate? I'm using Fluent Mapping.
I have tried
Map(x => x.Opened).Columns.Add(new string[] { "DateOpened", "TimeOpened" });
but I get the following error
property mapping has wrong number of columns: CBS.Tigerpaw.Data.ServiceOrder.Opened type: DateTime
if IUsertype is an option
public class DateTimeUserType : ImmutableUserType
{
public override object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var date = (DateTime)NHibernateUtil.DateTime.NullSafeGet(rs, names[0]);
var time = (DateTime)NHibernateUtil.DateTime.NullSafeGet(rs, names[0]);
return new DateTime(date.Year, ..., time.Hours, ...);
}
public override void NullSafeSet(IDbCommand cmd, object value, int index)
{
DateTime dt = (DateTime)(value ?? DateTime.MinValue);
NHibernateUtil.DateTime.NullSafeSet(cmd, dt.Date, index);
NHibernateUtil.DateTime.NullSafeSet(cmd, new DateTime(1899, 12, 30, dt.Hours, dt.Minutes, dt.Seconds), index + 1);
}
public override Type ReturnedType
{
get { return typeof(DateTime); }
}
public override SqlType[] SqlTypes
{
get { return new[] { SqlTypeFactory.DateTime, SqlTypeFactory.DateTime }; }
}
}
Map(x => x.Opened)
.Columns.Add("DateOpened", "TimeOpened")
.CustomType<DateTimeUserType>();
you can define that DateOpened + TimeOpened by using the .Map(...).Formula(...) & additional property in your class which has a private setter.
You might consider mapping the two separate columns independently and then having your class offer an additional (and un-mapped) "Opened" property that integrates their values. Regrettably, the two mapped properties would still be visible as public properties, since Fluent NHibernate requires this so that the lambda expressions in the mapping class can get at them.
Related
TLDR version: I'm having trouble getting my DDD domain model to work with NHibernate. If my value object itself contains a collection of value objects, I can't assign a new value without getting an NHibernate exception, and want to know what the best practice is in this situation.
Longer version:
Say I have an entity which contains a value object as a property, ValueObjectA, which itself contains a set of a different value objects of type ValueObjectB.
ValueObjectB only exists meaningfully as a property of ValueObjectA, i.e. if myEntity.ValueObjectA == null, it doesn't make sense for ValueObjectB to exist either.
I've written some example code to illustrate what I mean, with simplifications for brevity.
public class Entity
{
public int Id { get; private set; }
public ValueObjectA ValueObjectA { get; set; }
// Constructor: public Entity(ValueObjectA valueObjectA)
}
public class ValueObjectA : IEquatable<ValueObjectA>
{
public string X { get; private set; }
public ISet<ValueObjectB> ValueObjectBs { get; private set; }
// Constructor: public ValueObjectA(string x, ISet<ValueObjectB> valueObjectBs)
// Implementation of Equals/GetHahcode
}
public class ValueObjectB : IEquatable<ValueObjectB>
{
public int Y { get; private set; }
public int Z { get; private set; }
// Constructor: public ValueObjectB(int y, int z)
// Implementation of Equals/GetHahcode
}
I have a corresponding mapping class using mapping by code:
public class EntityMap : ClassMapping<Entity>
{
public EntityMap()
{
Table("Entity");
Id(x => x.Id, map => map.Generator(Generators.Identity));
Component(x => x.ValueObjectA, c =>
{
c.Property(x => x.X);
// Component relation is equilavent to <composite-element> in xml mappings
c.Set(x => x.ValueObjectBs, map =>
{
map.Table("ValueObjectB");
map.Inverse(true);
map.Cascade(Cascade.All | Cascade.DeleteOrphans);
map.Key(k => k.Column("Id"));
}, r => r.Component(ce =>
{
ce.Property(x => x.Y);
ce.Property(x => x.Z);
}));
});
}
}
The properties of ValueObjectA are mapped to the Entity table, but the properties of ValueObjectA.ValueObjectB are mapped to another table, since it is a one to many relationship. When a ValueObjectB is removed, I want that row to be deleted in the ValueObjectB table.
Since value objects are immutable, when I change the properties of entity.ValueObjectA, I should create a new instance of ValueObjectA. The problem is that the set of ValueObjectBs is a reference type, so when I try to save the entity with a different ValueObjectA, NHibernate will throw an exception because the original set that NHibernate is tracking is no longer referenced:
A collection with cascade="all-delete-orphan" was no longer referenced
by the owning entity instance.
Consider the following code:
var valueObjectBs_1 = new HashSet<ValueObjectB>
{
new ValueObjectB(1, 2),
new ValueObjectB(3, 4)
};
var valueObjectA_1 = new ValueObjectA("first", valueObjectBs_1);
var entity = new Entity(valueObjectA_1);
// Save entity, reload entity
var valueObjectBs_2 = new HashSet<ValueObjectB>
{
new ValueObjectB(1, 2)
};
var valueObjectA_2 = new ValueObjectA("second", valueObjectBs_2);
entity.ValueObjectA = valueObjectA_2;
// Save entity again
// NHIBERNATE EXCEPTION
I've managed to get around this by creating another ValueObjectA in order to preserve the reference to the set, e.g.
valueObjectA_1.ValueObjectBs.Remove(new ValueObjectB(3, 4));
entity.ValueObjectA = new ValueObjectA(valueObjectA_2.X, valueObjectA_1.ValueObjectBs);
However... that feels like a code smell - even if I wrote a custom setter for Entity.ValueObjectA, the implementation is starting to get complicated where the design is supposed to be simple.
public class Entity
{
// ...
private ValueObjectA valueObjectA;
public ValueObjectA ValueObjectA
{
// get
set
{
// Add/Remove relevant values from ValueObjectA.ValueObjectBs
valueObjectA = new ValueObjectA(value.X, ValueObjectA.ValueObjectBs);
}
}
}
What is the best practice in this type of situation? Or is this a sign that I'm trying to do something which violates the principles of DDD?
What you have is an anemic domain model.
You should replace public setters of the entity with methods that have meaningful names from the Ubiquitous language, that check the invariants and that do all the necessary cleanup in case of value objects replacements.
Although it may seem that things are more complicated this is payed back by the fact the now the entity is in full control about what happens with its internals. You now have full encapsulation.
I've defined a query in a class with a property, but am trying to build a fairly complex query using the property and have run into NHibernate telling me that it could not resolve property: DueDate.
My Query class looks like this:
public class SomeQuery {
public DateTime DueDate { get; private set; }
public SomeQuery(DateTime dueDate) {
DueDate = dueDate;
}
public QueryOver GetQueryOver() {
PrimaryObject po = null;
SubObject so = null;
return QueryOver.Of<PrimaryObject>(() => po)
.JoinAlias(() => so.SubObjects, () => so)
.Where(
Restrictions.Le(
DateProjections.DateDiff("d", () so.Value, () = DueDate),
0
)
);
}
}
I've implemented the DateProjections Class exactly as described in Andrew Whitaker's blog QueryOver Series - Part 7: Using SQL Functions
The contents of the PrimaryObject and SubObject aren't really important to the example except in the following:
public class PrimaryObject {
public virtual Guid Id { get; set; }
public List<SubObject> Implementations { get; set; }
}
public class SubObject {
public virtual Guid Id { get; set; }
public virtual string Value { get; set; }
}
For Mappings, you can assume that these fields are mapped to the database in sensible ways, as I don't feel like that is where the issue is.
When I try to use this query in a test, like the following:
var testDate = new DateTime(2015, 06, 01);
IEnumerable<PrimaryObject> result = repository.FindAll(new SomeQuery(testDate));
I get a NHibernate.QueryException:
NHibernate.QueryException : could not resolve property: DueDate of: PrimaryObject
Clearly, I've got an unmapped property, and that is causing the projection to have heartburn.
Looking for a minimal ceremony solution to getting the DueDate mapped. I've looked at Andrew's examples in QueryOver Series - Part 9: Extending QueryOver to Use Custom Methods and Properties, but it felt like a lot of ceremony.
I've also googled for solutions, but my google foo failed me..
Suggestions? Solutions?
The DateDiff implementation on the blog is assuming you wish to calculate the difference between database fields. This isn't what you want: you want to compare one database field with a constant.
You'll have to refactor the set of DateProjections methods to allow you to pass a constant as a parameter:
public static class DateProjections
{
private const string DateDiffFormat = "datediff({0}, ?1, ?2)";
// Here's the overload you need
public static IProjection DateDiff
(
string datepart,
Expression<Func<object>> startDate,
DateTime endDate
)
{
return DateDiff(
datePart,
Projections.Property(startDate),
Projections.Constant(endDate)
);
}
// Keeping Andrew Whitaker's original signature
public static IProjection DateDiff
(
string datepart,
Expression<Func<object>> startDate,
Expression<Func<object>> endDate
)
{
return DateDiff(
datePart,
Projections.Property(startDate),
Projections.Property(endDate)
);
}
// Added a function that's shared by
// all of the overloads
public static IProjection DateDiff(
string datepart,
IProjection startDate,
IProjection endDate)
{
// Build the function template based on the date part.
string functionTemplate = string.Format(DateDiffFormat, datepart);
return Projections.SqlFunction(
new SQLFunctionTemplate(NHibernateUtil.Int32, functionTemplate),
NHibernateUtil.Int32,
startDate,
endDate);
}
}
Now you can invoke it like so:
public QueryOver GetQueryOver() {
PrimaryObject po = null;
SubObject so = null;
return QueryOver.Of<PrimaryObject>(() => po)
.JoinAlias(() => so.SubObjects, () => so)
.Where(
Restrictions.Le(
DateProjections.DateDiff("d", () => so.Value, DueDate),
0
)
);
}
I am trying to create a mapping using Fluent NHibernate with Oracle 11g. This is the mapping I am creating:-
public class Abc : ClassMap<Abc>
{
public Abc()
{
Table("Abc");
DynamicUpdate();
Id(x => x.Id, "IdColumn").GeneratedBy.GuidComb();
Map(x => x.DecimalColumn, "DecimalColumn").Formula("TRUNC(DecimalColumn, 28)");
}
}
Now when I get the data using criteria query:-
var criteria = Session.QueryOver<Abc>().Where(x => x.Id == Id);
criteria.Future();
It throws Nhibernate.Exceptions.GenericADOException with InnerException (InvalidCastException) for the columns where decimal points are more than 28 so it requires truncation.
but If I remove the column from mapping such as (Note column name string is missing for DecimalColumn), it works.
public class Abc : ClassMap<Abc>
{
public Abc()
{
Table("Abc");
DynamicUpdate();
Id(x => x.Id, "IdColumn").GeneratedBy.GuidComb();
Map(x => x.DecimalColumn).Formula("TRUNC(DecimalColumn, 28)");
}
}
The problem is I have tests using SQLite and SQLite doesn't like it when the column name is omitted.
So if I fix it by removing the column name then I can't run tests. Any ideas ?
So, what I have figured out is as follows:-
Database column is float(126) which has more precision than supported by .NET hence, in some cases exception is thrown when .NET cannot handle the data (overflow). (Oracle number to C# decimal)
When a formula is specified in fluent mapping, a column becomes read only. The reason data can be read in the second case specified above is that Formula was being used but it also means that the same mapping cannot be used to insert/Update DecimalColumn. (How to map an NHibernate entity property using both a formula and a column specification)
As mentioned above, the type is database (FLOAT 126) which can have more precision(number of digits) than .NET supports. The ideal solution would be change the database column type of FLOAT(53) or something smaller if the data is never going to need more than FLOAT(53) (Precision 15 digits approx.) but if that is not possible then following can be done.
So the problem of overflow can be resolved without making the field read only (using formula in NHibernate mappings). A custom user type can be used. In the user type's "NullSafeGet" method use DataReader.GetDouble to read the data from the data reader and it works.
I am sharing the code below (which can definitely be improved and shouldn't be used in production without understanding and improving it as I am not sure all the methods are properly implemented).
namespace ABCD
{
using System;
using System.Data;
using NHibernate;
using NHibernate.SqlTypes;
using NHibernate.UserTypes;
public class MyDecimalType : IUserType
{
public bool IsMutable
{
get
{
return false;
}
}
public System.Type ReturnedType
{
get
{
return typeof(decimal);
}
}
public NHibernate.SqlTypes.SqlType[] SqlTypes
{
get
{
return new[] { SqlTypeFactory.GetSqlType(DbType.Decimal) };
}
}
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)
{
if (object.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(System.Data.IDataReader rs, string[] names, object owner)
{
var index = rs.GetOrdinal(names[0]);
var obj = rs.GetDouble(index);
return Convert.ToDecimal(obj);
}
public void NullSafeSet(System.Data.IDbCommand cmd, object value, int index)
{
if (value == null)
{
((IDataParameter)cmd.Parameters[index]).Value = DBNull.Value;
}
else
{
((IDataParameter)cmd.Parameters[index]).Value = value;
}
}
public object Replace(object original, object target, object owner)
{
return original;
}
}
}
Then use the custom user type in the mapping:-
public class Abc : ClassMap<Abc>
{
public Abc()
{
Table("Abc");
DynamicUpdate();
Id(x => x.Id, "IdColumn").GeneratedBy.GuidComb();
Map(x => x.DecimalColumn, "DecimalColumn").CustomType<MyDecimalType>();
}
}
I'm trying to use a convention to map UInt32 properties to a SQL Server 2008 database. I don't seem to be able to create a solution based on existing web sources, due to updates in the way Fluent NHibernate works - i.e. examples are out of date.
I'm trying to have NHibernate generate the schema (via ExposeConfiguration). I'm happy to have NHibernate map it to anything sensible (e.g. bigint).
Here's my code as it currently stands (which, when I try to expose the schema, fails due to SQL Server not supporting UInt32). Apologies for the code being a little long, but I'm not 100% sure what is relevant to the problem, so I'm erring on the side of caution. Most of it is based on this post.
The error reported is:
System.ArgumentException : Dialect does not support DbType.UInt32
I think I'll need a relatively comprehensive example, as I don't seem to be able to pull the pieces together into a working solution, at present.
FluentConfiguration configuration =
Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(connectionString))
.Mappings(mapping =>
mapping.AutoMappings.Add(
AutoMap.AssemblyOf<Product>()
.Conventions.Add<UInt32UserTypeConvention>()));
configuration.ExposeConfiguration(x => new SchemaExport(x).Create(false, true));
namespace NHibernateTest
{
public class UInt32UserTypeConvention : UserTypeConvention<UInt32UserType>
{
// Empty.
}
}
namespace NHibernateTest
{
public class UInt32UserType : IUserType
{
// Public properties.
public bool IsMutable
{
get
{
return false;
}
}
public Type ReturnedType
{
get
{
return typeof(UInt32);
}
}
public SqlType[] SqlTypes
{
get
{
return
new SqlType[]
{
SqlTypeFactory.Int32
};
}
}
// Public methods.
public object Assemble(object cached, object owner)
{
return cached;
}
public object DeepCopy(object value)
{
return value;
}
public object Disassemble(object value)
{
return value;
}
public new bool Equals(object x, object y)
{
return (x != null && x.Equals(y));
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
int? i = (int?)NHibernateUtil.Int32.NullSafeGet(rs, names[0]);
return (UInt32?)i;
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
UInt32? u = (UInt32?)value;
int? i = (Int32?)u;
NHibernateUtil.Int32.NullSafeSet(cmd, i, index);
}
public object Replace(object original, object target, object owner)
{
return original;
}
}
}
You'd need to map to an existing SQL Server datatype, of course.
Based on this question, "Best way to store UInt32 in Sql Server", your choices:
CLR datatype
bigint
decimal
a workaround using int.MinValue to map to int
this was posted on hibernate.org forums and nhusers list without much luck, so I thought I would try here.
Put simply, suppose I have a class:
class A
{
public virtual object SomeValue { get; set; }
}
the type of SomeValue is basically in the set of .NET IConvertible types (primitives like bool, byte, char, int16, double, float etc.), plus byte[] and string.
I am trying to create a nhibernate mapping for A to reflect this - so that I can basically set SomeValue to an arbitrary object (of one of the types above) and retrieve it later on on. My applogic will then reflect on it to find the type and behave accordingly.
So far I have tried Creating an implementation of IUserType to try and handle this. However I don't know what to return for the SqlType[] SqlTypes. I considered new SqlType(DbType.Object) but when I try to generate a schema from this I get a System.ArgumentException: Dialect does not support DbType.Object
If I try another data type then I get various cast exceptions when trying to convert the type. For instance if i use a DbType.Binary, and set someValue to an int32, upon attempting to commit I get System.InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.Byte[]'.
Is there a way to achieve this?
Attached code below for a non-working implementation of IUserType (based on http://intellect.dk/post/Implementing-custom-types-in-nHibernate.aspx )
public class DataElementType : IUserType
{
SqlType baseType = new SqlType(DbType.Binary);
public SqlType[] SqlTypes
{
get
{
return new[] { baseType };
}
}
public System.Type ReturnedType
{
get { return typeof(object); }
}
public new bool Equals(object x, object y)
{
if (x == null)
return false;
else
return x.Equals(y);
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
return rs[names[0]];
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var param = new SQLiteParameter(baseType.DbType, value);
cmd.Parameters.Insert(index, param);
}
public object DeepCopy(object value)
{
if (value == null) return null;
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;
}
}
It turns out to get around the problem with SqlType(DbType.Object) being unsupported by the Dialect, we make it supported by subclassing the SQLiteDialect with explicit support:
public class SQLiteDialectWithManifestTyping : SQLiteDialect
{
public SQLiteDialectWithManifestTyping() : base()
{
base.RegisterColumnType(DbType.Object, "NONE");
}
}
To use this dialect in Fluent, call Dialect() on your SQLiteConfiguration object. In NHibernate, set the configuration property dialect appropriatelly (see section 3.5.1 of the ref manual).
Then we can apply the above DataElementType implementation for our mappings (need to change the SqlTypes definition to this:
public SqlType[] SqlTypes
{
get
{
return new[] { new SqlType(DbType.Object) };
}
}
Notes:
It is not perfect. There is a tendency to upcast all discrete numbers to Int64 and floats to double.
There is no implicit way to store large unsigned values (e.g. values of ulong >= long.MaxValue) but this is a general sqlite problem (and possible a general ado.net problem?).
Due to the loss of compile time checking it is probably desireable to put some runtime checks in the NullSafeSet method to ensure the value is a primitive type. Attempting to store general objects seems to just cause the objects ToString() method to be called.