0

I am struggling to get second level caching and transactions working in my asp.net-mvc site and I am thinking it has to do with how I have my session managements setup.

Basically I have the following classes:

  • NhibernateRepository
  • SessionManager

and I am using Unity IOC Container:

this.RegisterType<IRepository, NHibernateRepository>(new PerResolveLifetimeManager());
this.RegisterType<ISessionManager, SessionManager>(new PerResolveLifetimeManager());

The NhibernateRepository class looks like this with a Session property

  public NHibernateRepository(UserModel userModel, ISessionManager sessionManager)
    {
        UserModel = userModel;
        SessionManager = sessionManager;
    }

    public ISession Session
    {
        get
        {
            using (_lock.WaitToRead())
            {
                if (_session != null) return _session;
            }
            using (_lock.WaitToWrite())
            {
                if (_session != null) return _session;
                _session = SessionManager.GetSession(UserModel == null ? "Task" : UserModel.FullName);
                return _session;
            }
        }
    }

The session Manager class looks like this:

public class SessionManager : ISessionManager
{
    private static readonly ResourceLock _lock = new OneManyResourceLock();

    public static ISessionFactory Factory { get; set; }


    public ISession GetSession(string userName)
    {
        ISession session = GetSessionFactory().OpenSession(new AuditInterceptor(userName));
        return session;
    }

    private static ISessionFactory GetSessionFactory()
    {
        using (_lock.WaitToRead())
        {
            if (Factory != null) return Factory;
        }
        using (_lock.WaitToWrite())
        {
            if (Factory != null) return Factory;
            string connectionString = ConfigurationManager.ConnectionStrings["DomainConnection"].ConnectionString;
            Factory = FluentlyConfigureFactory(connectionString, false);
            return Factory;
        }
    }

    private static ISessionFactory FluentlyConfigureFactory(string connectionString, bool showSql)
    {
          MsSqlConfiguration databaseConfiguration = MsSqlConfiguration.MsSql2005
            .ConnectionString(c => c.Is(connectionString))
            .Dialect<SparcMsSqlDialect>()
            .UseOuterJoin()
            .UseReflectionOptimizer();

        if (showSql)
        {
            databaseConfiguration.ShowSql();                
        }

        databaseConfiguration.Raw("generate_statistics", showSql.ToString());

        FluentConfiguration configuration = Fluently.Configure().Database(databaseConfiguration);
       return configuration
            .Mappings(m => m.FluentMappings.AddFromAssemblyOf<ApplicationMap>().Conventions.Add(typeof(Conventions)))
            .ExposeConfiguration(
                c => {
                    c.SetProperty("cache.provider_class", "NHibernate.Caches.SysCache.SysCacheProvider, NHibernate.Caches.SysCache");
                    c.SetProperty("cache.use_second_level_cache", "true");
                    c.SetProperty("cache.use_query_cache", "true");
                    c.SetProperty("expiration", "86400");
                })
            .BuildSessionFactory();
    }

Does anyone see anything fundamentally wrong with this? From googling I see all different opinions on how you should setup asp.net-mvc with nhibernate (adding transaction in beginRequest and committing on endRequest, etc) but I can't find the canonical way of getting it working with second level caching, etc that seems to be the best practice for having high scalability, etc

I tried adding transactions into this code given what I have read but i now seem to be getting this error:

