many-to-one, all-delete orphan, set property to null but entity not deleted - nhibernate

Using NHibernate v3.0. I have a class similar to this:
class Foo
{
bool barActive;
Bar bar;
}
The Bar instance is managed entirely internally to Foo:
when "barActive" is true, "bar" is set to a Bar instance.
when "barActive" is set to false, the "bar" field is set to null.
Foo.bar is mapped like so:
<many-to-one name="bar" column="BarId" cascade="all-delete-orphan" unique="true" />
However, when "bar" is set to null, it does not delete the Bar record in the database. Bar is an inherited class that is used elsewhere as well, so I can't just make this field a component.
I would have expected the "unique" constraint + "delete-orphan" to handle this. Am I missing something, or can NHibernate not handle this transparently? If it can't, it seems my only option is to raise an event so a higher-level scope can call ISession.Delete(bar).

I have a workaround, that will automatically delete the orphan. I believe it should work for NHibernate version 3 and above. It uses an interceptor - basically an object that handles various session-related events. When it detects the update operation on Foo, it will add an explicit delete for the orphaned Bar.
using System;
using System.Collections;
using NHibernate;
using NHibernate.Type;
class Interceptor : EmptyInterceptor
{
private ISession _session;
private Bar _barOrphan;
public override void SetSession(ISession session)
{
base.SetSession(session);
_session = session;
}
public override bool OnFlushDirty(object entity, object id, object[] currentStates, object[] previousStates, string[] propertyNames, IType[] types)
{
if (entity.GetType() != typeof(Foo)) return;
for (var i = 0; i < propertyNames.Length; i++)
{
if (!StringComparer.Ordinal.Equals(propertyNames[i], "bar")) continue;
object previousState = previousStates[i];
if (currentStates[i] != previousState)
{
_barOrphan = (Bar) previousState;
}
break;
}
}
public override void PostFlush(ICollection entities)
{
if (_barOrphan == null) return;
_session.Delete(_barOrphan);
_barOrphan = null;
_session.Flush();
}
}
Now, when opening your NHibernate session, you have to use one of the overloads that accepts an interceptor instance as an argument, e.g.
using (ISession session = YourSessionFactoryGoesHere.OpenSession(new Interceptor()))
{
...
}
Please note that this is just a draft, to explain the concept (I hope I did not screw up the code as I was rewriting it ;-). In a real usage scenario, you may have to deal with possible multiple orphans created in one unit of work (event on the same entity, e.g. Foo could have bar1 and bar2!), so instead of a single _barOrphan member you will need a queue of the delete actions to be executed in PostFlush(). Instead of hard-coding the types of the involved classes and the name of the property bar, you will want to use generics and a property selector (e.g. PropertySelector.GetPropertyName<Foo>(foo => foo.bar), see this link. DB constraint could be a problem, moving the delete operation to Interceptor.PreFlush() might help, but I did not test it. Don't forget about the performance impact (e.g. OnFlushDirty() is invoked for each updated entity, so don't make it a bottleneck).

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.

Ignore column using mapping by code in HNibernate

I'm using mapping by code in NHibernate.
I got a class with several properties. One of them is not related to any columns in DB but still has getter and setter.
I use ConventionModelMapper not ModelMapper. The first one assumes that all properties are mapped.
How i can tell to NHibernate to ignore it?
I find it easier to just create an attribute, attach that attribute to the property, and check for it in the mapper.IsPersistentProperty method. Something like this:
class IngnoreAttribute : Attribute
{
}
class Foo
{
[Ignore]
public virtual string Bar { get; set; }
}
mapper.IsPersistentProperty((mi, declared) => mi.GetCustomAttribute<IgnoreAttribute>() == null);
This way, I don't have to keep a list of properties to be ignored at the mapping codes.
Why not map the properties you want and leave the ones not needed to be mapped
check this
You can manage the persistence of ConventionModelMapper as following:
mapper.BeforeMapProperty += (mi, propertyPath, map) =>
{
// Your code here using mi, propertyPath, and map to decide if you want to skip the property .. can check for property name and entity name if you want to ignore it
};
A better answer would be:
mapper.IsPersistentProperty((mi, declared) =>
{
if (mi.DeclaringType == typeof (YourType) && mi.Name == "PropertyNameToIgnore")
return false;
return true;
});
If you do not mention the property that should be ignored in your NHibernate mapping, NHibernate will ignore it.

