How to avoid cache misses with memory cache in .net-core-2.1 - caching

How is it possible to update the cache in the background to avoid cache misses?
In .net-core-2.1 I can add a memory cache like so:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
}
}
Then it's very straightforward to use:
[Route("api")]
public class DataController : Controller
{
private readonly IMemoryCache _cache;
private readonly DataContext _dataContext;
public DataController(IMemoryCache cache, DataContext dataContext)
{
_cache = cache;
_dataContext = dataContext;
}
[HttpGet]
[Route("GimmeCachedData")]
public async Task<IActionResult> Get()
{
var cacheEntry = await
_cache.GetOrCreateAsync("MyCacheKey", entry =>
{
entry.AbsoluteExpiration = DateTime.Now.AddSeconds(20);
return Task.FromResult(_dataContext.GetOrders(DateTime.Now));
});
return Ok(cacheEntry);
}
}
However, after 20 seconds of amazingly fast cached powered bliss infused requests, as expected, the cached item is expired and the next request is stalled because of a cache-miss, and subsequent data loading.
Argh! so the cache only works sometimes. Why not have the option of having it work all the time?
How can I add functionality to:
return the old item (in the meantime) AND
automatically update the cache when items expire or are noticed to be expired so the next request will get the updated value?
In trying to solve this problem I have encountered 2 main obstacles with my implementation using an IHostedService:
When the cached item is expired it's evicted and no longer available; meaning I can't return it.
Updating cached items that require the database cause those calls to happen out of scope.
This cache update can kick off either directly after noticing a cache miss, or by actively monitoring for the next item to expire.
I've tried rolling my own cache (adding it as a singleton) using a ConcurrentDictionary<String, CacheItem>. The CacheItem class contains properties for the Value, Expiration, and a Factory (i.e.: a value-returning-delegate). But I found, as this delegate is probably set at request time and called in the IHostedService background thread, it caused context out of scope exception.