    Initializing[ (one of my domain objects) #1]-Could not initialize proxy - no Session. 

so I reverted that code. Basically I am hoping there is a "go to" best practice at this point for using any second level cache, transactions in asp.net-mvc . .

leora
  • 188,729
  • 360
  • 878
  • 1,366

1 Answers1

2

I like to use an implementation of memory cache class which practically handles the object locks for you, I've implemented a custom module for a 2 level cache for nHibernate and you can plug by doing some small configuration, for that you need to implement is ICache interface.

 public class NHibernateCache2 : ICache
    {
        private static readonly IInternalLogger Log = LoggerProvider.LoggerFor(typeof(NHibernateCache2));
        private readonly string _region;
        private string _regionPrefix;
        private readonly MemoryCache _cache;
        private TimeSpan _expiration;
        private CacheItemPriority _priority;
        // The name of the cache key used to clear the cache. All cached items depend on this key.
        private readonly string _rootCacheKey;
        private bool _rootCacheKeyStored;
        private static readonly TimeSpan DefaultExpiration = TimeSpan.FromSeconds(300);
        private static readonly string DefauktRegionPrefix = string.Empty;
        private const string CacheKeyPrefix = "NHibernate-Cache:";

        public NHibernateCache2():this("nhibernate", null)
        {
        }

        public NHibernateCache2(string region):this(region, null)
        {
        }

        /// There are two (2) configurable parameters:
        /// expiration = number of seconds to wait before expiring each item
        /// priority = a numeric cost of expiring each item, where 1 is a low cost, 5 is the highest, and 3 is normal. Only values 1 through 5 are valid.
        /// All parameters are optional. The defaults are an expiration of 300 seconds and the default priority of 3.

        public NHibernateCache2(string region, IDictionary<string, string> properties)
        {
            _region = region;
            _cache = MemoryCache.Default;
            Configure(properties);
            _rootCacheKey = GenerateRootCacheKey();
            StoreRootCacheKey();
        }

        /// Defines property in order to get the region for the NHibernate's Cache.
        public string Region
        {
            get { return _region; }
        }

        /// Obtains a expiration value that indicates the time in seconds after which an object is automatically 
        /// evicted from the cache.
        public TimeSpan Expiration
        {
            get { return _expiration; }
        }

        /// Obtains a priority value that indicates the likelihood that an object of that region evicts 
        /// another already cached object of a lower priority region.
        public CacheItemPriority Priority
        {
            get { return _priority; }
        }

        private void Configure(IDictionary<string, string> props)
        {
            if (props == null)
            {
                if (Log.IsWarnEnabled)
                {
                    Log.Warn("configuring cache with default values");
                }
                _expiration = DefaultExpiration;
                _priority = CacheItemPriority.Default;
                _regionPrefix = DefauktRegionPrefix;
            }
            else
            {
                _priority = GetPriority(props);
                _expiration = GetExpiration(props);
                _regionPrefix = GetRegionPrefix(props);
            }
        }

        private static string GetRegionPrefix(IDictionary<string, string> props)
        {
            string result;
            if (props.TryGetValue("regionPrefix", out result))
            {
                Log.DebugFormat("new regionPrefix :{0}", result);
            }
            else
            {
                result = DefauktRegionPrefix;
                Log.Debug("no regionPrefix value given, using defaults");
            }
            return result;
        }

        private static TimeSpan GetExpiration(IDictionary<string, string> props)
        {
            TimeSpan result = DefaultExpiration;
            string expirationString;
            if (!props.TryGetValue("expiration", out expirationString))
            {
                props.TryGetValue(NHibernate.Cfg.Environment.CacheDefaultExpiration, out expirationString);
            }

            if (expirationString != null)
            {
                try
                {
                    int seconds = Convert.ToInt32(expirationString);
                    result = TimeSpan.FromSeconds(seconds);
                    Log.Debug("new expiration value: " + seconds);
                }
                catch (Exception ex)
                {
                    Log.Error("error parsing expiration value");
                    throw new ArgumentException("could not parse 'expiration' as a number of seconds", ex);
                }
            }
            else
            {
                if (Log.IsDebugEnabled)
                {
                    Log.Debug("no expiration value given, using defaults");
                }
            }
            return result;
        }

        private static CacheItemPriority GetPriority(IDictionary<string, string> props)
        {
            CacheItemPriority result = CacheItemPriority.Default;
            string priorityString;
            if (props.TryGetValue("priority", out priorityString))
            {
                result = ConvertCacheItemPriorityFromXmlString(priorityString);
                if (Log.IsDebugEnabled)
                {
                    Log.Debug("new priority: " + result);
                }
            }
            return result;
        }


        private static CacheItemPriority ConvertCacheItemPriorityFromXmlString(string priorityString)
        {
            if (string.IsNullOrEmpty(priorityString))
            {
                return CacheItemPriority.Default;
            }
            var ps = priorityString.Trim().ToLowerInvariant();
            if (ps.Length == 1 && char.IsDigit(priorityString, 0))
            {
                // the priority is specified as a number
                int priorityAsInt = int.Parse(ps);
                if (priorityAsInt >= 1 && priorityAsInt <= 6)
                {
                    return (CacheItemPriority)priorityAsInt;
                }
            }
            else
            {
                /// change for your own priority settings
                switch (ps)
                {
                    case "abovenormal":
                        return CacheItemPriority.Default;
                    case "belownormal":
                        return CacheItemPriority.Default;
                    case "default":
                        return CacheItemPriority.Default;
                    case "high":
                        return CacheItemPriority.Default;
                    case "low":
                        return CacheItemPriority.Default;
                    case "normal":
                        return CacheItemPriority.Default;
                    case "notremovable":
                        return CacheItemPriority.NotRemovable;
                }
            }
            Log.Error("priority value out of range: " + priorityString);
            throw new IndexOutOfRangeException("Priority must be a valid System.Web.Caching.CacheItemPriority; was: " + priorityString);
        }

        private string GetCacheKey(object key)
        {
            return String.Concat(CacheKeyPrefix, _regionPrefix, _region, ":", key.ToString(), "@", key.GetHashCode());
        }

        /// Gets an object that exist in the second level cache of NHibernate by the specified key.
        ///A unique identifier for the cache entry to get.
        ///Returns an entry from the NHibernate's Cache.
        public object Get(object key)
        {
            if (key == null)
            {
                return null;
            }
            string cacheKey = GetCacheKey(key);
            if (Log.IsDebugEnabled)
            {
                Log.Debug(String.Format("Fetching object '{0}' from the cache.", cacheKey));
            }

            object obj = _cache.Get(cacheKey);
            if (obj == null)
            {
                return null;
            }

            var de = (DictionaryEntry)obj;
            if (key.Equals(de.Key))
            {
                return de.Value;
            }
            else
            {
                return null;
            }
        }

        /// Adds a specific object inside the in the second level cache of NHibernate by using its key and its content.
        /// A key value of an item from the second level cache of NHibernate.
        /// Data for an entry of second level cache of NHibernate.
        public void Put(object key, object value)
        {
            if (key == null)
            {
                throw new ArgumentNullException("key", "null key not allowed");
            }
            if (value == null)
            {
                throw new ArgumentNullException("value", "null value not allowed");
            }
            string cacheKey = GetCacheKey(key);
            if (_cache[cacheKey] != null)
            {
                if (Log.IsDebugEnabled)
                {
                    Log.Debug(String.Format("updating value of key '{0}' to '{1}'.", cacheKey, value));
                }

                // Remove the key to re-add it again below
                _cache.Remove(cacheKey);
            }
            else
            {
                if (Log.IsDebugEnabled)
                {
                    Log.Debug(String.Format("adding new data: key={0}&value={1}", cacheKey, value));
                }
            }

            if (!_rootCacheKeyStored)
            {
                StoreRootCacheKey();
            }

            var cacheItemPolicy = new CacheItemPolicy()
            {
                AbsoluteExpiration = DateTime.Now.Add(_expiration),
                SlidingExpiration = ObjectCache.NoSlidingExpiration,
                Priority = _priority,
            };
            cacheItemPolicy.ChangeMonitors.Add(_cache.CreateCacheEntryChangeMonitor(new[] { _rootCacheKey }));
            _cache.Add(
                cacheKey,
                new DictionaryEntry(key, value),
                cacheItemPolicy);
        }

        /// Removes a cache entry from second level cache of NHibernate by a key. 
        /// A key value of an item from second level cache of NHibernate.
        public void Remove(object key)
        {
            if (key == null)
            {
                throw new ArgumentNullException("key");
            }
            string cacheKey = GetCacheKey(key);
            if (Log.IsDebugEnabled)
            {
                Log.Debug("removing item with key: " + cacheKey);
            }
            _cache.Remove(cacheKey);
        }

        /// Removes an object/item from second level cache of NHibernate.
        public void Clear()
        {
            RemoveRootCacheKey();
            StoreRootCacheKey();
        }

        /// Generate a unique root key for all cache items to be dependant upon
        private string GenerateRootCacheKey()
        {
            return GetCacheKey(Guid.NewGuid());
        }

        private void RootCacheItemRemoved(CacheEntryRemovedArguments arguments)
        {
            _rootCacheKeyStored = false;
        }

        private void StoreRootCacheKey()
        {
            _rootCacheKeyStored = true;
            var policy = new CacheItemPolicy
            {
                AbsoluteExpiration = ObjectCache.InfiniteAbsoluteExpiration,
                SlidingExpiration = ObjectCache.NoSlidingExpiration,
                Priority = CacheItemPriority.Default,
                RemovedCallback = RootCacheItemRemoved
            };
            _cache.Add(
                _rootCacheKey,
                _rootCacheKey,
                policy);
        }

        private void RemoveRootCacheKey()
        {
            _cache.Remove(_rootCacheKey);
        }

        /// Clears the second level cache of NHibernate.
        public void Destroy()
        {
            Clear();
        }

        public void Lock(object key)
        {
            // Do nothing
        }

        public void Unlock(object key)
        {
            // Do nothing
        }
        /// Obtains the next timestamp value.
        public long NextTimestamp()
        {
            return Timestamper.Next();
        }

        /// Defines property in order to get the sliding expiration time for the second level cache of NHibernate.
        public int Timeout
        {
            get { return Timestamper.OneMs * 60000; } // 60 seconds
        }

        /// Retrieves the name of NHibernate second level cache region.
        public string RegionName
        {
            get { return _region; }
        }
    }

then you need to define an ICacheProvider implementation:

public class NHibernateCacheProvider2 : ICacheProvider
    {
        private static readonly Dictionary<string, ICache> Caches;
        private static readonly IInternalLogger Log;

        static NHibernateCacheProvider2()
        {
            Log = LoggerProvider.LoggerFor(typeof(NHibernateCacheProvider2));
            Caches = new Dictionary<string, ICache>();
        }

        /// Builds a new SysCache through the region and a collection of properties.
        /// regionName: The name of the cache region.
        /// properties: Configuration settings.
        /// returns A new instance of NHibernateCache by using a region of the cache.
        public ICache BuildCache(string regionName, IDictionary<string, string> properties)
        {
            if (regionName == null)
            {
                regionName = string.Empty;
            }

            ICache result;
            if (Caches.TryGetValue(regionName, out result))
            {
                return result;
            }

            // create cache
            if (properties == null)
            {
                properties = new Dictionary<string, string>(1);
            }

            if (Log.IsDebugEnabled)
            {
                var sb = new StringBuilder();
                sb.Append("building cache with region: ").Append(regionName).Append(", properties: ");

                foreach (KeyValuePair<string, string> de in properties)
                {
                    sb.Append("name=");
                    sb.Append(de.Key);
                    sb.Append("&value=");
                    sb.Append(de.Value);
                    sb.Append(";");
                }
                Log.Debug(sb.ToString());
            }
            return new NHibernateCache2(regionName, properties);
        }

        public long NextTimestamp()
        {
            return Timestamper.Next();
        }


        public void Start(IDictionary<string, string> properties) { //your impl it's not necessary }

        public void Stop() { }

    }

if you are using fluent nhibernate you can register it with the following configuration:

Fluently.Configure().Database(MsSqlConfiguration.MsSql2008.
ConnectionString(builder =>   builder.FromConnectionStringWithKey(connectionStringKey)))
.ExposeConfiguration(c =>{c.SetProperty("show_sql", "true");}).
Cache(builder =>builder.ProviderClass<NHibernateCacheProvider2().
UseSecondLevelCache().UseQueryCache())

I hope that helps

pedrommuller
  • 15,741
  • 10
  • 76
  • 126
  • I am confused why you would write your owner implementation when there are many that exists out of the box. Anyway, I am not sure you answered the question about how to setup the repository and sessionmanager – leora May 03 '14 at 14:12