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 網站還在不斷開發中…..