I have found a solution that seems to work.
Implement an IHostedService (extended from BackgroundService class). This class will serve as the background thread managed by the .net core framework. The background thread will keep the cache updates going (by calling ICache.UpdateCache as explained below), to avoid request-time cache misses.
public class CacheUpdateService : BackgroundService
{
private readonly ILogger<CacheUpdateService> _logger;
private readonly IServiceProvider _serviceProvider;
private readonly ICache _cache;
public CacheUpdateService(ILogger<CacheUpdateService> logger, IServiceProvider serviceProvider, ICache cache)
{
_logger = logger;
_serviceProvider = serviceProvider;
_cache = cache;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogDebug("CacheUpdateService is starting.");
stoppingToken.Register(Dispose);
while (!stoppingToken.IsCancellationRequested)
{
try
{
using (var scope = _serviceProvider.CreateScope())
{
var dataContext = scope.ServiceProvider.GetRequiredService<DataContext>();
// This tight loop calls the UpdateCache, which will block if no updates are necessary
await Task.Run(() => _cache.UpdateCache(dataContext), stoppingToken);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception in the CacheUpdateService");
}
}
_logger.LogDebug("CacheUpdateService has stopped.");
}
public override void Dispose()
{
using(var scope = _serviceProvider.CreateScope())
{
var scopedProcessingService = scope.ServiceProvider.GetRequiredService<ICache>();
// Dispose here on ICache will release any blocks
scopedProcessingService.Dispose();
}
base.Dispose();
}
}
The Cache class below implements the background UpdateCache method which will update 1 expired item at a time. Prioritizing the one most expired. It also implements request-scoped GetOrCreate method. Note I'm using a delegate (Func<IDataContext, Object>) in the CacheEntry as the value population factory. This allows Cache class to inject a properly scoped DataContext (received from the IHostedService) and it also allows the caller to specify which method of the DataContext is called to get the results of the specific cache key value. Notice I'm using an AutoResetEvent to wait for 1st-time data population as well as a timer to kick off the next cache refresh. This implementation will suffer a cache-miss for the 1st time the item is called (and I guess after it hasn't been called for more than 1 hour; as it will be evicted after 1 hr.).
public class CacheEntry
{
public String Key { get; set; }
public Object Value { get; set; }
public Boolean Updating { get; set; }
public Int32 ExpirySeconds { get; set; }
public DateTime Expiration { get; set; }
public DateTime LastAccessed { get; set; }
public Func<IDataContext, Object> ValueFactory { get; set; }
}
public interface ICache : IDisposable
{
void UpdateCache(IDataContext dataContext);
T GetOrCreate<T>(String key, Func<IDataContext, T> factory, Int32 expirySeconds = 0) where T : class;
}
public class Cache : ICache
{
private readonly ILogger _logger;
private readonly ConcurrentDictionary<String, CacheEntry> _cache;
private readonly AutoResetEvent _governor;
public Cache(ILogger<Cache> logger)
{
_logger = logger;
_cache = new ConcurrentDictionary<String, CacheEntry>();
_governor = new AutoResetEvent(false);
}
public void Dispose()
{
_governor.Set();
}
public static Int32 CacheForHour => 3600;
public static Int32 CacheForDay => 86400;
public static Int32 CacheIndefinitely => 0;
public void UpdateCache(IDataContext dataContext)
{
var evictees = _cache.Values
.Where(entry => entry.LastAccessed.AddHours(1) < DateTime.Now)
.Select(entry => entry.Key)
.ToList();
foreach (var evictee in evictees)
{
_logger.LogDebug($"Evicting: {evictee}...");
_cache.Remove(evictee, out _);
}
var earliest = _cache.Values
.Where(entry => !entry.Updating)
.OrderBy(entry => entry.Expiration)
.FirstOrDefault();
if (earliest == null || earliest.Expiration > DateTime.Now)
{
var timeout = (Int32) (earliest?.Expiration.Subtract(DateTime.Now).TotalMilliseconds ?? -1);
_logger.LogDebug($"Waiting {timeout}ms for next expiry...");
_governor.WaitOne(timeout);
return;
}
try
{
_logger.LogDebug($"Updating cache for: {earliest.Key}...");
earliest.Updating = true;
earliest.Value = earliest.ValueFactory(dataContext);
earliest.Expiration = earliest.ExpirySeconds > 0
? DateTime.Now.AddSeconds(earliest.ExpirySeconds)
: DateTime.MaxValue;
_governor.Set();
}
finally
{
earliest.Updating = false;
}
}
public T GetOrCreate<T>(String key, Func<IDataContext, T> factory, Int32 expirySeconds = -1) where T : class
{
var success = _cache.TryGetValue(key, out var entry);
if (success && entry.Value != null)
{
entry.LastAccessed = DateTime.Now;
return (T) entry.Value;
}
if (entry == null)
{
_logger.LogDebug($"Adding new entry to the cache: {key}...");
entry = new CacheEntry
{
Key = key,
Expiration = DateTime.MinValue,
ExpirySeconds = expirySeconds,
LastAccessed = DateTime.Now,
ValueFactory = factory
};
_cache.TryAdd(key, entry);
_governor.Set();
}
while (entry.Value == null)
{
_logger.LogDebug($"Waiting for 1st time cache update: {entry.Key}...");
_governor.WaitOne();
}
return (T)entry.Value;
}
}
The DataContext class is then created like so. Using Dapper for example to retrieve the data from the database:
public class DataContext : DbContext, IDataContext
{
private readonly IOptions<Settings> _settings;
private String _databaseServer;
public DataContext(IOptions<Settings> settings)
{
_settings = settings;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlServer(_settings.Value.ConnectionString);
}
public IEnumerable<OrderInfo> GetOrders(DateTime date)
{
return Database.GetDbConnection().Query<OrderInfo>(
$"SchemaName.usp_GetOrders",
new {Date = date},
commandType: CommandType.StoredProcedure);
}
}
In the controllers the ICache is injected and used as follows:
[HttpGet]
[Route("Orders/{date}")]
public IActionResult GetOrders(DateTime date)
{
var result = _cache.GetOrCreate(
$"GetOrders_{date:yyyyMMdd}",
context => context.GetOrders(date),
date.Date < DateTime.Today ? Cache.CacheIndefinitely : 20);
return Ok(result);
}
Finally register the classes as a Singleton in the DI setup
services.AddOptions();
services.Configure<Settings>(Configuration);
services.AddLogging();
services.AddDbContext<DataContext>();
services.AddSingleton<ICache, Cache>();
services.AddSingleton<IHostedService, CacheUpdateService>();

I created a similar IHostedService based project https://github.com/dpatekar/CacheAnt
Take a look, it is very simple to use.
It is available as a NuGet package also.

Related

ItemsSource doesn't show/bind when ObservableCollection is filled before loading the page

I have a carouselview, in that view I have an ObservableCollection binded as an itemssource. I am able to bind the collection and it would show when I execute the viewmodel's command in the OnAppearing event.
Code that works:
Second Page
public partial class SecondPage : ContentPage
{
public Coll(bool hard, string subject)
{
InitializeComponent();
var vm = (DataSelectionViewModel)BindingContext;
vm.Hard = hard;
vm.Subject = subject;
/* had to set "hard" and "subject" here again, otherwise data won't load */
}
protected override async void OnAppearing()
{
var vm = (DataSelectionViewModel)BindingContext;
base.OnAppearing();
await vm.LoadData.ExecuteAsync().ConfigureAwait(false);
}
}
The viewmodel for second page
public class DataSelectionViewModel : BaseViewModel
{
private string subject;
public string Subject { get => subject; set => SetProperty(ref subject, value); }
private bool hard;
public bool Hard { get => hard; set => SetProperty(ref hard, value); }
public ObservableCollection<Items> FilteredData { get; set; }
public UserSelectionViewModel()
{
_dataStore = DependencyService.Get<IDataStore>();
LoadData= new AsyncAwaitBestPractices.MVVM.AsyncCommand(FilterData);
FilteredData = new ObservableCollection<Items>();
}
public async Task FilterData()
{
FilteredData.Clear();
var filtereddata = await _dataStore.SearchData(Hard, Subject).ConfigureAwait(false);
foreach (var data in filtereddata)
{
FilteredData.Add(data);
}
}
}
First Page where second page gets Hard and Subject values
private async void ButtonClick(object sender, EventArgs e)
{
var vm = (BaseViewModel)BindingContext;
vm.Hard = HardButtonSelected == Hard;
vm.Subject = vm.Subject.ToLower();
await Navigation.PushAsync(new SecondPage(vm.Hard, vm.Subject));
}
So I want to change my code so that if I press the button on the first page, data instantly starts to filter and add to the ObservableCollection and when it's finished, then navigate to the second page. However if I try to load it to the BaseViewModel and then get the data from the second viewmodel it won't show the data.
Code that doesn't work:
Second Page
public partial class SecondPage : ContentPage
{
public SecondPage()
{
InitializeComponent();
}
}
The viewmodel for second page
public class DataSelectionViewModel : BaseViewModel
{
public ObservableCollection<Items> FilteredData { get; set; }
public UserSelectionViewModel()
{
FilteredData = new ObservableCollection<Items>();
}
}
BaseViewModel
public class BaseViewModel : INotifyPropertyChanged
{
private string subject;
public string Subject { get => subject; set => SetProperty(ref subject, value); }
private bool hard;
public bool Hard { get => hard; set => SetProperty(ref hard, value); }
public ObservableCollection<Items> FilteredData { get; set; }
/* BaseViewModel has implementation of SetProperty */
}
First Page where second page gets Hard and Subject values
private async void ButtonClick(object sender, EventArgs e)
{
var vm = (BaseViewModel)BindingContext;
vm.Hard = HardButtonSelected == Hard;
vm.Subject = vm.Subject.ToLower();
}
First Page viewmodel
public class FirstPageViewModel : BaseViewModel
{
public IAsyncCommand MehetButtonClickedCommand { get; }
readonly IPageService pageService;
readonly IFeladatokStore _feladatokStore;
public FeladatValasztoViewModel()
{
_dataStore = DependencyService.Get<IDataStore>();
ButtonClickedCommand = new AsyncCommand(ButtonClicked);
pageService = DependencyService.Get<IPageService>();
}
private async Task ButtonClicked()
{
await FilterData();
await pageService.PushAsync(new SecondPage());
}
private async Task FilterData()
{
FilteredData.Clear();
var datas = await _dataStore.SearchData(Subject, Hard).ConfigureAwait(false);
foreach (var data in datas)
{
FilteredData.Add(data);
}
}
So basically this gives a null exception error. I also tried giving the ObservableCollection as an argument for SecondPage(ObservableCollection x) and that did work, but because I had to make another ObservableCollection for it and copy from one to another it stopped being async and froze for a couple of seconds. So my question is how can I make this async?
To avoid delay, build the new collection in a private variable. Then set the property to that variable:
// Constructor with parameter
public SomeClass(IList<Items> data)
{
SetFilteredDataCopy(data);
}
public ObservableCollection<Items> FilteredData { get; set; }
private void SetFilteredDataCopy(IList<Items> src)
{
var copy = new ObservableCollection<Items>();
foreach (var item in src)
copy.Add(item);
FilteredData = copy;
//MAYBE OnPropertyChanged(nameof(FilteredData));
}

Chain CollectionChanged Events

I'm trying to see how it would be possible to chain together x number of ObservableCollections.CollectionChanged event, exposed as a N level depth object tree to a single parent level CollectionChanged event that consumers can listen to? Essentially I want to funnel or bubble all child CollectionChanged events up to the top most parent. A number of solution I've noticed that tackle similar issues make an assumption of a fixed number of levels, say 2 deep. I idea is to support any level of depth.
Originally I had hoped I could just pass the instance of the FieldInfos to the child constructors and attach directly to the handler. However i get an error stating the "Event 'CollectionChanged' can only appear on the left hand side of+= or -=.
Thanks,
public class FieldInfos
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
private ObservableCollection<Field> _fields;
public ObservableCollection<Field> Fields => _fields ?? (_fields = new ObservableCollection<Field>());
}
public class Field
{
public string Name;
private ObservableCollection<FieldInstance> _instances;
public ObservableCollection<FieldInstance> Instances => _instances ?? (_instances = new ObservableCollection<FieldInstance>());
}
public class FieldInstance
{
public string Id { get; set; }
}
The simplest approach is subclass the original ObservableCollection<T>.
You'd need at least one interface to avoid covariance problems. You can also have your own classes to implement the INotifyDescendantsChanged interface.
public interface INotifyDescendantsChanged
{
event NotifyCollectionChangedEventHandler DescendantsChanged;
}
public class ObservableBubbleCollection<T> : ObservableCollection<T>, INotifyDescendantsChanged
{
public event NotifyCollectionChangedEventHandler DescendantsChanged;
protected virtual void OnDescendantsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler handler = DescendantsChanged;
if (handler != null)
handler(sender, e);
}
private readonly Func<T, INotifyDescendantsChanged> childSelector;
public ObservableBubbleCollection() { }
public ObservableBubbleCollection(Func<T, INotifyDescendantsChanged> childSelector)
{
this.childSelector = childSelector;
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
OnDescendantsChanged(this, e);
if (childSelector == null)
return;
if (e.NewItems != null)
foreach (var item in e.NewItems.Cast<T>())
childSelector(item).DescendantsChanged += OnDescendantsChanged;
if (e.OldItems != null)
foreach (var item in e.OldItems.Cast<T>())
childSelector(item).DescendantsChanged -= OnDescendantsChanged;
}
}
To use it, replace instances of ObservableCollection and pass a selector to the collection.
public class FieldInfos
{
private ObservableBubbleCollection<Field> _fields;
public ObservableBubbleCollection<Field> Fields => _fields ?? (_fields = new ObservableBubbleCollection<Field>(fi => fi.Instances));
}
public class Field
{
public string Name;
private ObservableBubbleCollection<FieldInstance> _instances;
public ObservableBubbleCollection<FieldInstance> Instances => _instances ?? (_instances = new ObservableBubbleCollection<FieldInstance>());
}
public class FieldInstance
{
public string Id { get; set; }
}
static class Program
{
static void Main(string[] args)
{
var fi = new FieldInfos();
fi.Fields.DescendantsChanged += (sender, e) =>
{
Console.WriteLine("Change from {0}", sender.GetType());
};
var field = new Field();
fi.Fields.Add(field);
field.Instances.Add(new FieldInstance());
Console.ReadLine();
}
}

What is the recommended way to doing nhibernate session management in asp.net-mvc that supports second level cache / transactions, etc?

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 . .
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

Castle Windsor LifestylePerWebRequest - Only seems to resolve on the first web request for ISession

I'm setting up my MVC project so that I resolve my ISession's on a PerWebRequest basis.
Here's what I have so far:
In my Castle Windsor Setup I register my ISession using a factory method:
Component.For<ISession>().UsingFactoryMethod(ctx => MsSql2008SessionFactory.OpenSession()).LifestylePerWebRequest()
In my Global.asax Application_Start() I Bind my ISession to NHibernate's CurrentSessionContext each time a request begins:
BeginRequest += delegate{
CurrentSessionContext.Bind(
MsSql2008SessionFactory.OpenSession());
};
EndRequest += delegate{
var session = MsSql2008SessionFactory
.SessionFactory
.GetCurrentSession();
if (session != null)
{
session.Dispose();
}
CurrentSessionContext
.Unbind(MsSql2008SessionFactory
.SessionFactory);
};
The first time I make a request to a page everything works fine.
The second time I make a request to a page I get an exception stating:
Session is closed! Object name: 'ISession'.
What am I not doing correctly?
This is how I do things may work for you. I use Fluent Nhibernate just in case some of the config doesn't jive.
public interface INHibernateSessionFactoryHelper
{
ISessionFactory CreateSessionFactory();
}
public class NhibernateSessionFactoryHelper
{
private static readonly string ConnectionString =
ConfigurationManager.ConnectionStrings["SqlConnectionString"].ToString();
public static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.ProxyFactoryFactory("NHibernate.Bytecode.DefaultProxyFactoryFactory, NHibernate")
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<EntityMap>())
.Database(
MsSqlConfiguration.MsSql2008.ConnectionString(ConnectionString).AdoNetBatchSize(1000))
.Cache(
c =>
c.ProviderClass<SysCacheProvider>().UseSecondLevelCache().UseQueryCache().UseMinimalPuts())
.ExposeConfiguration(c => c.SetProperty(Environment.GenerateStatistics, "true")
.SetProperty(Environment.SessionFactoryName, "My Session Factory")
.SetProperty(Environment.CurrentSessionContextClass, "web"))
.Diagnostics(d => d.Enable().OutputToFile(#"c:\temp\diags.txt"))
.BuildSessionFactory();
}
}
Then my Windsor installer looks like this
public class NHibernateInstaller:IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<ISessionFactory>().Instance(NhibernateSessionFactoryHelper.CreateSessionFactory()));
container.Register(Component.For<ISessionManager>().ImplementedBy<SessionManager>().LifestylePerWebRequest());
}
}
I have omitted the code for the SessionManager I use. Let me know if you would like it
UPDTAE: Here is the code I use for managing sessions and transactions( I found pieces of this scattered about the Internet but it all worked well without too much modification. ISessionManager is wired up per my previous example and injected in to the constuctor of my Services.
public interface ISessionManager : IDisposable
{
ISession Session { get; set; }
ISession GetSession();
}
public class SessionManager : ISessionManager
{
private readonly ISessionFactory _sessionFactory;
private TransactionScope _scope;
public SessionManager(ISessionFactory sessionFactory)
{
_sessionFactory = sessionFactory;
}
#region ISessionManager Members
public ISession Session { get; set; }
public ISession GetSession()
{
if (Session == null)
{
Session = _sessionFactory.OpenSession();
if (!CurrentSessionContext.HasBind(_sessionFactory))
{
_scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions {IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted});
Session.BeginTransaction(IsolationLevel.ReadCommitted);
CurrentSessionContext.Bind(Session);
}
}
Session = _sessionFactory.GetCurrentSession();
Session.FlushMode = FlushMode.Never;
return Session;
}
public void Dispose()
{
if (CurrentSessionContext.HasBind(_sessionFactory))
{
CurrentSessionContext.Unbind(_sessionFactory);
}
try
{
Session.Transaction.Commit();
_scope.Complete();
_scope.Dispose();
Session.Flush();
}
catch (Exception)
{
if (Session.Transaction != null && Session.Transaction.IsActive)
{
Session.Transaction.Rollback();
}
throw;
}
finally
{
Session.Close();
Session.Dispose();
}
}
#endregion
}
Example Constructor:
private readonly ISessionManager _sessionManager;
private readonly ISession _session;
public UserService(ISessionManager sessionManager)
{
_sessionManager = sessionManager;
_session = sessionManager.GetSession();
}
The answer to this turned out to be quite simple.
The repository that I was injecting my ISession into had a Singleton lifestyle.
This meant that the ISession that had been injected on the first request was also being used for the subsiquent requests (because my repository class was only being created at the start of the application) and was thus already disposed.

