Reputation: 16389
I start the session at the start of unit of work and close at the end of it. Unit of work is spreed across multiple methods.
In one method, I load the entity using Get
method. So this sits in session cache. Entity instance is local to the method. So when method scope ends, entity instance is not accessible. But entity is still in session cache.
Now, second method creates new instance of entity and tries to delete it. This throws NonUniqueObjectException
as expected.
Following is the solution I can imagine, but not able to implement:
public void Delete<T>(T instance) where T : BaseEntity
{
try
{
nhSession.Delete(instance);
}
catch(NonUniqueObjectException)
{
T instanceFromCache = GetInstanceFromCache<T>(instance);
nhSession.Evict(instanceFromCache);
nhSession.Delete(instance);
}
}
If I can get the entity instance from session cache somehow, I can Evict
it and hopefully the problem will be solved. But I am not able to implement my imaginary GetInstanceFromCache
method.
I tried using nhSession.Get
, but that is not helpful in my scenario. Primary key column name in my database is NOT "id" and also it is not same across tables. In one table, it is "Field1" and in other it is "Field2". So I cannot use something like nhSession.Get(instance.Id)
. My Delete<T>(T instance)
method receives entity instance to delete as parameter. It does not receive value of primary key to delete.
For more info, please refer my other question. That question discusses about UPDATE issue and how I fixed it; but scenario is similar.
Answer by "@Ricardo Peres" does not work as is, but I modified his code a little.
public static TEntity GetInstanceFromCache<TEntity>(this ISession nhSession, object instance) where TEntity : BaseEntity
{
var sessionImpl = nhSession.GetSessionImplementation();
foreach(BaseEntity baseEntity in sessionImpl.PersistenceContext.EntityEntries.Keys)
{
if(baseEntity is TEntity)
{
TEntity instanceFromCache = (TEntity)baseEntity;
if(nhSession.GetIdentifier(instanceFromCache) == nhSession.GetIdentifier(instance))
return baseEntity as TEntity;
}
}
return null;
}
The call nhSession.GetIdentifier(instance)
throws an exception TransientObjectException
("The instance was not associated with this session") which is expected. This is because instance
is unknown to nhSession
. Any way to get identifier of entity that is NOT associated with session?
Upvotes: 2
Views: 2107
Reputation: 109
A faster alternative that actually indexes the cache instead of iterating through all objects in it:
public static TEntity GetInstanceFromCache<TEntity>(this ISession nhSession, TEntity instance) where TEntity : BaseEntity
{
var sessionImpl = nhSession.GetSessionImplementation();
var sessionCache = sessionImpl.PersistenceContext.EntitiesByKey;
if (sessionCache.TryGetValue(new EntityKey(nhSession.GetIdentifier(instance), (NHibernate.Persister.Entity.IEntityPersister)nhSession.SessionFactory.GetClassMetadata(typeof(TEntity))), out var instanceFromCache))
{
return (TEntity)instanceFromCache;
}
return null;
}
Upvotes: 0
Reputation: 16389
With the code provided by @RicardoPeres, I was able to resolve it. But exact code does not work for me. I modified it a bit as follows:
public static TEntity GetInstanceFromCache<TEntity>(this ISession nhSession, object instance) where TEntity : BaseEntity
{
object detachedIdentifier = GetDetachedEntityId<TEntity>(instance, nhSession.SessionFactory);
var sessionImpl = nhSession.GetSessionImplementation();
foreach(BaseEntity baseEntity in sessionImpl.PersistenceContext.EntityEntries.Keys)
{
if(baseEntity is TEntity)
{
TEntity instanceFromCache = (TEntity)baseEntity;
string idFromCache = Convert.ToString(nhSession.GetIdentifier(instanceFromCache));
string idNew = Convert.ToString(detachedIdentifier);
if(idFromCache == idNew)
return baseEntity as TEntity;
}
}
return null;
//Another way-----------------------------
//var entity = nhSession.GetSessionImplementation().PersistenceContext.EntitiesByKey.SingleOrDefault(x => x.Value is TEntity && Convert.ToString(x.Key.Identifier) == Convert.ToString(detachedIdentifier));
//return entity.Value as TEntity;
//Another way-----------------------------
}
public static object GetDetachedEntityId<TEntity>(object instance, ISessionFactory sessionFactory) where TEntity : BaseEntity
{
Type entityType = typeof(TEntity);
var entityName = (sessionFactory as ISessionFactoryImplementor).TryGetGuessEntityName(entityType);
AbstractEntityPersister persister = (sessionFactory as ISessionFactoryImplementor).TryGetEntityPersister(entityName) as AbstractEntityPersister;
if(persister != null)
{
PropertyInfo idPropertyInfo = entityType.GetProperty(persister.IdentifierPropertyName);
object identifier = idPropertyInfo.GetValue(instance, null);
return identifier;
}
return null;
}
Upvotes: 1
Reputation: 14535
You need to get hold of the PersistenceContext, like this:
using System.Linq;
using NHibernate;
public static T GetInstanceFromCache<T>(this ISession session, object key) where T : class
{
var entity = session.GetSessionImplementation().PersistenceContext.EntitiesByKey.SingleOrDefault(x => x.Value is T && x.Key.Identifier == key);
return entity as T;
}
Upvotes: 3