Here is my new version, thanks to Greg BurghardtGreg Burghardt measures was taken to make the code testable, I also changed some return values from list to IEnumerable to gain more power with defered LINQ query whenever I want to apply more logic on-top of the already ran predicate.
Here is my new version, thanks to Greg Burghardt measures was taken to make the code testable, I also changed some return values from list to IEnumerable to gain more power with defered LINQ query whenever I want to apply more logic on-top of the already ran predicate.
Here is my new version, thanks to Greg Burghardt measures was taken to make the code testable, I also changed some return values from list to IEnumerable to gain more power with defered LINQ query whenever I want to apply more logic on-top of the already ran predicate.
Fourth iteration with generic class constructor, less clutter and no longer need to have multiple types in same repository.
using System;
using System.Collections.Generic;
using System.Linq;
using ServiceStack.Redis;
namespace Datamodel
{
public interface IObjectRepository<T> where T : class
{
void LoadIntoCache();
T FindFirstBy(Func<T, bool> predicate);
T FindSingleBy(Func<T, bool> predicate);
bool AddOrUpdate(T entity);
void Remove(T entity);
IEnumerable<T> FindBy(Func<T, bool> predicate);
IEnumerable<T> All();
long Next();
bool Contains(T Entity);
}
public class ObjectRepository<T> : IObjectRepository<T> where T : class
{
private static readonly PooledRedisClientManager m = new PooledRedisClientManager();
private static HashSet<T> _cache = new HashSet<T>();
private IRedisClientsManager RedisManager { get; set; }
private HashSet<T> Cache { get; set; }
public ObjectRepository()
{
RedisManager = m;
Cache = _cache;
}
public ObjectRepository(IRedisClientsManager redisManager, HashSet<T> cache)
{
RedisManager = redisManager;
Cache = cache;
}
/// <summary>
/// Load {T} into Object-cache from Data Store.
/// </summary>
public void LoadIntoCache()
{
// Lets make sure we never replace _cache[T] if key is already present.
_cache = new HashSet<T>(RedisGetAll().ToList());
}
/// <summary>
/// Find First {T} in Object-cache.
/// </summary>
/// <param name="predicate">linq statement</param>
/// <returns></returns>
public T FindFirstBy(Func<T, bool> predicate)
{
// Lets prevent race conditions, locking down cache.
lock (_cache)
{
return _cache.Where(predicate).FirstOrDefault();
}
}
/// <summary>
/// Find Single {T} in Object-cache.
/// </summary>
/// <param name="predicate">linq statement</param>
/// <returns></returns>
public T FindSingleBy(Func<T, bool> predicate)
{
// Lets prevent race conditions, locking down cache.
lock (_cache)
{
return _cache.Where(predicate).SingleOrDefault();
}
}
/// <summary>
/// Tries to update or Add entity to Object-cache and Data Store.
/// </summary>
/// <param name="predicate">linq expression</param>
/// <param name="entity">entity</param>
public bool AddOrUpdate(T entity)
{
// Lets prevent race conditions, locking down cache.
lock (_cache)
{
if (_cache.Contains(entity))
{
_cache.Remove(entity);
}
_cache.Add(entity);
}
// Redis does not care if record is new or old as it will Add or Update regardless.
RedisStore(entity);
return true;
}
/// <summary>
/// Delete single {T} from Object-cache and Data Store.
/// </summary>
/// <param name="entity">class object</param>
public void Remove(T entity)
{
// Lets prevent race conditions, locking down cache.
lock (_cache)
{
if (_cache.Contains(entity))
{
_cache.Remove(entity);
}
RedisDelete(entity);
}
}
/// <summary>
/// Check if {T} exists in Object-cache.
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public bool Contains(T entity)
{
// Lets prevent race conditions, locking down cache.
lock (_cache)
{
return _cache.Contains(entity);
}
}
/// <summary>
/// Find List<T>(predicate) in Object-cache.
/// </summary>
/// <param name="predicate">linq statement</param>
/// <returns></returns>
public IEnumerable<T> FindBy(Func<T, bool> predicate)
{
// Lets prevent race conditions, locking down cache.
lock (_cache)
{
return _cache.Where(predicate);
}
}
/// <summary>
/// Get all {T} from Object-cache.
/// </summary>
/// <returns></returns>
public IEnumerable<T> All()
{
// Lets prevent race conditions, locking down cache.
lock (_cache)
{
return _cache;
}
}
/// <summary>
/// Get Next Sequence for the given {T} Entity from Data Store.
/// </summary>
/// <returns>long</returns>
public long Next()
{
return RedisNext();
}
#region Redis Commands
//
// Following methods are ment as static private methods.
//
private long RedisNext()
{
using (var ctx = m.GetClient())
return ctx.As<T>().GetNextSequence();
}
private void RedisDelete(T entity)
{
using (var ctx = m.GetClient())
ctx.As<T>().Delete(entity);
}
private T RedisFind(long id)
{
using (var ctx = m.GetClient())
return ctx.As<T>().GetById(id);
}
private HashSet<T> RedisGetAll()
{
using (var ctx = m.GetClient())
return new HashSet<T>(ctx.As<T>().GetAll());
}
private void RedisStore(T entity)
{
using (var ctx = m.GetClient())
ctx.Store<T>(entity);
}
#endregion
}
}
Fourth iteration with generic class constructor, less clutter and no longer need to have multiple types in same repository.
using System;
using System.Collections.Generic;
using System.Linq;
using ServiceStack.Redis;
namespace Datamodel
{
public interface IObjectRepository<T> where T : class
{
void LoadIntoCache();
T FindFirstBy(Func<T, bool> predicate);
T FindSingleBy(Func<T, bool> predicate);
bool AddOrUpdate(T entity);
void Remove(T entity);
IEnumerable<T> FindBy(Func<T, bool> predicate);
IEnumerable<T> All();
long Next();
bool Contains(T Entity);
}
public class ObjectRepository<T> : IObjectRepository<T> where T : class
{
private static readonly PooledRedisClientManager m = new PooledRedisClientManager();
private static HashSet<T> _cache = new HashSet<T>();
private IRedisClientsManager RedisManager { get; set; }
private HashSet<T> Cache { get; set; }
public ObjectRepository()
{
RedisManager = m;
Cache = _cache;
}
public ObjectRepository(IRedisClientsManager redisManager, HashSet<T> cache)
{
RedisManager = redisManager;
Cache = cache;
}
/// <summary>
/// Load {T} into Object-cache from Data Store.
/// </summary>
public void LoadIntoCache()
{
// Lets make sure we never replace _cache[T] if key is already present.
_cache = new HashSet<T>(RedisGetAll().ToList());
}
/// <summary>
/// Find First {T} in Object-cache.
/// </summary>
/// <param name="predicate">linq statement</param>
/// <returns></returns>
public T FindFirstBy(Func<T, bool> predicate)
{
// Lets prevent race conditions, locking down cache.
lock (_cache)
{
return _cache.Where(predicate).FirstOrDefault();
}
}
/// <summary>
/// Find Single {T} in Object-cache.
/// </summary>
/// <param name="predicate">linq statement</param>
/// <returns></returns>
public T FindSingleBy(Func<T, bool> predicate)
{
// Lets prevent race conditions, locking down cache.
lock (_cache)
{
return _cache.Where(predicate).SingleOrDefault();
}
}
/// <summary>
/// Tries to update or Add entity to Object-cache and Data Store.
/// </summary>
/// <param name="predicate">linq expression</param>
/// <param name="entity">entity</param>
public bool AddOrUpdate(T entity)
{
// Lets prevent race conditions, locking down cache.
lock (_cache)
{
if (_cache.Contains(entity))
{
_cache.Remove(entity);
}
_cache.Add(entity);
}
// Redis does not care if record is new or old as it will Add or Update regardless.
RedisStore(entity);
return true;
}
/// <summary>
/// Delete single {T} from Object-cache and Data Store.
/// </summary>
/// <param name="entity">class object</param>
public void Remove(T entity)
{
// Lets prevent race conditions, locking down cache.
lock (_cache)
{
if (_cache.Contains(entity))
{
_cache.Remove(entity);
}
RedisDelete(entity);
}
}
/// <summary>
/// Check if {T} exists in Object-cache.
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public bool Contains(T entity)
{
// Lets prevent race conditions, locking down cache.
lock (_cache)
{
return _cache.Contains(entity);
}
}
/// <summary>
/// Find List<T>(predicate) in Object-cache.
/// </summary>
/// <param name="predicate">linq statement</param>
/// <returns></returns>
public IEnumerable<T> FindBy(Func<T, bool> predicate)
{
// Lets prevent race conditions, locking down cache.
lock (_cache)
{
return _cache.Where(predicate);
}
}
/// <summary>
/// Get all {T} from Object-cache.
/// </summary>
/// <returns></returns>
public IEnumerable<T> All()
{
// Lets prevent race conditions, locking down cache.
lock (_cache)
{
return _cache;
}
}
/// <summary>
/// Get Next Sequence for the given {T} Entity from Data Store.
/// </summary>
/// <returns>long</returns>
public long Next()
{
return RedisNext();
}
#region Redis Commands
//
// Following methods are ment as static private methods.
//
private long RedisNext()
{
using (var ctx = m.GetClient())
return ctx.As<T>().GetNextSequence();
}
private void RedisDelete(T entity)
{
using (var ctx = m.GetClient())
ctx.As<T>().Delete(entity);
}
private T RedisFind(long id)
{
using (var ctx = m.GetClient())
return ctx.As<T>().GetById(id);
}
private HashSet<T> RedisGetAll()
{
using (var ctx = m.GetClient())
return new HashSet<T>(ctx.As<T>().GetAll());
}
private void RedisStore(T entity)
{
using (var ctx = m.GetClient())
ctx.Store<T>(entity);
}
#endregion
}
}
Here is my new version, thanks to Greg Burghardt measures was taken to make the code testable, I also changed some return values from list to IEnumerable to gain more power with defered LINQ query whenever I want to apply more logic on-top of the already ran predicate.
I also did some research finding that HashSet has a "free" Contains(T) which is why I have also added a method for this, improvement searching for a object in a huge set, lets say 400k objects by making a temp item takes a few ns compared to several ms (searching for a keyed entity) which in the end is thousands percent performance increase. However it's then important that Equals and GetHashCode is overridden with proper key logic in entities themselves.
Adding a million objects now take seconds compared to minutes when cross referencing object relations.
For now this is my final version as it currently contains every need I have at this time.
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using ServiceStack.Redis;
namespace Datamodel
{
public interface IRepository
{
void LoadIntoCache<T>() where T : class;
Type EnsureTypeInCache(Type type);
T FindFirstBy<T>(Func<T, bool> predicate) where T : class;
T FindSingleBy<T>(Func<T, bool> predicate) where T : class;
bool AddOrUpdate<T>(T entity) where T : class;
void Remove<T>(T entity) where T : class;
IEnumerable<T> FindBy<T>(Func<T, bool> predicate) where T : class;
IEnumerable<T> All<T>() where T : class;
long Next<T>() where T : class;
bool Contains<T>(T Entity) where T : class;
}
public class Repository : IRepository
{
private static readonly PooledRedisClientManager m = new PooledRedisClientManager();
readonly static ConcurrentDictionary<Type, HashSet<object>> _cache = new ConcurrentDictionary<Type, HashSet<object>>();
private IRedisClientsManager RedisManager { get; set; }
private IDictionary<Type, HashSet<object>> Cache { get; set; }
public ObjectRepository()
{
RedisManager = m;
Cache = _cache;
}
public ObjectRepository(IRedisClientsManager redisManager, IDictionary<Type, HashSet<object>> cache)
{
RedisManager = redisManager;
Cache = cache;
}
/// <summary>
/// Load {T} into Object-cache from Data Store.
/// </summary>
/// <typeparam name="T">class</typeparam>
public 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>
public 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>());
//_cache[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 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 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>
/// Tries to update or Add entity to Object-cache and Data Store.
/// </summary>
/// <typeparam name="T">class</typeparam>
/// <param name="predicate">linq expression</param>
/// <param name="entity">entity</param>
public 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 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>
/// Check if {T} exists in Object-cache.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entity"></param>
/// <returns></returns>
public bool Contains<T>(T entity) where T : class
{
// Lets prevent race conditions, locking down cache.
lock (EnsureTypeInCache(typeof(T)))
{
return _cache[typeof(T)].Contains(entity);
}
}
/// <summary>
/// Find List<T>(predicate) in Object-cache.
/// </summary>
/// <typeparam name="T">class</typeparam>
/// <param name="predicate">linq statement</param>
/// <returns></returns>
public IEnumerable<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);
}
}
/// <summary>
/// Get all {T} from Object-cache.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public IEnumerable<T> All<T>() where T : class
{
// Lets prevent race conditions, locking down cache.
lock (EnsureTypeInCache(typeof(T)))
{
return _cache[typeof(T)].Cast<T>();
}
}
/// <summary>
/// Get Next Sequence for the given {T} Entity from Data Store.
/// </summary>
/// <typeparam name="T">class</typeparam>
/// <returns>long</returns>
public long Next<T>() where T : class
{
return RedisNext<T>();
}
#region Redis Commands
//
// Following methods are ment as static private methods.
//
private long RedisNext<T>() where T : class
{
using (var ctx = m.GetClient())
return ctx.As<T>().GetNextSequence();
}
private void RedisDelete<T>(T entity) where T : class
{
using (var ctx = m.GetClient())
ctx.As<T>().Delete(entity);
}
private T RedisFind<T>(long id) where T : class
{
using (var ctx = m.GetClient())
return ctx.As<T>().GetById(id);
}
private HashSet<T> RedisGetAll<T>() where T : class
{
using (var ctx = m.GetClient())
return new HashSet<T>(ctx.As<T>().GetAll());
}
private void RedisStore<T>(T entity) where T : class
{
using (var ctx = m.GetClient())
ctx.Store<T>(entity);
}
#endregion
}
}