利用訂閱模式實現緩存更新


1. 引言

很多Web項目,都需要和數據庫打交道,典型的就是CRUD(讀,寫,更新,刪除)操作。無論是哪種數據庫,Asp.Net MVC 作為后端框架的項目,都有很多操作數據庫的類庫。最近在一個Web項目中就用到了EntityFramework來存取Sql Server。相信很多人都懂得,如何利用EntityFramework存取數據,所以這方面不做詳細的介紹。 今天給大家介紹一種如何利用訂閱模式來實現緩存更新。

實現過程主要參照NopCommerce,它是一個開源的電商平台,里面有不少精妙的設計,值得每一個.Net程序員一看。

 

2. 實現

先來看看未采用Cache的設計,定義一個Service,這個Service主要是利用EntityFramework存取數據。

   //  定義簡單的數據模型
    public class TargetSegment
    {
        public int Id { get; set; }
        public string Segment { get; set; }
    }

Service接口和默認實現

    public interface ITargetSegmentService
    {
        TargetSegment GetById(object id);

        void Insert(TargetSegment item);

        void Update(TargetSegment item);

        void Delete(TargetSegment item);

        IList<TargetSegment> GetAll();
    }
    public class TargetSegmentService : ITargetSegmentService
    {
        private IRepository<TargetSegment> _repository;
        public TargetSegmentService(IRepository<TargetSegment> repository)
        {
            this._repository = repository;
        }

        public TargetSegment GetById(object id)
        {
            return this._repository.GetById(id);
        }

        public void Insert(TargetSegment item)
        {
            if (item == null)
                throw new ArgumentNullException("item");

            this._repository.Insert(item);
        }

        public void Update(TargetSegment item)
        {
            if (item == null)
                throw new ArgumentNullException("item");

            this._repository.Update(item);
        }

        public void Delete(TargetSegment item)
        {
            if (item == null)
                throw new ArgumentNullException("item");

            this._repository.Delete(item);
        }

        public IList<TargetSegment> GetAll()
        {
            return this._repository.Table.ToList();
        }
    }

這個實現能夠滿足基本的需求,但是缺乏優化使得每次當Service調用GetAll 函數的時候,都會從數據庫讀取所有該類條目。如果沒有Update,Create或者Delete每次GetAll返回的數據都是一樣的。

因此可以在GetAll這里添加緩存,在Update,Create,Delete更新緩存。

首先我們利用.Net自帶的System.Runtime.Caching.ObjectCache 類 定義緩存接口以及簡單實現:

    public interface ICache : IDisposable
    {
        T Get<T>(string key);
        void Set(string key, object data, int cacheMinutes);
        bool IsSet(string key);
        void Remove(string key);
        void RemoveByPattern(string pattern);
        void Clear();
    }

    public static class CacheExtension
    {
        public static T GetOrAdd<T>(this ICache cache, string key, int cacheMinutes, Func<T> factory)
        {
            if (cache.IsSet(key))
            {
                return cache.Get<T>(key);
            }
            else
            {
                var data = factory();
                cache.Set(key, data, cacheMinutes);
                return data;
            }
        }

        public static void RemoveByPattern(this ICache cache, string pattern, IEnumerable<string> keys)
        {
            Regex regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
            foreach (var target in keys.Where(k=>regex.IsMatch(k)))
            {
                cache.Remove(target);
            }
        }
    }
    public partial class MemoryCache : ICache
    {
        protected System.Runtime.Caching.ObjectCache Cache
        {
            get { return System.Runtime.Caching.MemoryCache.Default; }
        }

        public MemoryCache()
        {
        }

        public T Get<T>(string key)
        {
            return (T) this.Cache[key];
        }

        public void Set(string key, object data, int cacheMinutes)
        {
            if (data == null)
                return;

            this.Cache.Add(new CacheItem(key, data),
                new CacheItemPolicy() {AbsoluteExpiration = DateTime.Now + TimeSpan.FromMinutes(cacheMinutes)});
        }

        public bool IsSet(string key)
        {
            return this.Cache.Contains(key);
        }

        public void Remove(string key)
        {
            this.Cache.Remove(key);
        }

        public void RemoveByPattern(string pattern)
        {
            this.RemoveByPattern(pattern, Cache.Select(item => item.Key));
        }

        public void Clear()
        {
            foreach (var item  in Cache)
            {
                this.Remove(item.Key);
            }
        }

        public void Dispose()
        {
            this.Clear();
        }
    }

到此為止,已經可以實現緩存跟新了,只要在Insert,Update,Delete 函數里面添加 緩存更新即可。我們進一步利用訂閱模式。

訂閱模式 一般有三個對象,一個是Subject代表發送給所有訂閱者的主題信息。一個是Consumer代表訂閱者接受訂閱的Subject, 另一個是Publisher代表發送Subject的實現。

