EFCoreベースのデータCache実装

11689 ワード

.NetCore内蔵キャッシュはEFCore操作に組み込まれ、データの更新やクエリー時に自動的にキャッシュが更新されます.githubアドレス

2019-04-27論理コードの作成を初歩的に完了し、まだテストされていないため、多くの詳細が改善されなければならない。


2019-04-28簡単な機能テストと一部のエラーロジック修正。

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace FXY_NetCore_DbContext
{
    public class DefaultContext
    {
        /// 
        /// a queue to save the handle,if will be empted when call savechanges().
        /// 
        private ConcurrentQueue CacheQueue { get; set; }

        /// 
        /// databse context.
        /// 
        private DbContext Context { get; set; }

        /// 
        /// netocre inner cache.
        /// 
        private IMemoryCache Cache { get; set; }

        /// 
        /// the time of cache's life cycle
        /// 
        private int ExpirtTime { get; set; } = 10;

        /// 
        /// entity context.
        /// 
        /// database context
        /// cache
        /// expirt time,default 60 sencond.
        public DefaultContext(DbContext context, IMemoryCache cache, int expirtTime = 10)
        {
            CacheQueue = new ConcurrentQueue();
            Context = context;
            Cache = cache;
            ExpirtTime = expirtTime;
        }

        /// 
        /// add entity to database context and add the handle to the queue.
        /// it will be excuted when call Savechange().
        /// 
        /// 
        /// 
        public void Add(TEntity entity)
            where TEntity : class, new()
        {
            Context.Add(entity);
        }

        /// 
        /// add entity list to database context and add the handle to the queue.
        /// it will be excuted when call Savechange().
        /// 
        /// 
        /// 
        public void AddRange(params TEntity[] entities)
            where TEntity : class, new()
        {
            foreach (var item in entities)
                Add(item);
        }

        /// 
        /// remove entity to database context and add the handle to the queue.
        /// it will be excuted when call Savechange().
        /// 
        /// 
        /// 
        public void Remove(TEntity entity)
            where TEntity : class, new()
        {
            bool reesult = Enqueue(entity);
            if (reesult)
                Context.Remove(entity);
        }

        /// 
        /// remove entity list to database context and add the handle to the queue.
        /// it will be excuted when call Savechange().
        /// 
        /// 
        /// 
        public void RemoveRange(params TEntity[] entities)
            where TEntity : class, new()
        {
            foreach (var item in entities)
                Remove(item);
        }

        /// 
        /// update entity to database context and add the handle to the queue.
        /// it will be excuted when call Savechange().
        /// 
        /// 
        /// 
        public void Update(TEntity entity)
            where TEntity : class, new()
        {
            bool reesult = Enqueue(entity);
            if (reesult)
                Context.Update(entity);
        }

        /// 
        /// update entity list to database context and add the handle to the queue.
        /// it will be excuted when call Savechange().
        /// 
        /// 
        /// 
        public void UpdateRange(params TEntity[] entities)
            where TEntity : class, new()
        {
            foreach (var item in entities)
                Update(item);
        }

        /// 
        /// attach entity to database context add the handle to the queue.
        /// it will be excuted when call Savechange().
        /// 
        /// 
        /// 
        public void Attach(TEntity entity)
            where TEntity : class, new()
        {
            bool reesult = Enqueue(entity);
            if (reesult)
                Context.Attach(entity);
        }

        /// 
        /// attach entity list to database context add the handle to the queue.
        /// it will be excuted when call Savechange().
        /// 
        /// 
        /// 
        public void AttachRange(params TEntity[] entities)
            where TEntity : class, new()
        {
            foreach (var item in entities)
                Attach(item);
        }


        /// 
        /// update cache and database.
        /// update cache at first,if update cache is failed,return false,else commit the changes to database.
        /// 
        /// 
        public bool SaveChanges()
        {
            bool result = Dequeue();
            if (result)
                result = Context.SaveChanges() > 0;
            return result;
        }

        /// 
        /// single query.
        /// find it in the cache first,return if find it,otherwise search it in database by efcore.
        /// 
        /// 
        /// 
        /// 
        public TEntity Get(string key)
            where TEntity : class, new()
        {
            var result = GetCache(key);
            if (result == null)
            {
                result = Context.Find(key);
                var cacheEntity = GetCacheEntity(result);
                AddCache(cacheEntity);
            }
            else
            {
                var cacheEntity = GetCacheEntity(result);
                UpdateCache(cacheEntity);
            }
            return result;
        }

        /// 
        /// collection query.
        /// do not allow fuzzy query
        /// 
        /// 
        /// 
        /// 
        public List Get(string[] keys)
        where TEntity : class, new()
        {
            var result = new List();
            foreach (var item in keys)
                result.Add(Get(item));
            return result;
        }

        #region private

        #region cache queue

        /// 
        /// add the handle to the context queue.
        /// 
        /// 
        /// 
        private bool Enqueue(object model)
        {
            CacheEntity entity = GetCacheEntity(model);
            if (CacheQueue.TryPeek(out CacheEntity cacheEntity1))
                return false;
            else
            {
                CacheQueue.Enqueue(entity);
                return CacheQueue.TryPeek(out CacheEntity cacheEntity2);
            }
        }

        /// 
        /// update the changes to cache,and remove it from the cache queue.
        /// include add,delete and update.
        /// 
        /// 
        private bool Dequeue()
        {
            bool check = false;
            bool dequeue = CacheQueue.TryDequeue(out CacheEntity cacheEntity);
            if (dequeue)
                check = RemoveCache(cacheEntity);
            else
                check = false;
            return check;
        }

        #endregion

        #region cache core

        /// 
        /// add cache
        /// 
        /// 
        /// 
        private bool AddCache(CacheEntity cacheEntity)
        {
            bool check;
            Cache.Set(cacheEntity.key, cacheEntity.Value, new TimeSpan(0, 0, ExpirtTime));
            check = Cache.Get(cacheEntity.key) != null;
            return check;
        }

        /// 
        /// remove cache.
        /// 
        /// 
        /// 
        private bool RemoveCache(CacheEntity cacheEntity)
        {
            bool check;
            Cache.Remove(cacheEntity.key);
            check = Cache.Get(cacheEntity.key) == null;
            return check;
        }

        /// 
        /// update cache.
        /// 
        /// 
        /// 
        private bool UpdateCache(CacheEntity cacheEntity)
        {
            bool check = RemoveCache(cacheEntity);
            if (check)
                check = AddCache(cacheEntity);
            return check;
        }

        /// 
        /// get cache by key.
        /// 
        /// 
        /// 
        private TEntity GetCache(string key)
            where TEntity : class, new()
        {
            Cache.TryGetValue(key, out object value);
            return value as TEntity;
        }

        #endregion

        #region other

        /// 
        /// get private cache entity.
        /// 
        /// 
        /// 
        /// 
        private CacheEntity GetCacheEntity(object model)
        {
            var key = GetModelKey(model);
            var entity = new CacheEntity()
            {
                Value = model,
                key = key
            };
            return entity;
        }

        /// 
        /// get the key of a entity.
        /// 
        /// 
        /// 
        private string GetModelKey(object model)
        {
            string key = "";
            var type = model.GetType().GetProperties();
            foreach (var item in type)
            {
                if (item.GetCustomAttributes(typeof(KeyAttribute), true).Length > 0)
                {
                    key = item.GetValue(model).ToString();
                    break;
                }
            }
            return key;
        }

        #endregion
        #endregion
    }

    /// 
    /// a entity to handle cache
    /// 
    public sealed class CacheEntity
    {
        /// 
        /// cache key
        /// 
        [Key]
        public string key { get; set; }

        /// 
        /// cache value
        /// 
        public object Value { get; set; }
    }
}