NHibernate hbm mapping collection whith where condition on boolean property - nhibernate

I have an entity A with a bool property (lets call it BProp).
In a second entity there is a bag mapping of A elements with a where condition on BProp like this:
<bag name="MyBag" cascade="all-delete-orphan" inverse="false" where="BProp = 1">
<key column="Structure_Id" />
<one-to-many entity-name="A" />
</bag>
The problem is tha BProp = 1 works with SqlServer and Oracle but breaks PostgreSQL which needs a condition like
where="BProp = true"
Is there a clever way to create a single hbm.xml mapping for all the three db I have to suppport?

As the query substition does not seem to be a usable solution in this filter case, you may go for query interceptor (here a quite rough implementation) :
public class BooleanInterceptor : EmptyInterceptor, IInterceptor
{
public string TrueToken { get; set; }
public string FalseToken { get; set; }
NHibernate.SqlCommand.SqlString IInterceptor.OnPrepareStatement(NHibernate.SqlCommand.SqlString sql)
{
return sql.Replace("{TrueToken}", TrueToken).Replace("{FalseToken}", FalseToken);
}
}
Your mapping would then be where="BProp = {TrueToken}"
It would be used like this
var interceptor = new BooleanInterceptor ();
using (var session = sessionFactory.OpenSession(interceptor))
{
interceptor.FalseToken = "0"; // this replacement value should be taken from a config file
interceptor.TrueToken = "1"; // see above
// your code here
session.Close();
}
I bet there are way better solutions, but I hope this will help anyway
-------------------------- previous answer
I use to insert query substitutions in my NHibernate configuration, like this
var cfg = new Configuration();
cfg.Properties.Add("dialect", "NHibernate.Dialect.MsSql2005Dialect");
.
cfg.Properties.Add("query.substitutions", "true 1, false 0");
.
I guess you could have your xml mapping with where="BProp = true" and just insert the cfg property for query.substitions depending if the dialect is MsSql / Oracle or PosgresSQL

Related

Fluent NHibernate mapping using a condition

I have a class with a boolean property, like this:
public bool HasPermission { get; set; }
but in our database, in this column we have information in varchar, and we have values like:
"yes","y","no","n","0","1"
How can I map this values to a bool property?
Sorry, should have written this as a comment but not enough rep; have you tried query substitutions?
Something like
<property name="query.substitutions">
true 1, false 0, true 'y', false 'n'
</property>
should work
Edit after comment: as keyess has pointed out, for fluent nhibernate, something like this
var props = new Dictionary<string, string>();
props.Add("query.substitutions","true=1;false=0")
props.Add("query.substitutions","true=yes;false=no")
props.Add(...)
var sessionFactory = Fluently.Configure().BuildConfiguration().AddProperties(props);
should work (see this answer)
Also, another approach may be used based on IUserType

How do I change a child's parent in NHibernate when cascade is delete-all-orphan?

