I have a user that has a number of roles. The user is linked to the roles using a link entity table. I have set the configuration file to cascade delete the user role link entities when a user is deleted.
We are currently using soft delete to delete entities. We have added a soft delete event listener that is triggered by a delete. When an entity is being deleted, it triggers the DeleteEntity
event which marks the entity as deleted.
We also have an override the OnPostUpdate
event to remove the entities from the cache by calling Evict on the entity.
If I create a user without any roles, then delete it, everything works fine (it also works if cascade disabled). However if I have a user with at least one role assigned and I delete the user, after the call to Evict in OnPostUpdate
, I get a NHibernate exception "NHibernate.AssertionFailure: Possible nonthreadsafe access to session".
I have tried, in OnPostUpdate
, to use the child session to Evict the entity, the exception is not thrown, however, the entity is not evicted.
public void UserDelete(.....)
{
var user = repository.Fetch<User>(id);
repository.Remove(user);
repository.Connection.Commit();
}
// soft delete event listener
protected override void DeleteEntity(NHibernate.Event.IEventSource session, object entity, ..)
{
var repositoryEntity = entity as deletableentity;
if (repositoryEntity != null)
{
if (!repositoryEntity.IsDeleted)
{
// this marks the entity as deleted
repositoryEntity.isDeleted = true;
// cascade delete
this.CascadeBeforeDelete(session, persister, repositoryEntity, entityEntry, transientEntities);
this.CascadeAfterDelete(session, persister, repositoryEntity, transientEntities);
}
}
}
public void OnPostUpdate(PostUpdateEvent @event)
{
if (@event == null) throw new ArgumentNullException("event");
var entity = @event.Entity as deletableentity;
// Evict any entities that have been set as deleted from first level cache.
if (entity != null && entity.IsDeleted)
{
@event.Session.Evict(entity);
}
}
Any ideas on how to resolve it?
According to https://forum.hibernate.org/viewtopic.php?p=2424890 another way to avoid this is to basically call
session.save(s);
session.flush(); // allow evict to work
session.evict(s);
and that the root of the problem is that "if I've evicted the entity from cache the commit() will not find it there" (i.e. it's not a thread safety issue at all, it's a cache was modified issue).
Found what the problem was. Using soft delete will actually trigger update to set isDeleted flag. Because of this line in a mapping
cascade="all"
the cascade is applied for both Update and Evict actions. My postUpdate will be fired 2 times, but the same time Evict will try to evict on child entities.
The solution was to remove Evict from cascade in the mapping file. Now it is:
cascade="persist, merge, save-update, delete, lock, refresh"
I ran into the same problem, but since I'm using Fluent mappings there's no option to exclude Evict
from the cascade. My solution was to avoid calling Evict
and just remove the entity from the session cache:
public void OnPostUpdate(PostUpdateEvent @event)
{
var entity = @event.Entity as ISoftDeletable;
if (entity != null && entity.Deleted)
{
IEventSource session = @event.Session;
IEntityPersister persister = @event.Persister;
var key = new EntityKey(@event.Id, persister, session.EntityMode);
session.PersistenceContext.RemoveEntity(key);
session.PersistenceContext.RemoveProxy(key);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With