İçeriğe geç

C# ile Cache Kurgulama

Projelerinizde ORM kullanıyorsanız arka planda cache mimarisi eklemeniz neredeyse zorunludur. Hatta ORM kullanmadığınız durumlarda bile cache kullanmak güzel sonuçlar almamızı sağlayabilir. Projelerin büyüklüğüne göre, gereksinimlere göre ve daha çok farklı parametreye göre farklı cache mimarileri hazırlamak mümkün. Senaryo gereği bir proje düşünelim ve özelliklerini aşağıdaki gibi sıralayalım.

  • Sadece veri girişinin ve veriye erişimin olduğu bir uygulama.(update-delete çok nadir olarak düşünelim)
  • Hali hazırda belli bir verinin bulunduğu ve veri girişinin çok nadir durumlarda gerçekleştiği bir uygulama.

Şimdi bu senaryoya göre en hızlı ve etkili olacak cache ihtiyacımızı karşılamaya çalışalım. Aşama aşama gideceğimiz için ilk bölümler sıkıcı gelebilir. İlk etapta aşağıdaki kod satırını hemen stackoverflow’dan(unuttuysak) bulabiliriz 🙂 .

HttpContext.Current.Cache.Insert(key, list); //list: saklayacağımız veri.

Uygulamamızdaki verileri cache için hazır hale getirdik. Peki eklemeden önce tahmin edersiniz ki ilk önce cache içinde veri varmı yoksa null durumunda mı kontrol etmemiz gerekecek. Aksi durumda sürekli cache insert gerçekleştirmiş olacağız.

var currentData = HttpContext.Current.Cache[key];

if (currentData == null)
{
HttpContext.Current.Cache.Insert(key, list);
}

Şimdi cache içinde ilgili key’e ait verinin kontrolünü yapıyoruz ve olmadığı durumda cache içine ekliyoruz. Peki yeterli mi? Tabiki hayır. Şimdi bu metodun bir IQueryable nesne ile kullanılacağını düşünelim ve tip olarak anonymous tip kullanalım. Yine üstteki mevcut kodların üzerine devam edeceğiz ve ekleyeceğimiz kısım eğer veriler cache içinde yoksa IQueryable nesnesini kullanarak veriye erişip cache içine atmak olacak. Aşağıdaki gibi inceleyebiliriz.

        public static List<T> GetCache<T>(this IQueryable<T> query, string key)
        {
            List<T> currentData = HttpContext.Current.Cache[key] as List<T>;
            if (currentData == null)
            {
                List<T> list = query.ToList();
                currentData = list;
                HttpContext.Current.Cache.Insert(key, list);
            }
            return currentData;
        }

Şimdi geldiğimiz noktada generic bir metodumuz var ve ilgili veriyi cache’de olmaması durumunda IQueryable nesnesinden sağlayarak cache’liyoruz. Bu noktada metot yeterli mi dersek yine cevabımız hayır olur. Bariz şekilde metot içindeki eksikliği görebiliriz. Eksiklik şudur ki ilgili key aracılığı ile cache durumunun null olup olmamasına göre işlem yapıyoruz ve biz IQueryable ile elde edebildiğimiz verilerin tamamının cache içinde olup olmadığından emin olamayız. İlgili T tipine karşılık gelen tabloya yeni bir kayıt eklendiğinde cache içinde olmayacak. Bu noktada bizim veritabanındaki T tipine karşılık gelen tablodaki son eklenmiş olan satırı cache içindeki son satır ile match etmemiz gerekli. Bunun için T tipine base olabilecek bir ModelBase nesnei düşünebiliriz. Bu class içinde “Id”, “CreateDate” gibi çeşitli özellikler tanımlamanız mümkündür.

        public static List<T> GetCache<T>(this IQueryable<T> query, string key) where T : ModelBase
        {
            List<T> currentData = HttpContext.Current.Cache[key] as List<T>;
            if (currentData == null ||
                !(currentData.OrderByDescending(k => k.Id).First().Equals(query.OrderByDescending(k => k.Id).First())))
            {
                List<T> list = query.ToList();
                currentData = list;
                HttpContext.Current.Cache.Insert(key, list);
            }
            return currentData;
        }

Şimdi geldiğimiz nokta itibari ile iyi bir konumdayız eskiye göre 🙂 . Peki yeterli mi bu şekilde dersek aslında cevabımız yine hayır olabilir. Çünkü atladığımız önemli bir nokta var. Elinizdeki T tipine göre cache içindeki verilere bakarsanız T tipinin ilişkili olduğu tablolar için data olmadığını göreceksiniz. Bunun sebebi basittir. IQueryable nesnesine sizin istediğiniz ilişkili olan tabloların da dolmasını istiyosanız bunları include etmeniz gerekecek. T tipindeki nesnenin ilişkisel tablolarını elde edeceğimiz için burada bu şekilde lamda ifadesi kullanabiliriz: params Expression>[] includes “Include” ifadesinin kullanımı için System.Data.Entity kütüphanesini dahil etmeyi unutmayın.

        public static List<T> GetCache<T>(this IQueryable<T> query, string key,
            params Expression<Func<T, object>>[] includes) where T : ModelBase
        {
            List<T> currentData = HttpContext.Current.Cache[key] as List<T>;
            if (currentData == null ||
                !(currentData.OrderByDescending(k => k.Id).First().Equals(query.OrderByDescending(k => k.Id).First())))
            {
                if (includes != null)
                    query = includes.Aggregate(query, (current, include) => current.Include(include));

                List<T> list = query.ToList();
                currentData = list;
                HttpContext.Current.Cache.Insert(key, list);
            }
            return currentData;
        }

Şimdi geldiğimiz noktada artık cache insert işlemine ilişkisel tabloları da ekledik. Bu noktada metodumuz yeterli mi dersek belki evet diyebiliriz ama geliştirmenin sınırı yok. Bu noktada çok kullanıcılı bir sistem olduğunu düşünürsek ve cache insert durumunda diğer kullanıcıların beklemesini lock bloğu ile sağlamak uygun olabilir.

    public static class IQueryableExtension
    {
        private static string lockObj = "lock";
        public static List<T> GetCache<T>(this IQueryable<T> query, string key,
            params Expression<Func<T, object>>[] includes) where T : ModelBase
        {
            List<T> currentData = HttpContext.Current.Cache[key] as List<T>;
            if (currentData == null ||
                !(currentData.OrderByDescending(k => k.Id).First().Equals(query.OrderByDescending(k => k.Id).First())))
            {
                lock (lockObj)
                {
                    if (includes != null)
                        query = includes.Aggregate(query, (current, include) => current.Include(include));

                    List<T> list = query.ToList();
                    currentData = list;
                    HttpContext.Current.Cache.Insert(key, list);
                }
            }
            return currentData;
        }
    }

En başta belirttiğim üzere yazılımda ihtiyaçları karşılamak için çok çeşitli çözümler olabilir. Bu noktada buradaki örneğimiz tüm projelere uygunluk göstermesi imkansızdır. Buradaki örneği sadece ihtiyaçlara göre en hızlı ve en etkili çözümü üretme anlamında incelersek daha sağlıklı olacaktır. Herkese mutlu kodlamalar dilerim.

Tarih:C#

İlk Yorumu Siz Yapın

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

This site uses Akismet to reduce spam. Learn how your comment data is processed.