Wednesday, January 30, 2019

Attribute caching in C#

Mentioning cache policy using an attribute at each model classes make it a flexible and easy to use approach.

In entity framework this can be implemented using GenericRepository and custom attributes.

So first, lets create an enum to represent the cache policies to be used in our application:


public enum WA1CachePoliciesEnum
    {
        None,
        SlidingExpiration10Minutes,
        AbsoluteExpiration1Hour,
        AbsoluteExpiration1Day,
        NeverExpire
    }

    Now we need to define the attribute



[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class CachePolicyAttribute : Attribute
    {
        public WA1CachePoliciesEnum policy;
        public CachePolicyAttribute(WA1CachePoliciesEnum policy)
        {
            this.policy = policy;
        }
    }
 The atrribute class merely saves CachedItemPolicy object passed to it in a public property. This attribute can be added to any class, but we intend to use it for our model classes only. Lets use the attribute in a model.

[CachePolicy(WA1CachePoliciesEnum.AbsoluteExpiration1Hour)]
    public class Students
    {
        [Key]
        public int StudentId { get; set; }

        public string Name { get; set; }

        [ForeignKey("Class")]
        [Column("ClassId")]
        public int ClassId { get; set; }
        public virtual Class Class { get; set; }


    }


Now we need to modify the Generic repository class to use the attribute

public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
    {
        internal CustomContext context;
        internal DbSet<TEntity> dbSet;
        internal DbContext dbcontext;
        protected ObjectCache cache = MemoryCache.Default;
        public GenericRepository(CustomContext context)
        {
            this.context = context;
            this.dbSet = context.Set<TEntity>();
        }

        public GenericRepository(DbContext dbcontext)
        {
            this.dbcontext = dbcontext;
            this.dbSet = context.Set<TEntity>();
        }

        public virtual IEnumerable<TEntity> Get()
        {
            Type entityType = typeof(TEntity);
            object[] policyAttributes = entityType.GetCustomAttributes(typeof(CustomCachePolicyAttribute), false);
            var result = new List<TEntity>();
            if (policyAttributes != null && policyAttributes.Length > 0)
            {
                var policy = CustomCacheHelper.GetPolicy(((CustomCachePolicyAttribute)policyAttributes[0]).policy);
                result = (List<TEntity>)cache.Get(entityType.Name);
                if (result == null)
                {
                    result = GetQuery().ToList();
                    cache.Add(new CacheItem(entityType.Name, result), policy);
                }
            }
            else
            {
                result = GetQuery().ToList();
            }

            return result;
        }

       

        public virtual IQueryable<TEntity> GetQuery()
        {
            IQueryable<TEntity> query = dbSet;
            return query;
        }

        public virtual IQueryable<TEntity> GetByIdAndIncludeRelated(object id, List<string> relatedEntities)
        {
            IQueryable<TEntity> query = dbSet.AsQueryable();
            if (relatedEntities != null)
            {
                query = relatedEntities.Aggregate(query,
                          (current, include) => current.Include(include));
            }
            // TEntity entity = dbSet.Find(id);
            // var entityKey = GetEntityKey(context, entity)
            // object res = entityKey.EntityKeyValues.FirstOrDefault().Value;
            //string key = entityKey.EntityKeyValues.FirstOrDefault().Key;
            return query;
        }

        public virtual IQueryable<TEntity> GetAndIncludeRelated(List<string> relatedEntities)
        {
            IQueryable<TEntity> query = dbSet.AsQueryable();
            if (relatedEntities != null)
            {
                query = relatedEntities.Aggregate(query,
                          (current, include) => current.Include(include));
            }
            return query;
        }

        public virtual EntityKey GetEntityKey<T>(DbContext context, T entity)
    where T : class
        {
            var oc = ((IObjectContextAdapter)context).ObjectContext;
            ObjectStateEntry ose;
            if (null != entity && oc.ObjectStateManager
                                    .TryGetObjectStateEntry(entity, out ose))
            {
                return ose.EntityKey;
            }
            return null;
        }

        public virtual EntityKey GetEntityKey<T>(DbContext context
                                               , DbEntityEntry<T> dbEntityEntry)
            where T : class
        {
            if (dbEntityEntry != null)
            {
                return GetEntityKey(context, dbEntityEntry.Entity);
            }
            return null;
        }

        public virtual TEntity GetByID(object id)
        {
            return dbSet.Find(id);
        }

        public virtual void Insert(TEntity entity)
        {
            dbSet.Add(entity);
        }

        public virtual void Delete(object id)
        {
            TEntity entityToDelete = dbSet.Find(id);
            Delete(entityToDelete);
        }

        public virtual void Delete(TEntity entityToDelete)
        {
            if (context.Entry(entityToDelete).State == EntityState.Detached)
            {
                dbSet.Attach(entityToDelete);
            }
            dbSet.Remove(entityToDelete);
        }

        public virtual void Update(TEntity entityToUpdate)
        {
            dbSet.Attach(entityToUpdate);
            context.Entry(entityToUpdate).State = EntityState.Modified;
        }

    }
In the code above, I have highlighted the Get() method which uses our custom cache attribute values. Every time we call get method on an entity, it checks if cache is enabled and if the entity is present in the cache before fetching data from database.


public class CustomCacheHelper
    {
        public static CacheItemPolicy GetPolicy(CustomCachePoliciesEnum cmCachePolicy)
        {
            var result = new CacheItemPolicy { AbsoluteExpiration = DateTime.MinValue };
            switch (cmCachePolicy)
            {
                case CustomCachePoliciesEnum.None:
                    break;
                case CustomCachePoliciesEnum.SlidingExpiration10Minutes:
                    result = new CacheItemPolicy { SlidingExpiration = new TimeSpan(0, 10, 0) };
                    break;
                case CustomCachePoliciesEnum.AbsoluteExpiration1Hour:
                    result = new CacheItemPolicy { AbsoluteExpiration = DateTime.Now.AddHours(1) };
                    break;
                case CustomCachePoliciesEnum.AbsoluteExpiration1Day:
                    result = new CacheItemPolicy { AbsoluteExpiration = DateTime.Now.AddDays(1) };
                    break;
                case CustomCachePoliciesEnum.NeverExpire:
                    result = new CacheItemPolicy { AbsoluteExpiration = DateTime.MaxValue };
                    break;
                default:
                    break;
            }
            return result;
        }
    }



This helper class creates cache item policies based on settings passed to it. 

No comments:

c# httpclient The remote certificate is invalid according to the validation procedure: RemoteCertificateNameMismatch

 If we get this error while trying to get http reponse using HttpClient object, it could mean that certificate validation fails for the remo...