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

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.

Related

Mask properties from dirty check

I have a column in all my tables called LoggedInPersonID. To avoid cluttering mapping code, an Nhibernate Interceptor overrides OnFlushDirty and OnSave to assign the LoggedInPersonID property automatically.
If LoggedInPersonID is the only property changed, I consider the entity clean. At the moment Nhibernate (rightfully) considers the entity to be dirty.
Does any mapping construct exist to escape a property from Nhibernate's dirty check, while still including the column in any inserts/updates?
Alternatively, I have considered implementing the IPreUdateEventListener interface and use the OnPreUpdate event to check whether the only difference between OldState and State is in the property LoggedInPersonID, and cancel the update if that is the case. Would that be a valid approach?
I think if you already change the property in OnSave, the dirty check will come after, and finally OnFlushDirty will occur, when it is already decided. At least if you (unnecessarily) call Save() or SaveOrUpdate() on your object, although it is not a newly created one.
Simplier case
I would rather try to avoid setting LoggedInPersonID if the entity is not dirty. I am not comfortable with cancelling the update from IPreUdateEventListener: a lot of other processing still occurs, like second level cache updating, and other PostUpdate processing.
OnFlushDirty xml doc states:
Called when an object is detected to be dirty, during a flush.
So this means NHibernate considers your object to be dirty even before you have set its LoggedInPersonID.
You should probably check that in your interceptor with a conditional break-point to stop only on your entity type having troubles, and check if there is already some other changes between currentState and previousState before your code affects its LoggedInPersonID.
Maybe have you by example some other logic elsewhere which has already set LoggedInPersonID.
Harder case
But checking NHibernate code, it could be a bit muddier. It looks to me like OnflushDirty could be called on entities which might be dirty. (And maybe this "might be dirty" is caused by what I had suspected in my answer on your previous question.)
I such case, you should do your own dirty check inside your interceptor. You may do the dirty check in your OnFlushDirty, but then NHibernate will still do its own, causing the dirty check to be done twice. To avoid dirty checking twice each entity, you need then do your first idea: evicting LoggedInPersonID from the dirty check if this is the only dirty property.
NHibernate dirty check implementation is not trivial. Better reuse it than coding your own dirty check. But this need adding some code to your interceptor. (Done with the help of this blog on NHibernate.info.)
using NHibernate;
using NHibernate.Type;
using NHibernate.Proxy;
...
public class LoggedInPersonIDInterceptor : EmptyInterceptor
{
...
// your previous code handling the setting of LoggedInPersonID
...
private ISession _session;
public override void SetSession(ISession session)
{
_session = session;
}
public override int[] FindDirty(object entity, object id,
object[] currentState, object[] previousState,
string[] propertyNames, IType[] types)
{
var sessionImpl = _session.GetSessionImplementation();
var persistenceContext = sessionImpl.PersistenceContext;
var entry = persistenceContext.GetEntry(entity);
if (entry == null)
{
// The blog post try to handle proxy case but that part looks
// buggy to me. If you do not need to handle proxies, just let
// default implementation do the job by returning null here.
return null;
}
var persister = sessionImpl.Factory.GetEntityPersister(entry.EntityName);
var dirtyPropertiesIndexes = persister.FindDirty(currentState,
previousState, entity, sessionImpl);
// Probable superfluous null check...
if (dirtyPropertiesIndexes == null || dirtyPropertiesIndexes.Length != 1)
{
return dirtyPropertiesIndexes;
}
if (propertyNames[dirtyPropertiesIndexes[0]] == "LoggedInPersonID")
{
// return empty array for telling that nothing has changed
return new int[] {};
}
return dirtyPropertiesIndexes;
}
}
Side note : I have seen in your other question revisions your were testing on propertyNames[i].ToLower() == "loggedinpersonid". If you need that, I generally prefer do that this way : StringComparer.OrdinalIgnoreCase.Equals(propertyNames[i], "LoggedInPersonID"). This avoid messing up while manually lower-casing the property name.
Other solution
Maybe this other way I found later would be easier.

How can I detect NHibernate HasManyToMany mapping at run-time?