I have two entities in a bi-directional one-to-many relationship:
public class Storage
{
public IList<Box> Boxes { get; set; }
}
public class Box
{
public Storage CurrentStorage { get; set; }
}
And the mapping:
<class name="Storage">
<bag name="Boxes" cascade="all-delete-orphan" inverse="true">
<key column="Storage_Id" />
<one-to-many class="Box" />
</bag>
</class>
<class name="Box">
<many-to-one name="CurrentStorage" column="Storage_Id" />
</class>
A Storage can have many Boxes, but a Box can only belong to one Storage.
I have them mapped so that the one-to-many has a cascade of all-delete-orphan.
My problem arises when I try to change a Box's Storage. Assuming I already ran this code:
var storage1 = new Storage();
var storage2 = new Storage();
storage1.Boxes.Add(new Box());
Session.Create(storage1);
Session.Create(storage2);
The following code will give me an exception:
// get the first and only box in the DB
var existingBox = Database.GetBox().First();
// remove the box from storage1
existingBox.CurrentStorage.Boxes.Remove(existingBox);
// add the box to storage2 after it's been removed from storage1
var storage2 = Database.GetStorage().Second();
storage2.Boxes.Add(existingBox);
Session.Flush(); // commit changes to DB
I get the following exception:
NHibernate.ObjectDeletedException : deleted object would be re-saved by cascade (remove deleted object from associations)
This exception occurs because I have the cascade set to all-delete-orphan. The first Storage detected that I removed the Box from its collection and marks it for deletion. However, when I added it to the second Storage (in the same session), it attempts to save the box again and the ObjectDeletedException is thrown.
My question is, how do I get the Box to change its parent Storage without encountering this exception? I know one possible solution is to change the cascade to just all, but then I lose the ability to have NHibernate automatically delete a Box by simply removing it from a Storage and not re-associating it with another one. Or is this the only way to do it and I have to manually call Session.Delete on the box in order to remove it?
See http://fabiomaulo.blogspot.com/2009/09/nhibernate-tree-re-parenting.html
Basically, it boils down to this... You need to define a custom collection type for NHibernate that re-defines what it means to be an orphan. NHibernate's default behavior is to do just as you discovered - to consider a child to be orphaned if it has been removed from the parent. Instead, you need NHibernate to test the child to see if it has been assigned to a new parent. NHibernate does not do this by default because it would require additional information on the one-to-many mapping - it would need to know the name of the corresponding many-to-one property on the child.
Change your Storage mapping to look like this:
<class name="Storage">
<bag name="Boxes" cascade="all-delete-orphan" inverse="true" collection-type="StorageBoxBag">
<key column="Storage_Id" />
<one-to-many class="Box" />
</bag>
</class>
Define a new type named StorageBoxBag (note - this code is written against NHibernate 2.1 - if you are using NH3 you may have to tweak this a bit):
public class StorageBoxBag : IUserCollectionType
{
public object Instantiate(int anticipatedSize)
{
return new List<Box>();
}
public IPersistentCollection Instantiate(ISessionImplementor session, ICollectionPersister persister)
{
return new PersistentStorageBoxBag(session);
}
public IPersistentCollection Wrap(ISessionImplementor session, object collection)
{
return new PersistentStorageBoxBag(session, (IList<Box>)collection);
}
public IEnumerable GetElements(object collection)
{
return (IEnumerable)collection;
}
public bool Contains(object collection, object entity)
{
return ((IList<Box>)collection).Contains((Box)entity);
}
public object IndexOf(object collection, object entity)
{
return ((IList<Box>) collection).IndexOf((Box) entity);
}
public object ReplaceElements(object original, object target, ICollectionPersister persister, object owner, IDictionary copyCache, ISessionImplementor session)
{
var result = (IList<Box>)target;
result.Clear();
foreach (var box in (IEnumerable)original)
result.Add((Box)box);
return result;
}
}
... and a new type named PersistentStorageBoxBag:
public class PersistentStorageBoxBag: PersistentGenericBag<Box>
{
public PersistentStorageBoxBag(ISessionImplementor session)
: base(session)
{
}
public PersistentStorageBoxBag(ISessionImplementor session, ICollection<Box> original)
: base(session, original)
{
}
public override ICollection GetOrphans(object snapshot, string entityName)
{
var orphans = base.GetOrphans(snapshot, entityName)
.Cast<Box>()
.Where(b => ReferenceEquals(null, b.CurrentStorage))
.ToArray();
return orphans;
}
}
The GetOrphans method is where the magic happens. We ask NHibernate for the list of Boxes that it thinks are orphans, and then filter that down to only the set of Boxes that actually are orphans.

Why does NHibernate delete then insert composite-elements on select?