這里我們定義幾個Subject

   // 代表數據刪除的Subject
   public class EntityDeleted<T> where T: class 
    {
        public T Entity { get; private set; }

        public EntityDeleted(T entity)
        {
            this.Entity = entity;
        }
    }
    public class EntityInserted<T> where T : class
    {
        public T Entity { get; private set; }

        public EntityInserted(T entity)
        {
            this.Entity = entity;
        }
    }
    public class EntityUpdated<T> where T : class 
    {
        public T Entity { get; private set; }

        public EntityUpdated(T entity)
        {
            this.Entity = entity;
        }
    }

定義Consumer接口和Cache相關的Consumer實現

    public interface IConsumer<T>
    {
        void HandleEvent(T eventMessage);
    }
    public class CacheEventConsumer : 
        IConsumer<EntityInserted<TargetSegment>>,
        IConsumer<EntityUpdated<TargetSegment>>, 
        IConsumer<EntityDeleted<TargetSegment>>

    {
        private readonly ICache _cache;
        public CacheEventConsumer()
        {
            this._cache = DependencyResolver.Current.GetService(typeof(ICache)) as ICache;
        }

        public const string AllTargetSegmentPattern = "TargetSegment.All";

        public void HandleEvent(EntityInserted<TargetSegment> eventMessage)
        {
            this._cache.RemoveByPattern(AllTargetSegmentPattern);
        }

        public void HandleEvent(EntityUpdated<TargetSegment> eventMessage)
        {
            this._cache.RemoveByPattern(AllTargetSegmentPattern);
        }

        public void HandleEvent(EntityDeleted<TargetSegment> eventMessage)
        {
            this._cache.RemoveByPattern(AllTargetSegmentPattern);
        }
    }

定義publish接口和實現

    public interface IEventPublisher
    {
        void Publish<T>(T eventMessage);
    }

    public static class EventPublisherExtension
    {
        public static void EntityInserted<T>(this IEventPublisher eventPublisher, T entity) where T : class
        {
            eventPublisher.Publish(new EntityInserted<T>(entity));
        }

        public static void EntityUpdated<T>(this IEventPublisher eventPublisher, T entity) where T : class
        {
            eventPublisher.Publish(new EntityUpdated<T>(entity));
        }

        public static void EntityDeleted<T>(this IEventPublisher eventPublisher, T entity) where T : class
        {
            eventPublisher.Publish(new EntityDeleted<T>(entity));
        }
    }
    public class EventPublisher : IEventPublisher
    {
        public EventPublisher()
        {
        }

        public void Publish<T>(T eventMessage)
        {
            var consumers = DependencyResolver.Current.GetServices<IConsumer<T>>();
            foreach (var consumer in consumers)
            {
                this.PublishToConsumer(consumer, eventMessage);
            }
        }

        protected virtual void PublishToConsumer<T>(IConsumer<T> consumer, T eventMessage)
        {
            try
            {
                consumer.HandleEvent(eventMessage);
            }
            catch (Exception exception)
            {
                   throw;
            }
        }
    }

最終的Service多了ICache 和 IEventPublish 兩個對象:

    public class TargetSegmentService : ITargetSegmentService
    {
        private IRepository<TargetSegment> _repository;
        private ICache _cache;
        private IEventPublisher _eventPublisher;

        public TargetSegmentService(IRepository<TargetSegment> repository, ICache cache, IEventPublisher eventPublisher)
        {
            this._repository = repository;
            this._cache = cache;
            this._eventPublisher = eventPublisher;
        }

        public TargetSegment GetById(object id)
        {
            return this._repository.GetById(id);
        }

        public void Insert(TargetSegment item)
        {
            if (item == null)
                throw new ArgumentNullException("item");

            this._repository.Insert(item);
            this._eventPublisher.EntityInserted(item);
        }

        public void Update(TargetSegment item)
        {
            if (item == null)
                throw new ArgumentNullException("item");

            this._repository.Update(item);
            this._eventPublisher.EntityUpdated(item);
        }

        public void Delete(TargetSegment item)
        {
            if (item == null)
                throw new ArgumentNullException("item");

            this._repository.Delete(item);
            this._eventPublisher.EntityDeleted(item);
        }

        public IList<TargetSegment> GetAll()
        {
            return this._cache.GetOrAdd(SnappsCacheEventConsumer.AllTargetSegmentPattern, 60, () =>
            {
                return this._repository.Table.ToList();
            });
        }
    }

最后通過Unity實現依賴注入

            Container.RegisterInstance<ICache>(new MemoryCache(), new ContainerControlledLifetimeManager());
            Container.RegisterInstance<IEventPublisher>(new EventPublisher(), new ContainerControlledLifetimeManager());
            Container.RegisterType(typeof(IConsumer<>), typeof(CacheEventConsumer), new ContainerControlledLifetimeManager());

 

3. 總結

這是訂閱模式的一種運用,在NopCommerce里面有很多設計模式都運用的非常巧妙,對於EntityFramework的優化遠不止這些,以后再給大家分享。

 

歡迎訪問我的個人主頁 51zhang.net  網站還在不斷開發中…..


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM