• 订阅模式实现缓存更新


    订阅模式实现缓存更新

    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  网站还在不断开发中…..

     
    分类: .NetAsp.Net MVC
  • 相关阅读:
    推断两条单链表是否相交
    字典树的实现
    ActivityGroup window bad token问题深入分析
    &quot;《 Serial Drivers 》by Alessandro Rubini&quot; 学习笔记
    rac_安装软件时报版本号过高问题
    Mac OS 环境下 安装 Asp.Net及使用Yeoman 创建Asp.Net 项目
    iOS:一个Cell中设置另外一个Cell中的button
    Java虚拟机定义
    一道超级坑爹的水题(ACdream oj 无耻的出题人)
    Cocos2d-x 3.x版2048游戏开发
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/5610693.html
Copyright © 2020-2023  润新知