I am trying to detect HasManyToMany relationships in entities at run-time for testing purposes. I've had many problems with people writing bad mappings so I wrote a test to test every single mapping on our continuous integration server.
Right now I can test every entity, composite and non-composite by detecting the mapped Id property(s) and calling .Get() or a composite getter. Most of which is done using reflection. I am using GetClassMetadata while going over every entity type.
But I missed testing something that was broken, a HasManyToMany.
I am using Fluent NHibernate, and the mapping is:
mapping.HasManyToMany<ServiceType>(x => x.ServiceTypes)
.Schema("Marketplace")
.Table("ListingServiceTypes")
.ParentKeyColumn("PackageID")
.ChildKeyColumn("ServiceTypeID")
.Cascade.SaveUpdate().LazyLoad();
Now since there is no entity to "test" this relationship with, I do not run over it.
What I need to know is how can I find the properties of an object that are mapped with "HasManyToMany". I can invoke them just fine, if I could only detect them.
I do not want to have to force lazy loading of every collection because if the mapping for the individual entities are correct, the mappings will be, because we use conventions for them.
Get the source code of FluentNHibernate, and copy to your project HasManyToManyStep.cs (FluentNHibernate.Automapping.Steps)
Add your logic to ShouldMap() method. This method is called by FNH to detect Many To Many relations. You can alter the way many to many relations are determined (for example by an attribute). In your case you want probably add a an attribute by reflection to tag the properties...
Replace the default step with your new one :
public class MyMappingConfiguration : DefaultAutomappingConfiguration
{
public override IEnumerable<IAutomappingStep> GetMappingSteps(AutoMapper mapper, IConventionFinder conventionFinder)
{
var steps = base.GetMappingSteps(mapper, conventionFinder);
var finalSteps = steps.Where(c => c.GetType() != typeof(FluentNHibernate.Automapping.Steps.HasManyToManyStep)).ToList();
var idx = finalSteps.IndexOf(steps.Where(c => c.GetType() == typeof(PropertyStep)).First());
finalSteps.Insert(idx + 1, new MyCustomHasManyStep(this));
return finalSteps;
}
}
Had to do this today.
var CollectionMetaData = SessionFactory.GetCollectionMetadata(T.FullName + '.' + info.Name);
if (CollectionMetaData is NHibernate.Persister.Collection.BasicCollectionPersister)
{
if (((NHibernate.Persister.Collection.BasicCollectionPersister)CollectionMetaData).IsManyToMany)
{
//Do something.
}
}
where T is the Type and info is the property info from T.GetProperties()

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.

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

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>

How to find unmapped properties in a NHibernate mapped class?

I just had a NHibernate related problem where I forgot to map one property of a class.
A very simplified example:
public class MyClass
{
public virtual int ID { get; set; }
public virtual string SomeText { get; set; }
public virtual int SomeNumber { get; set; }
}
...and the mapping file:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MyAssembly"
namespace="MyAssembly.MyNamespace">
<class name="MyClass" table="SomeTable">
<property name="ID" />
<property name="SomeText" />
</class>
</hibernate-mapping>
In this simple example, you can see the problem at once:
there is a property named "SomeNumber" in the class, but not in the mapping file.
So NHibernate will not map it and it will always be zero.
The real class had a lot more properties, so the problem was not as easy to see and it took me quite some time to figure out why SomeNumber always returned zero even though I was 100% sure that the value in the database was != zero.
So, here is my question:
Is there some simple way to find this out via NHibernate?
Like a compiler warning when a class is mapped, but some of its properties are not.
Or some query that I can run that shows me unmapped properties in mapped classes...you get the idea.
(Plus, it would be nice if I could exclude some legacy columns that I really don't want mapped.)
EDIT:
Okay, I looked at everything you proposed and decided to go with the meta-data API...that looks the easiest to understand for me.
Now that I know what to search for, I found some examples which helped me to get started.
So far, I have this:
Type type = typeof(MyClass);
IClassMetadata meta = MySessionFactory.GetClassMetadata(type);
PropertyInfo[] infos = type.GetProperties();
foreach (PropertyInfo info in infos)
{
if (meta.PropertyNames.Contains(info.Name))
{
Console.WriteLine("{0} is mapped!", info.Name);
}
else
{
Console.WriteLine("{0} is not mapped!", info.Name);
}
}
It nearly works, except one thing:
IClassMetadata.PropertyNames returns the names of all the properties except the ID.
To get the ID, I have to use IClassMetadata.IdentifierPropertyName.
Yes, I could save .PropertyNames in a new array, add .IdentifierPropertyName to it and search that array.
But this looks strange to me.
Is there no better way to get all mapped properties including the ID?
You could use the NHibernate meta-data API to find the mapped properties, and reflection to find all the properties.
Edit No, there isn't any other way list all the properties including the id. It isn't that hard to use:
foreach (PropertyInfo info in infos)
{
if (meta.PropertyNames.Contains(info.Name) || info.Name = meta.IdentifierPropertyName)
{
Console.WriteLine("{0} is mapped!", info.Name);
}
else
{
Console.WriteLine("{0} is not mapped!", info.Name);
}
}
There are two tools I'm aware of that can help with this:
Fluent NHibernate persistence specification testing
Nhibernate Ghostbuster
but they don't specifically address the problem you had with an unmapped property. The best solution is to write good unit tests that ensure that the properties you want to persist are persisted correctly. It's tedious but necessary.