Caching in .NET
In every application you have some data that is more read-more, write-once or twice. For example you can have the list of Cities of a country, the list of Countries of the world or list of exchange currency. This data is modified rarely. Also, you can have data that is not very sensitive to be real-time , such as the list of invoices for the day.
In .NET 3.5 you have several options
1. ASP.NET caching – and implementing in other applications with HttpRuntime ( even if MS says “The Cache class is not intended for use outside of ASP.NET applications”)
2. Enterprise caching block : hard to configure
3. memcached , velocity, sharedcache and other third party providers – that comes with their options and configuration
In .NET 4.0 you have a new kid : objectcache and , more important , an implementation : MemoryCache
http://msdn.microsoft.com/en-us/library/system.runtime.caching.memorycache%28v=VS.100%29.aspx
What is very good is that now you can cache in Memory what do you want – and apply easily to your Business Layer. More, the object is a singleton for the application – that is even better (see the test on the final of the post)
What it is missing is an easy implementation for List and an implementation to remove data after a defined time.
So I decided to do my implementation for that (ok, it is wrong to have both implementations in a single class – but you can separate easily )
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.Caching; namespace CachingData{ /// <summary> /// List<int> i = new List<int>() { 1, 10, 100 }; //CacheData_List<List<int>, int> data = new CacheData_List<List<int>, int>(2); //data.Add(i); //Assert.AreEqual(3, data.Items().Count, "must be 3"); //data = new CacheData_List<List<int>, int>(); //Assert.AreEqual(3, data.Items().Count, "must be 3"); //Assert.IsTrue(data.ContainsData, "must have data"); //Thread.Sleep(1000 * 3); //Assert.IsFalse(data.ContainsData, "must not have data"); /// </summary> /// <typeparam name="T">and generic ILIST </typeparam> /// <typeparam name="U"></typeparam> public class CacheData_List<T, U> where T: class ,IList<U>, new () { /// <summary> /// use this for adding in cache /// </summary> public event EventHandler RemovedCacheItem; private System.Timers.Timer timerEvent; private MemoryCache buffer; private int TimeToLiveSeconds; private DateTimeOffset dto; private string Key; /// <summary> /// default constructor - cache 600 seconds = 10 minutes /// </summary> public CacheData_List() : this (600) { } /// <summary> /// constructor cache the mentioned TimeSeconds time /// </summary> /// <param name="TimeSeconds">value of time for cache</param> public CacheData_List( int TimeSeconds) { TimeToLiveSeconds = TimeSeconds; timerEvent= new System.Timers.Timer(TimeToLiveSeconds * 1000); timerEvent.AutoReset = true ; timerEvent.Elapsed += new System.Timers.ElapsedEventHandler(timerEvent_Elapsed); dto = new DateTimeOffset(DateTime.UtcNow.AddSeconds(TimeToLiveSeconds)); Key = typeof (T).ToString(); buffer = MemoryCache.Default; } void timerEvent_Elapsed( object sender, System.Timers.ElapsedEventArgs e) { if (RemovedCacheItem != null ) { RemovedCacheItem( this , EventArgs.Empty); } } /// <summary> /// remove item from cache /// </summary> public void Remove() { if (buffer.Contains(Key)) { buffer.Remove(Key); } dto= new DateTimeOffset(DateTime.UtcNow.AddSeconds(TimeToLiveSeconds)); } /// <summary> /// add multiple items to cache /// </summary> /// <param name="items">items to add to the cache</param> public void Add(T items) { if (buffer.Contains(Key)) { T data = Items(); foreach ( var t in data) { items.Add(t); } buffer.Remove(Key); } buffer.Add(Key, items, dto); } /// <summary> /// add a item to the IList of the cache /// </summary> /// <param name="item">an item to add</param> public void AddItem(U item) { T data= new T(); if (buffer.Contains(Key)) { data = buffer.Get(Key) as T; buffer.Remove(Key); } data.Add(item); buffer.Add(Key, data,dto); } /// <summary> /// usefull if you do not intercept the removed event /// </summary> public bool ContainsData { get { return buffer.Contains(Key); } } /// <summary> /// retrieve items /// </summary> /// <returns></returns> public T Items() { if (!buffer.Contains(Key)) return null ; return buffer.Get(Key) as T; } } } |
Please note that the test for usage is in the summary :
01 02 03 04 05 06 07 08 09 10 | </pre> List i = new List() { 1, 10, 100 }; CacheData_List <List< int >, int > data = new CacheData_List<List< int >, int >(2); data.Add(i); Assert.AreEqual(3, data.Items().Count, "must be 3" ); data = new CacheData_List<List< int >, int >(); Assert.AreEqual(3, data.Items().Count, "must be 3" ); Assert.IsTrue(data.ContainsData, "must have data" ); Thread.Sleep(1000 * 3); Assert.IsFalse(data.ContainsData, "must not have data" ); |
You can download the file from
http://msprogrammer.serviciipeweb.ro/wp-content/uploads/2010/05/CachingData.zip
(Last Note : for synchonization maybe it was better to lock on readerwriterlockslim
great post as usual!
Great job!
That’s will really rocks inluding regions!!
Nu vad de ce e necesara neaparat o lista. De obicei in cazul unui cache ai o cheie si vrei sa obtii obiectul daca a fost cacheuit.
Independent de asta, mi se pare ca reinventezi roata:
http://msdn.microsoft.com/en-us/library/system.runtime.caching.cacheitempolicy.absoluteexpiration%28v=VS.100%29.aspx
http://msdn.microsoft.com/en-us/library/dd780569%28v=VS.100%29.aspx
Cand adaugi un item in Cache poti sa ii setezi prin policy timeout-ul pe care tu il faci manual.
Mai mult, poti avea timeout-uri diferite per itemuri & poti avea si un sliding timeout (e pastrat in cache cu conditia sa fie accesat intr-o perioada de timp)
Se pare ca intr-adevar re-inventez roata un pic …dar pentru liste … O sa ma gindesc si o sa refac codul…
Nice post but I always cringe when I see seconds passed as ints. That’s what TimeSpan is there for!