NHibernate Custom Collections hack works outside of a transaction, but not inside - nhibernate

Following the technique described here, I was able to populate a domain object that uses custom collections for its children. The relevant property mapping looks like this:
<component name="Contacts" class="My.CustomList`1[[Domain.Object, DomainAssembly]], MyAssembly">
<set name="InnerList">
<key column="PARENT_ID" />
<one-to-many class="Contact" />
</set>
</component>
My custom collection class exposes the InnerList property as an ICollection like so:
protected System.Collections.ICollection InnerList
{
get
{
return this;
}
set
{
Clear();
foreach (DomainObject o in value)
{
Add(o);
}
}
}
This worked like a charm to load data from the database and not have to abandon my rather useful custom collection class.
Then I moved on to try implement saving, and following the advice given in this thread, decided to wrap every call to NHibernate in a transaction.
Now when I commit following my load, NHibernate throws an InvalidCastException: "Unable to cast object of type 'My.CustomList`1[Domain.Object, DomainAssembly]' to type 'Iesi.Collections.ISet'."
Is there a way to make this work the way I expect it to?
EDIT:
Following the lead provided by Raphael, I tried switching to ICollection<T> which gives me a different InvalidCastException when I commit the transaction: Unable to cast object of type 'My.CustomList`1[Domain.Object]' to type 'NHibernate.Collection.IPersistentCollection'.

Change the property to be of type
IList<T>

Related

Nhibernate lazy loading a set not working

I'm using nhibernate2.1 as part of spring.net 1.3. I have the following declaration as part of my mapping. My understanding is that this object should not load unless the getter is called. I have a break point set on the setter and also dump all nhibernate SQL statements to the logger. In part of my testing, I've actually created a brand new child object and a brand new property on my original object (hence the "2" on the names) so I'm positive that property is not being accessed anywhere. Despite this, as soon as my parent object loads, I can verify that this property is loaded. So...what am I missing here?
<set name="UserCustomer2" lazy="true">
<key column="[FK_USERS]" />
<one-to-many class="UserCustomer2" />
</set>
#A: here is my property:
private ICollection<UserCustomer2> _UserCustomer2 = new HashSet<UserCustomer2>();
public virtual ICollection<UserCustomer2> UserCustomer2
{
get { return _UserCustomer2; }
set { this._UserCustomer2 = value; }
}
and here is how I request the parent object:
IQuery query = dao.GetQuery("FROM UserImpl u WHERE u.UserName = :username AND u.Password = :password");
query.SetParameter("username", username);
query.SetParameter("password", password);
IList users = query.List();
#dotjoe, you led me down the right path. I tested out quite a few scenarios and based on breakpoints and the logged SQL statements I've determined that setting breakpoints does NOT trigger the lazy load. However, when you hover over the property to inspect it while in debug mode, it DOES in fact hydrate the object. I guess that sort of makes sense but I was assuming when I inspected the object, I would just see a proxy object instead of the fully hydrated object. I'm surprised that a debug mode action would trigger the lazy load -- I assumed that would only be triggered from actual application code. The other thing I'm curious about is if the setter is ALWAYS called and just passed a proxy object or if it was only called BECAUSE I had put a breakpoint in the setter. I would assume the former but so far my assumptions have been wrong. If anyone can provide some insights, I'm very curious how this actually works behind the scenes.

super() type functionality on ORM CFC