Can someone explain this little mystery to me about how NHibernate handles composite elements.
I have classes that look like this;
public class Blog
{
public virtual int Id
{
get;
private set;
}
public virtual ISet<Comment> Comments
{
get;
set;
}
}
public class Comment
{
public virtual string CommentText
{
get;
set;
}
public virtual DateTime Date
{
get;
set;
}
}
and mappings like this;
<class name="Blog" table="blog">
<id name="Id" column="id" unsaved-value="0">
<generator class="hilo"/>
</id>
<set name="Comments" table="blog_comments">
<key column="blog_id" />
<composite-element class="Comment">
<property name="CommentText" column="comment" not-null="true" />
<property name="Date" column="date" not-null="true" />
</composite-element>
</set>
</class>
However when i perform a select like this;
using (ITransaction transaction = session.BeginTransaction())
{
Blog blog = session.CreateCriteria(typeof(Blog))
.SetFetchMode("Comments", FetchMode.Eager)
.Add(Expression.IdEq(2345))
.UniqueResult();
transaction.Commit();
}
NHibernate issues a select with a join to get the blog with posts BUT then deletes all comments and then inserts the comments! Why is it doing this? If i do not use a transaction then it will ONLY perform the select and not the DELETE and INSERT as I would expect. What am I missing? I am using NHibernate 2.0
I think you need to override Equals() and GetHashCode() on Comment. NHibernate doesn't have an ID to go on for entity equality so you have to define what makes a comment entity equal to another comment.
Could be wrong :)
Edit
From nhibernate.info (8.2)
Note: if you define an ISet of composite elements, it is very important to implement Equals() and GetHashCode() correctly.
And an example of implementing Equals / GetHashCode from nhibernate.info (4.3)
public class Cat
{
...
public override bool Equals(object other)
{
if (this == other) return true;
Cat cat = other as Cat;
if (cat == null) return false; // null or not a cat
if (Name != cat.Name) return false;
if (!Birthday.Equals(cat.Birthday)) return false;
return true;
}
public override int GetHashCode()
{
unchecked
{
int result;
result = Name.GetHashCode();
result = 29 * result + Birthday.GetHashCode();
return result;
}
}
}
My question would be why you are committing if you only need to do a select? I believe the reason it's deleting all the comments is that when you call commit on the transaction, the blog object and it's associated comments are cached in the session that is used to create the transaction. When you call the commit, you are causing all the objects in the session to be saved which is causing the save back to the database. I'm not clear on why it's deleting the comments but it is correct behaviour to save the objects.
I also stumbled upon this today:
NHibernate is deleting my entire
collection and recreating it instead
of updating the table.
This generally happens when NHibernate
can't figure out which items changed
in the collection. Common causes are:
replacing a persistent collection entirely with a new collection instance
passing NHibernate a manually constructed object and calling Update on it.
serializing/deserializing a persistent collection apparently also causes this problem.
updating a with inverse="false" - in this case, NHibernate can't construct SQL to update an individual collection item.
Thus, to avoid the problem:
pass the same collection instance that you got from NHibernate back to it (not necessarily in the same session),
try using some other collection instead of ( or ), or
try using inverse="true" attribute for .

NHibernate Mapping with No Id

Is it possible to do a mapping in NHibernate without using an Id?
What I'm trying to do is, call a stored procedure using
session.CreateSqlQuery( myQuery ).AddEntity( typeof( MyEntity ) );
The procedure is an aggregate and there is no Id field being returned, but I would still like to take advantage of NHibernate mapping the data returned into an entity. The only thing I have come up with so far is having the procedure add a field "O as Id" or something, and have the entity have a fake Id field.
Your entity must be "unique" in some way, even if it doesn't have an Id field.
Decide which properties, when taken together, must be unique, and use a composite key over them...
<composite-id>
<key-property name="Property1" column="Column1"/>
<key-property name="Property2" column="Column2"/>
</composite-id>
I found this really isn't possible. The data returned from the stored procedure has to have an Id also. It worked to just create a dummy column with an Id, and a dummy property on the object, but I just decided to convert the data returned by NHibernate to an object by hand.
Basically, you're not supposed to use stored procedures with NHibernate. It IS an ORM afterall. In my case, I had no choice but to use the procedure.
From #Andy McCluggage's post:
You can very much do this in the mapping:
<composite-id>
<key-property name="Property1" column="Column1"/>
<key-property name="Property2" column="Column2"/>
</composite-id>
But, you must override Equals() and GetHashCode() in your model:
public class MyClass
{
public virtual DateTime Property1;
public virtual string Property2;
public virtual int SomeOtherProperty;
public override bool Equals(object obj)
{
if(obj == null)
return false;
var t = obj as MyClass;
if(t == null)
return false;
if (this.Property1 == t.Property1 && this.Property2 == t.Property2)
return true;
return false;
}
public override int GetHashCode()
{
return (this.Property1 + "|" + this.Property2).GetHashCode();
}
}
I need to double-check the xsd, but I believe either id or composite-id are required. However, per the docs, name isn't required. So, you should be able to specify an almost-blank id section.
In a similar situation, I've set the class to mutable="false" and mapped the id to the row index and set the generator in the id mapping to assigned.

