I have previously attempted to write a object-cache that I use in co-junction with Redis keeping a persistent data-model. Old post can be read here Cached-object Store c# with Redis client for persistent storage Cached-object Store c# with Redis client for persistent storage
I have previously attempted to write a object-cache that I use in co-junction with Redis keeping a persistent data-model. Old post can be read here Cached-object Store c# with Redis client for persistent storage
I have previously attempted to write a object-cache that I use in co-junction with Redis keeping a persistent data-model. Old post can be read here Cached-object Store c# with Redis client for persistent storage
Object-cache C# Take two
I have previously attempted to write a object-cache that I use in co-junction with Redis keeping a persistent data-model. Old post can be read here Cached-object Store c# with Redis client for persistent storage
Not focusing on Redis it's the Object-cache that is the important portion, one of the questions i previously got was what if the data-model changes in Redis how will the application know about this? It wont, and at current stage this is not something that is a concern, for now I use Redis just as a dump, read / writes happen from only "this" application.
Where is this used? In the environment where this is used, application itself is a REST / RPC (json) service where multiple clients connect and 99% of the time they read and most of the time its a matter of searching through very long lists of entities, I store entities in HashSet's where i override both Equals and GetHashCode code so that the "key" of each entity is it's unique Id of each entity (in some cases a composite key). With tests of millions of entities in the HashSet's finding single/multiple entities take a few milliseconds compared to several seconds or minutes at times when model/predicate is getting complex when I use ORM's such as EF / Castle ActiveRecord / native NHibernate. I am not trying to write my own ORM, however I duplicate some of the functionality that are typically found in ORM's.
Is this looking right? Previously my code contained a huge amount of mistakes and outright wrong code.
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Runtime.Caching;
using System.Linq;
using ServiceStack.Redis;
namespace Datamodel
{
public class Repository
{
private static readonly PooledRedisClientManager m = new PooledRedisClientManager();
readonly static ConcurrentDictionary<Type, HashSet<object>> _cache = new ConcurrentDictionary<Type, HashSet<object>>();
/// <summary>
/// Load {T} into Object-cache from Data Store.
/// </summary>
/// <typeparam name="T">class</typeparam>
public static void LoadIntoCache<T>() where T : class
{
// Lets make sure we never replace _cache[T] if key is already present.
if (!_cache.ContainsKey(typeof(T)))
{
_cache[typeof(T)] = new HashSet<object>(RedisGetAll<T>().Cast<object>().ToList());
}
}
/// <summary>
/// Ensures that given type exist in Object-cache.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private static Type EnsureTypeInCache(Type type)
{
// Check if type exist in _cache
if (!_cache.ContainsKey(type))
{
// I am aware that this portion should contain exception handling.
// Key not present adding key and empty cache.
_cache.TryAdd(type, new HashSet<object>());
}
return type;
}
/// <summary>
/// Find First {T} in Object-cache.
/// </summary>
/// <typeparam name="T">class</typeparam>
/// <param name="predicate">linq statement</param>
/// <returns></returns>
public static T FindFirstBy<T>(Func<T, bool> predicate) where T : class
{
// Lets prevent race conditions, locking down cache.
lock (EnsureTypeInCache(typeof(T)))
{
return _cache[typeof(T)].Cast<T>().Where(predicate).FirstOrDefault();
}
}
/// <summary>
/// Find Single {T} in Object-cache.
/// </summary>
/// <typeparam name="T">class</typeparam>
/// <param name="predicate">linq statement</param>
/// <returns></returns>
public static T FindSingleBy<T>(Func<T, bool> predicate) where T : class
{
// Lets prevent race conditions, locking down cache.
lock (EnsureTypeInCache(typeof(T)))
{
return _cache[typeof(T)].Cast<T>().Where(predicate).SingleOrDefault();
}
}
/// <summary>
/// Add or Update entity in Object-cache and Data Store.
/// </summary>
/// <typeparam name="T">class</typeparam>
/// <param name="predicate">linq expression</param>
/// <param name="entity">entity</param>
public static bool AddOrUpdate<T>(T entity) where T : class
{
// Lets prevent race conditions, locking down cache.
lock (EnsureTypeInCache(typeof(T)))
{
if (_cache[typeof(T)].Contains(entity))
{
_cache[typeof(T)].Remove(entity);
}
_cache[typeof(T)].Add(entity);
}
// Redis does not care if record is new or old as it will Add or Update regardless.
RedisStore<T>(entity);
return true;
}
/// <summary>
/// Delete single {T} from Object-cache and Data Store.
/// </summary>
/// <typeparam name="T">class</typeparam>
/// <param name="entity">class object</param>
public static void Remove<T>(T entity) where T : class
{
// Lets prevent race conditions, locking down cache.
lock (EnsureTypeInCache(typeof(T)))
{
if (_cache[typeof(T)].Contains(entity))
{
_cache[typeof(T)].Remove(entity);
}
RedisDelete<T>(entity);
}
}
/// <summary>
/// Find List<T>(predicate) in Object-cache.
/// </summary>
/// <typeparam name="T">class</typeparam>
/// <param name="predicate">linq statement</param>
/// <returns></returns>
public static List<T> FindBy<T>(Func<T, bool> predicate) where T : class
{
// Lets prevent race conditions, locking down cache.
lock (EnsureTypeInCache(typeof(T)))
{
return _cache[typeof(T)].Cast<T>().Where(predicate).ToList();
}
}
/// <summary>
/// Get all {T} from Object-cache.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static List<T> All<T>() where T : class
{
// Lets prevent race conditions, locking down cache.
lock (EnsureTypeInCache(typeof(T)))
{
return _cache[typeof(T)].Cast<T>().ToList();
}
}
/// <summary>
/// Get Next Sequence for the given {T} Entity from Data Store.
/// </summary>
/// <typeparam name="T">class</typeparam>
/// <returns>long</returns>
public static long Next<T>() where T : class
{
return RedisNext<T>();
}
#region Redis Commands
//
// Following methods are ment as static private methods.
//
private static long RedisNext<T>() where T : class
{
using (var ctx = m.GetClient())
return ctx.As<T>().GetNextSequence();
}
private static void RedisDelete<T>(T entity) where T : class
{
using (var ctx = m.GetClient())
ctx.As<T>().Delete(entity);
}
private static T RedisFind<T>(long id) where T : class
{
using (var ctx = m.GetClient())
return ctx.As<T>().GetById(id);
}
private static HashSet<T> RedisGetAll<T>() where T : class
{
using (var ctx = m.GetClient())
return new HashSet<T>(ctx.As<T>().GetAll());
}
private static void RedisStore<T>(T entity) where T : class
{
using (var ctx = m.GetClient())
ctx.Store<T>(entity);
}
#endregion
}
}