When I use CF9's ORM feature and generate an explict setter for my ORM CFC, is there anyway to call the default funcitionailty of the ORM CFC after i have done the work needed in the method. For example i am looking for something like this. Obviosuly the code will not run , and super is the wrong concept since the ORM CFC isnt inherting anything, but thats the type of functionality I am looking for.
public void setMovie(String movie){
if(movie == "inception"){
ORMCFC.super().setMovie("Greatest movie ever made")
}else{
ORMCFC.super().setMovie(movie)
}
In your model CFC for the ORM you can specify additional "decorator" functions.
component persistent="true" table="Movie" schema="dbo" output="false"
{
/* properties */
property name="MovieNo" column="MovieNo" type="numeric" ormtype="double" fieldtype="id" ;
property name="Name" column="Name" type="string" ormtype="string" ;
/* decorator */
public void function setMovie(name)
{
if(name == "inception"){
setName("Greatest movie ever made")
}else{
setName(name)
}
}
}
Otherwise if you need to (using your example) setMovie() you will need to do an EntityLoad or create a new entity to set a value to.

NHibernate: Parent list properties and related child properties are not synchronized

I have two related objects: ProgramSession and ProgramTask, with a one-to-many relationship. A ProgramSession has many ProgramTasks. So the objects looks like this:
public class ProgramSession
{
public virtual IList<ProgramTask> ProgramTasks
{
get { return _programTasks; }
set { _programTasks = value; }
}
}
public class ProgramTask
{
public virtual ProgramSession ProgramSession
{
get { return _programSession; }
set { _programSession = value; }
}
}
And the mappings...
ProgramSession.hbm.xml
<bag name="ProgramTasks" lazy="false" cascade="all-delete-orphan" inverse="true" >
<key column="SessionUid"></key>
<one-to-many class="ProgramTask"></one-to-many>
</bag>
ProgramTask.hbm.xml
<many-to-one name="ProgramSession" column="SessionUid" class="ProgramSession" />
The problems begin when I try to change the ProgramSession of a ProgramTask.
If I remove the ProgramTask from the ProgramSession.ProgramTasks list property of the old session, and then add it to the same property on the new session, NHibernate tells me that the a deleted object will be resaved.
If I simply change the value of the ProgramTask.ProgramSession object, I have no problem saving. However, I get weird behavior if I do not save immediately because the ProgramSession.ProgramTasks properties (on both sessions) are not synchronized until after the NHibernate session is refreshed.
Changing the ProgramTask.ProgramSession object without directly modifying the lists as well, creates an invalid state. Take the following code as an example:
programTask.ProgramSession = newProgramSession;
Assert.That(newProgramSession.ProgramTasks.Contains(programTask)); // fails
Assert.That(!oldProgramSession.ProgramTasks.Contains(programTask)); // fails
This is more problematic in code that executed later on, that assumes the ProgramTasks collections are synchronized with the ProgramSession property. For example:
foreach(var programTask in programSession.ProgramTasks)
{
// whatever
}
One hack I used to work around this was to query the list. I can't use it everywhere, and it's clearly a bad solution, but it underscores the problem:
var tasksActuallyInSession =
programSession.ProgramTasks
.Where(task => task.ProgramSession == programSession)
.ToList();
Is there any way to handle this kind of situation? A best practice? Am I doing something wrong? Is the mapping incorrect? Is there some super-secret NHibernate flag I need to set?
Not sure if I understand everything what you are doing here. Some thoughts:
If you decide to move ProgramTasks around, then they get independent and should not be mapped using cascade="all-delete-orphan". If you do this, NH removes the ProgramTask from the database when you remove it from a ProgramSession.
Map it using cascade="none" and control the lifecycle of the object yourself. (this means: store it before a ProgramSession gets stored. Delete it when it is not used anymore.)
Not sure if this this is also a problem, but note that if you have a inverse reference, you code is responsible to make the references consistent. Of course, references get cleaned up after storing to the database and loading into an empty session, this is because there is only one foreign key in the database. But this is not the way it should be done. It is not responsibility of NH to manage your references. (Its only responsibility is to persist what you are doing in memory.) So you need to make it consistent in memory, and implement your business logic as if there wasn't NHibernate behind.

NHibernate: how to map a Point?

I have a class which contains a collection of Points (PointF's rather).
I want to be able to persist instances of that class using NHibernate.
My class looks somewhat like this (simplified):
public class MyClass
{
public IDictionary<string, PointF> Points = new Dictionary<string, PointF>();
public void AddPoint( location, PointF position )
{
Points.Add(location, position);
}
}
The mapping of this collection looks like this (simplified):
<map name="Points" table="Locations">
<key column="MyClassId" />
<index column="LocationName" />
<composite-element class="System.Drawing.PointF, System.Drawing">
<property name="X" column="X" />
<property name="Y" column="Y" />
</composite-element>
</map>
The problem now is, that NHibernate throws an error while processing the mapping file, since PointF is not a known (mapped) entity.
How can I solve this in the most simple way ?
How can I make sure that NHibernate is able to persist my collection of locations (with their coordinates (point) ?
The problem is not that you didn't map the type PointF - because you map it as composite-element, which is correct.
When mapping such types you need to make sure
that properties are writable (which is luckily the case here)
that it has a default constructor, which is not the case here.
So how should NH create new instances when there is not default constructor? It can't.
Your options are:
implement an interceptor or NH event. I think it is possible to inject code there which creates instances of certain types, but I don't know how.
implement a NH user type (derived from ICompositeUserType), which is not too hard to do
map another type (eg. a wrapper to PointF)

Creating a NHibernate object and initializing a Set

I have a Table called Product and I have the Table StorageHistory.
Now, Product contains a reference to StorageHistory in it's mappings
<set name="StorageHistories" lazy="false">
<key column="ProductId" />
<one-to-many class="StorageHistory" />
</set>
And it works, when I retrieve an object from the ORM I get an empty ISet.
What gives me a headache is how to construct the object in the first place.
When I do the following:
var product = new Product();
session.Save(product);
the product.StorageHistories property is NULL and I get a NullReferenceException.
So, how do I add items to that collection, or should I go the way to add the StorageHistory items themselves to the DB?
I always do the following in the ctor of the parent object:
histories = new HashedSet();
This covers the Save() use case. The Load()/Get() etc usecase is covered by NHibernate as you stated.
Why not?
private ISet _StorageHistories;
public virtual ISet StorageHistories {
protected set { _StorageHistories = value;}
get { if (_StorageHistories == null) _StorageHistories = new HashSet();
return _StorageHistories;
}
}
Of course if you go through the trouble of having a private anyway you might as well put it in the constructor.