Amit Joshi
Amit Joshi

Reputation: 16389

NHibernate: How to get entity instance from session cache?

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.

Edit 1

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

Answers (3)

user2469321
user2469321

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

Amit Joshi
Amit Joshi

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

Ricardo Peres
Ricardo Peres

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

Related Questions