Fluent NHibernate Mapping test takes forever

I've recently started to learn Fluent NH, and I'm having some trouble with this test method. It takes forever to run (it's been running for over ten minutes now, and no sign of progress...).
[TestMethod]
public void Entry_IsCorrectlyMapped()
{
Action<PersistenceSpecification<Entry>> testAction = pspec => pspec
.CheckProperty(e => e.Id, "1")
.VerifyTheMappings();
TestMapping<Entry>(testAction);
}
with this helper method (slightly simplified - i have a couple of try/catch blocks too, to provide nicer error messages):
public void TestMapping<T>(Action<PersistenceSpecification<T>> testAction) where T : IEntity
{
using (var session = DependencyFactory.CreateSessionFactory(true).OpenSession())
{
testAction(new PersistenceSpecification<T>(session));
}
}
The DependencyFactory.CreateSessionFactory() method looks like this:
public static ISessionFactory CreateSessionFactory(bool buildSchema)
{
var cfg = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.InMemory())
.Mappings(m => m.FluentMappings.AddFromAssembly(typeof(Entry).Assembly));
if (buildSchema)
{
cfg = cfg.ExposeConfiguration(config => new SchemaExport(config).Create(false, true));
}
return cfg.BuildSessionFactory();
}
I've tried debugging, but I can't figure out where the bottleneck is. Why is this taking so long?
I would think it has to do with the way your trying to use the session together with the persistence spec. Make a base test class like the one below that provides you a session; if whole test takes longer than about 3 - 4 seconds max something is wrong.
Cheers,
Berryl
[TestFixture]
public class UserAutoMappingTests : InMemoryDbTestFixture
{
private const string _nickName = "berryl";
private readonly Name _name = new Name("Berryl", "Hesh");
private const string _email = "bhesh#cox.net";
protected override PersistenceModel _GetPersistenceModel() { return new UserDomainAutoMapModel().Generate(); }
[Test]
public void Persistence_CanSaveAndLoad_User()
{
new PersistenceSpecification<User>(_Session)
.CheckProperty(x => x.NickName, _nickName)
.CheckProperty(x => x.Email, _email)
.CheckProperty(x => x.Name, _name)
.VerifyTheMappings();
}
}
public abstract class InMemoryDbTestFixture
{
protected ISession _Session { get; set; }
protected SessionSource _SessionSource { get; set; }
protected Configuration _Cfg { get; set; }
protected abstract PersistenceModel _GetPersistenceModel();
protected PersistenceModel _persistenceModel;
[TestFixtureSetUp]
public void SetUpPersistenceModel()
{
_persistenceModel = _GetPersistenceModel();
}
[SetUp]
public void SetUpSession()
{
NHibInMemoryDbSession.Init(_persistenceModel); // your own session factory
_Session = NHibInMemoryDbSession.Session;
_SessionSource = NHibInMemoryDbSession.SessionSource;
_Cfg = NHibInMemoryDbSession.Cfg;
}
[TearDown]
public void TearDownSession()
{
NHibInMemoryDbSession.TerminateInMemoryDbSession();
_Session = null;
_SessionSource = null;
_Cfg = null;
}
}
public static class NHibInMemoryDbSession
{
public static ISession Session { get; private set; }
public static Configuration Cfg { get; private set; }
public static SessionSource SessionSource { get; set; }
public static void Init(PersistenceModel persistenceModel)
{
Check.RequireNotNull<PersistenceModel>(persistenceModel);
var SQLiteCfg = SQLiteConfiguration.Standard.InMemory().ShowSql();
SQLiteCfg.ProxyFactoryFactory(typeof(ProxyFactoryFactory).AssemblyQualifiedName);
var fluentCfg = Fluently.Configure().Database(SQLiteCfg).ExposeConfiguration(cfg => { Cfg = cfg; });
SessionSource = new SessionSource(fluentCfg.BuildConfiguration().Properties, persistenceModel);
Session = SessionSource.CreateSession();
SessionSource.BuildSchema(Session, true);
}
public static void TerminateInMemoryDbSession()
{
Session.Close();
Session.Dispose();
Session = null;
SessionSource = null;
Cfg = null;
Check.Ensure(Session == null);
Check.Ensure(SessionSource == null);
Check.Ensure(Cfg == null);
}
}

Resources