tisdag 6 november 2012

Cache av resultat från klassmetoder

I många klassmetoder så gör man tunga databasfrågor eller andra prestandakrävande operationer som ofta resulterar i samma resultat eftersom inget har ändrats i datat sedan sist denna metod kördes.
Då kan det vara en bra idé att resultatet läggs i cachminnet under en tidsperiod som man tycker är rimligt.

Låt säga att att en metod efterfrågas varje gång första sidan av din webbplats anropas, och denna metod kräver en eller flera databasanrop för att kunna presentera resultatet på sidan. Men eftersom datat för sidan uppdateras i regel som oftast kanske bara en gång var 5:e minut, eller ännu mer sällan så räcker det med att hämta datat en gång från  databasen och sedan låta det datat vara ok att presentera (från cacheminnet) på första sidan i, låt säga en minut, oavsett om datat har uppdaterats i databasen.

I följande fåniga exempel visar jag hur man kan cache'a resultatet av en klass olika metoder med olika unika strängnycklar som identifierar resultatet beroende på klass-, metodnamn och metodens olika inparametrar.

Först har jag en hjälpklass med metoden GetCacheKey() som kan användas för alla olika klasser:


 namespace CacheTestProject.Helpers  
 {  
  public class Helper  
  {  
   public static string GetCacheKey(string className, string methodName, params string[] parameters)  
   {  
    return className + "-" + methodName + "-" + string.Join("-", parameters);  
   }  
  }  
 }  

På sidan har jag sedan placerat en Literal kallas litOut.
Jag har också skapat en liten Car-klass som har en metod GetCarStamp() som ger tillbaka en specifik sträng för objektet.
Vi ponerar att denna metod kräver prestandakrävande databasanrop så därför är värdet lagrat i cacheminnet i 1minut innan man återigen måste läsa upp det från databasen.


 public class Car  
 {  
   private const string ClassCacheKey = "Car";  
   public string RegNumber { get; set; }  
   public string Color { get; set; }  
   public Car(string regnr, string color)  
   {  
     RegNumber = regnr;  
     Color = color;  
   }  
   // EXAMPLE METHOD 1 without cachedependency!  
   public string GetCarStamp(bool withRegNumber, bool withColor)  
   {  
     string cacheKey = Helper.GetCacheKey(  
             ClassCacheKey,  
             "GetCarStamp",  
             RegNumber);  
     string data = (string)HttpRuntime.Cache.Get(cacheKey);  
     if (!string.IsNullOrEmpty(data))  
     {  
       return data;  
     }  
     //Call for the performance demanding method as nothing is in the cache!  
     data = GetCarStampFromDatabase();  
     HttpRuntime.Cache.Insert(  
         cacheKey,  
         data,  
         null,  
         DateTime.Now.AddSeconds(60),  
         Cache.NoSlidingExpiration);  
     return data;  
   }  
   //Simulate a performance demanding method that we want to  
   //avoid calling as much as possible  
   private string GetCarStampFromDatabase()  
   {  
     var regNumber = (withRegNumber) ? RegNumber : string.Empty;  
     var color = (withColor) ? Color : string.Empty;  
     return string.Format("{0}{1}", RegNumber, color);  
   }  
 }  


Page_Load metoden ser ut som följer:
 protected void Page_Load(object sender, EventArgs e)  
 {  
   Car MyCar1 = new Car("ABC123", "BLUE");  
   litOut.Text = MyCar1.GetCarStamp();  
 }  


Man skulle då få tillbaka strängen "ABC123BLUE" i en minut tills cachen släpper, även om färgen eller registreringsnumret eventuellt har ändrats i databasen under denna tid.

På detta vis kan man göra likadant för alla andra prestandakrävande metoder som klassen eventuellt innehåller och spara dess resultat i cachen med klassnamn (ClassCacheKey) och metodnamn som unik identifierare.

Med Dependency

Om man istället vill att resultatet ska ligga kvar i cachen så länge som inte någonting ändras, alltså att resultatet ska invalideras vid någon form av uppdatering på objektet, kan man justera koden till något i stil med nedanstående.
I min Helper-fil har jag lagt till följande statiska metoder:


 public static string GetCacheDependencyKey(string className, string SomeUniqueId)  
 {  
   return className + "-CacheDepKey-" + SomeUniqueId;  
 }  
 public static CacheDependency GetCacheDependency(string className, string SomeUniqueId)  
 {  
   //Get unique identification key for the dependency  
   string DependencyKey = GetCacheDependencyKey(className, SomeUniqueId);  
   //If it is not already in the cache, add it!  
   if (HttpRuntime.Cache.Get(DependencyKey) == null)  
   {  
     HttpRuntime.Cache.Insert( //HttpRuntime.Cache.Insert will replace object in cache, which HttpRuntime.Cache.Add does not!  
       DependencyKey,  
       string.Empty,  
       null,  
       Cache.NoAbsoluteExpiration,  
       Cache.NoSlidingExpiration);  
   }  
   //Then create a dependency that tells to depend on the cacheitem with key "DependencyKey"  
   return new CacheDependency(  
     null,  
     new[] { DependencyKey }  
   );  
 }  


Och min Car-klass ser nu ut såhär:

 public class Car  
 {  
   private const string ClassCacheKey = "Car";  
   public string RegNumber { get; set; }  
   public string Color { get; set; }  
   public Car(string regnr, string color)  
   {  
     RegNumber = regnr;  
     Color = color;  
   }  
   public void UpdateCarColor(string newColor)  
   {  
     Color = newColor;  
     HttpRuntime.Cache.Remove(  
      Helper.GetCacheDependencyKey(  
       ClassCacheKey, RegNumber  
       )  
     );  
   }  
   // EXAMPLE METHOD 1 without cachedependency!  
   public string GetCarStamp()  
   {  
     string cacheKey = Helper.GetCacheKey(  
             ClassCacheKey,  
             "GetCarStamp",  
             RegNumber);  
     string data = (string)HttpRuntime.Cache.Get(cacheKey);  
     if (!string.IsNullOrEmpty(data))  
     {  
       return data;  
     }  
     //Call for the performance demanding method...  
     data = GetCarStampFromDatabase();  
     HttpRuntime.Cache.Insert(  
         cacheKey,  
         data,  
         Helper.GetCacheDependency(ClassCacheKey, RegNumber),  
         Cache.NoAbsoluteExpiration,  
         Cache.NoSlidingExpiration);  
     return data;  
   }  
   //Simulate a performance demanding method that we want to  
   //avoid calling as much as possible  
   private string GetCarStampFromDatabase()  
   {  
     return string.Format("{0}{1}", RegNumber, Color);  
   }  
 }  

Så cachen för GetCarStamp() släpper inte förrän färgen ändras via UpdateCarColor(), eller att applikationen startas om.
Vill man att den ändå ska släppa efter en viss tid så är det bara att byta ut Cache.NoAbsoluteExpiration till det värde man önskar.

Inga kommentarer:

Skicka en kommentar