Skip to main content
replaced http://codereview.stackexchange.com/ with https://codereview.stackexchange.com/
Source Link

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.

New updated version with Generic Class Constructor.
Source Link
Joachim
  • 205
  • 3
  • 11

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
    }
}
Source Link
Joachim
  • 205
  • 3
  • 11

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