views:

23

answers:

1

I have an object where I maintain a relationship to another object:

public class Foo {
    public Bar Bar { get; set; } 
}

In the mapping I reference Bar to that I can maintain the relationship - I don't want the parent object to update properties in the child, just the existence of the relationship:

References(x => x.Bar, "BarId")
  .Cascade.None();

In the UI layer I create the relationship using a property which is not the underlying primary key:

item.Bar = new Bar { Code = "123" };

In the repository layer I hydrate the object if it doesn't have the primary key populated:

if(item.Bar.Id == null)
{
  item.Bar = barRepository.RetrieveByCode(item.Bar.Code);
}

When I the RetrieveByCode line runs (which is a Criteria.UniqueResult under the covers) I get a TransientObjectException telling me that "the object references an unsaved transient instance - save the transient instance before flushing" for the Bar type.

When I run the same code path without creating the temporary Bar object it works. It appears that the Bar created as a temporary oject is tracked by NHibernate, yet I want it to forget that it ever existed as it is only a placeholder.

Any thoughts on how to achieve this?

UPDATE: Doing some more testing on this it seems to be the change tracking in Foo that is causing trouble. If I call Session.Evict(item) after retrieving it, but before making any changes and then re-attach the object using Session.Update(item) after I am done it seems to work, however it updates the child objects which is not what I want - I only want to manage the relationship.

UPDATE 2: I changed the FlushMode from Auto to Commit. It seems to have disabled the queueing of any interim changes to the object. Having researched NH behavior a bit further it seems that Update works more like a "re-attach" call rather than an explicit "update now" call.

UPDATE 3: It appears changing FlushMode caused other issues with transactions that required several operational steps. I reverted back to try another approach:

if(item.Bar.Id == null) 
{ 
  var barCode = item.Bar.Code;
  item.Bar = null;
  item.Bar = barRepository.RetrieveByCode(barCode); 
}
A: 

Why do you want it to work that way? Why not simply set item.Bar using the retrieved Bar object:

item.Bar = barRepository.RetrieveByCode("123");

You might be able to make your current pattern work using Load:

if(item.Bar.Id == null)
{
    var bar = barRepository.RetrieveByCode(item.Bar.Code);
    item.Bar = session.Load<Bar>(bar.Id);
}
Jamie Ide
The UI layer doesn't concern itself with how the object gets persisted. It's telling the domain service to create a relationship between Foo and Bar. The domain service calls out the various repositories to hydrate the proper objects. The challenge with your Load example is that the TransientObjectException is thrown during RetrieveByCode when it tries to call Criteria.UniqueResult, so I won't get to the Load call.
Colin Bowern
The repositories should be sharing a single ISession so that they can participate in the transaction. I don't understand why a criteria query would throw that exception; can you post the code?
Jamie Ide
It probably throws because it's flushing and finding that fake Bar object being referenced. If it were cascading, it would persist a new instance. The whole usage is wrong.
Diego Mijelshon
Diego is right - it's flushing the fake objects. Removing the references causes it to forget about them. Learning now that Update isn't really equivalent to making a SQL UPDATE call - much more magic going on to manage references than I had expected.
Colin Bowern