Mapping a collection of enums with NHibernate

Mapping a collection of enums with NHibernate
Specifically, using Attributes for the mappings.
Currently I have this working mapping the collection as type Int32 and NH seems to take care of it, but it's not exactly ideal.
The error I receive is "Unable to determine type" when trying to map the collection as of the type of the enum I am trying to map.
I found a post that said to define a class as
public class CEnumType : EnumStringType {
public CEnumType() : base(MyEnum) { }
}
and then map the enum as CEnumType, but this gives "CEnumType is not mapped" or something similar.
So has anyone got experience doing this?
So anyway, just a simple reference code snippet to give an example with
[NHibernate.Mapping.Attributes.Class(Table = "OurClass")]
public class CClass : CBaseObject
{
public enum EAction
{
do_action,
do_other_action
};
private IList<EAction> m_class_actions = new List<EAction>();
[NHibernate.Mapping.Attributes.Bag(0, Table = "ClassActions", Cascade="all", Fetch = CollectionFetchMode.Select, Lazy = false)]
[NHibernate.Mapping.Attributes.Key(1, Column = "Class_ID")]
[NHibernate.Mapping.Attributes.Element(2, Column = "EAction", Type = "Int32")]
public virtual IList<EAction> Actions
{
get { return m_class_actions; }
set { m_class_actions = value;}
}
}
So, anyone got the correct attributes for me to map this collection of enums as actual enums? It would be really nice if they were stored in the db as strings instead of ints too but it's not completely necessary.
You will need to map your CEnum type directly. In XML mappings this would mean creating a new class mapping element in your NHibernate XML mappings file.
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="YourAssembly"
auto-import="true" default-lazy="false">
...
<class name="YourAssemblyNamespace.CEnum" table="CEnumTable" mutable="false" >
<id name="Id" unsaved-value="0" column="id">
<generator class="native"/>
</id>
...
</class>
</hibernate-mapping>
To do it with attribute mappings, something like this on top of your CEnum class:
[NHibernate.Mapping.Attributes.Class(Table = "CEnumTable")] //etc as you require
This is the way i do it. There's probably an easier way but this works for me.
Edit: sorry, i overlooked that you want it as a list. I don't know how to do that...
Edit2: maybe you can map it as a protected IList[string], and convert to public IList[EAction] just as i do with a simple property.
public virtual ContractGroups Group
{
get
{
if (GroupString.IsNullOrEmpty())
return ContractGroups.Default;
return GroupString.ToEnum<ContractGroups>(); // extension method
}
set { GroupString = value.ToString(); }
}
// this is castle activerecord, you can map this property in NH mapping file as an ordinary string
[Property("`Group`", NotNull = true)]
protected virtual string GroupString
{
get;
set;
}
/// <summary>
/// Converts to an enum of type <typeparamref name="TEnum"/>.
/// </summary>
/// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="self">The self.</param>
/// <returns></returns>
/// <remarks>From <see href="http://www.mono-project.com/Rocks">Mono Rocks</see>.</remarks>
public static TEnum ToEnum<TEnum>(this string self)
where TEnum : struct, IComparable, IFormattable, IConvertible
{
Argument.SelfNotNull(self);
return (TEnum)Enum.Parse(typeof(TEnum), self);
}
instead of
[NHibernate.Mapping.Attributes.Element(2, Column = "EAction", Type = "Int32")]
try
[NHibernate.Mapping.Attributes.Element(2, Column = "EAction", Type = "String")]
ie: change the Int32 to String
While I haven't tried using it myself, I stumbled across this code a little while ago and it looks pretty interesting:
http://www.lostechies.com/blogs/jimmy_bogard/archive/2008/08/12/enumeration-classes.aspx
Like I said, I haven't used it myself, but I'm going to give it a go in a project RSN.