Why both NHibernate OnPreInsert and OnPreUpdate methods get called for an object

I use the NHibernate OnPreInsert and OnPreUpdate events in a PreSaveEventListener to set the CreatedDate and ModifiedDate of my entities. The problem is, there are two entities for which both events get triggered when I first create them. This causes an issue because the entity state does not get saved after the OnPreInsert event, so the OnPreUpdate event operates on a whole new entity state and my CreatedDate never gets set (defaults to 01/01/0001).
At first, I thought this was because my code that was initiating two SaveOrUpdate calls back to back before the end of the transaction. Sure enough, I found some code to this effect. But then I realized this was still happening for the other entity. So far as I can tell, only these two entities have this issue. I temporarily solved the problem by setting the CreatedDate in their constructors, but I want to avoid this.
Here's my structure:
Business entity (an abstract class that has two concrete joined-subclasses)
BusinessContact entity with a Many-To-One relationship with Business
EDIT: I have recently realized that it's also happening on one other object (InvoiceLineItem), but not a near identical object (BillLineItem) instantiated and used in near identical ways. Seems rather arbitrary.
Has anyone seen this before?
Here's the event listener code:
public class PreSaveEventListener : IPreInsertEventListener, IPreUpdateEventListener {
public bool OnPreInsert(PreInsertEvent #event) {
EntityWithGuidId entity = #event.Entity as EntityWithGuidId;
if (null != entity) {
var createdDate = DateTime.Now;
var modifiedDate= DateTime.Now;
Set(#event.Persister, #event.State, "CreatedDate", createdDate);
Set(#event.Persister, #event.State, "ModifiedDate", modifiedDate);
entity.CreatedDate = createdDate;
entity.ModifiedDate = modifiedDate;
}
return false;
}
public bool OnPreUpdate(PreUpdateEvent #event) {
EntityWithGuidId entity = #event.Entity as EntityWithGuidId;
if (null != entity) {
var modifiedDate= DateTime.Now;
Set(#event.Persister, #event.State, "ModifiedDate", modifiedDate);
entity.ModifiedDate = modifiedDate;
}
return false;
}
private void Set(IEntityPersister persister, object[] state, string propertyName, object value) {
var index = Array.IndexOf(persister.PropertyNames, propertyName);
if (index == -1)
return;
state[index] = value;
}
}
Event listeners caused a lot of different issues in my project and many of them doesn't make sense to me. I think your issue can be caused in case when NHibernate really updates your entity after it created. NHibernate can update version of entity or set some id (or guid) for it. Can you put here mapping of issued entity? I also will suggest you to look at sql queries in profiler.
I actually ran into this, there is a chance it may be the same issue.
I implemented my own StringTrimEnd typehandler that did just that, trimmed the end of strings before inserting into the database, or after retrieving them.
Well, I implemented the Equals method wrong and it returned false for Equals(object x, object y) when x and y where null.
Therefore when I created a new object with a null string on it, it compared the loaded value (null) with the current value (null) and decided an update was needed (as well as the insert).
Maybe this will help someone out at some point.
You have nullable field in DB which wasn't marked as nullable in NH

nHibernate (a==b) equality

I'm currently using nHibernate and having a problem where two objects loaded separately but representing the same entity in the database do not have equal references.
See the following section of code for example:
CrudRepository<Customer> cr1 = new CrudRepository<Customer>();
Customer c1 = cr1.GetById(new Guid("0D19B0F0-CA41-4D4B-9FB9-9CF600F86A4B"));
CrudRepository<Customer> cr2 = new CrudRepository<Customer>();
Customer c2 = cr2.GetById(new Guid("0D19B0F0-CA41-4D4B-9FB9-9CF600F86A4B"));
if (c1.Equals(c2))
MessageBox.Show("SAME!"); // True.
if (c1.GetHashCode().Equals(c2.GetHashCode()))
MessageBox.Show("SAME!"); // True.
if (c1 == c2)
MessageBox.Show("SAME!"); // False.
My interpretation of the nHibernate documentation lead me to believe that once you override the Equals() and GetHashCode() methods, nHibernate can load entities into the same reference.
This is the line I am referring to:
This only applies if these objects are loaded in two different ISessions,
as NHibernate only guarantees identity
( a == b , the default implementation
of Equals()) inside a single
ISession!
I understand that (c1 == c2) is comparing the references of objects loaded at different times. However, I thought (and hoped) that nHibernate would automatically track pre-loaded references and update them accordingly.
Is this not true?
Or is there something I am doing wrong?
Thanks :)
NOTE: I use the singleton pattern to keep the same session throughout the application.
Edit: Every time I perform a CRUD task I use this NHibernateHelper class to get the single instance of the same session builder. This was an example piece of code.
public T GetById(Guid guid)
{
using (ISession session = NHibernateHelper.OpenSession())
using (ITransaction transaction = session.BeginTransaction())
{
return session.Get<T>(guid);
}
}
And this is my helper class:
public class NHibernateHelper
{
private static ISessionFactory sessionFactory;
private static ISessionFactory SessionFactory
{
get
{
if (sessionFactory == null)
{
var configuration = new Configuration();
configuration.Configure();
configuration.AddAssembly(typeof(Customer).Assembly);
sessionFactory = configuration.BuildSessionFactory();
}
return sessionFactory;
}
}
public static ISession OpenSession()
{
return SessionFactory.OpenSession();
}
}
You are creating a new session on every request (this is what your NHibernateHelper.OpenSession() method does) and nHibernate only guarantees reference equality within one session. Once you make sure that your requests are within the same session, the result of the two call will be the same object.

NHibernate Interceptor Auditing Inserted Object Id

I am using NHibernate interceptors to log information about Updates/Inserts/Deletes to my various entities.
Included in the information logged is the Entity Type and the Unique Id of the entity modified. The unique Id is marked as a <generator class="identity"> in the NHibernate mapping file.
The obvious problem is when logging an Insert operation using IInterceptor.OnSave() the Id of the entity has not yet been assigned.
How can I obtain the Id of the inserted entity before logging the audit information?
(I have looked into NHibernate Listeners PostSave event but can't get them working with the Spring.net configuration being used, so I would like to stick with interceptors if at all possible)
Code:
// object id parameter is null...
public override bool OnSave(object entity, object id, object[] state,
string[] propertyNames, IType[] types)
{
AddAuditItem(entity, INSERT);
return false;
}
I've worked around this problem by adding a list to my interceptor class which is populated with objects during the OnSave implementation.
In the PostFlush implementation the list is iterated over and each element is audited as an insert. The objects in this list have been persisted in PostFlush() and thus have generated IDs.
This seems to work OK but I'd be grateful if any potential pitfalls were pointed out :-)
public class AuditInterceptor : EmptyInterceptor
{
// To hold inserted items to be audited after insert has been flushed
private IList<object> insertItems = new List<object>();
public override void PostFlush(System.Collections.ICollection entities)
{
foreach (var entity in insertItems)
{
AddAuditItem(entity, INSERT);
}
insertItems.Clear();
base.PostFlush(entities);
}
public override bool OnSave(object entity, object id, object[] state,
string[] propertyNames, IType[] types)
{
var auditable = entity as IAuditable;
if (auditable != null)
insertItems.Add(entity);
return false;
}
}
try the OnFlushDirty method.. or maybe PostFlush
edit: also, can you post your code? Don't you get the Id as a parameter to OnSave?