16 Şubat 2020 Pazar

IQueryable - IEnumerable

     S.A. Arkadaşlar,
     Bugün birbirine benzeyen yapılarından IQueryable ve IEnumerable kavramlarını incelemeye çalışacağız. İki yapı aynı gibi dursa da birbirinden farkları var. Belki de en bariz olan farkı Burak Selim Hoca burada nokta atışı ile anlatmış, fakat ben genel olarak konuyu ele alıp bulduğum farkları derlemeye çalışacağım.
      IQueryable ile IEnumerable arasında en önemli fark, IQueryable sorgu düzenlenip sunucu tarafında çağrılırken, IEnumerable ise çekilen verinin tamamını belleğe alıp oradan istenildiği şekilde düzenlenebilir. Aşağıda aynı gibi duran 2 örnek mevcut:
IQueryable queryable = _dbContext.Set<User>().Skip(10).Take(10);
IEnumerable enumerable = _dbContext.Set<User>().Skip(10).Take(10);

     İlk bakışta aynı duran yukarıdaki 2 kod satırı aynı zamanda aynı sonucu dönüyorlar fakat arka planda çalışma prensipleri farklı. Şöyle ki, IQueryable olan 1.satır direkt olarak sunucuda çalışacağı için belirtilen tablodan ilk 10 değeri atlayıp ikinci 10 satırı çeker. IEnumerable ise tüm satırları çeker ve hepsini bellekte tutar daha sonra ilk 10 kaydı atlayıp ikinci 10 kaydı gösterir. Aslında IEnumerable'da tüm tabloyu tutuyor ve hala diğer değerlerle ilgili işlem yapılabilir.

     Şuradan aldığım kod parçasını paylaşmak istiyorum. Hani biz susalım kod konuşsun :)
public static class Enumerable
{
    public static IEnumerable<TSource> Where<TSource>(
        this IEnumerable<TSource> source, 
        Func<TSource, bool> predicate)
    {
        return (IEnumerable<TSource>) new Enumerable.WhereEnumerableIterator<TSource>(source, predicate);
    }
}


public static class Queryable
{
    public static IQueryable<TSource> Where<TSource>(
        this IQueryable<TSource> source, 
        Expression<Func<TSource, bool>> predicate)
    {
        return source.Provider.CreateQuery<TSource>(
            Expression.Call(
                null, 
                ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(
                    new Type[] { typeof(TSource) }), 
                    new Expression[] { source.Expression, Expression.Quote(predicate) }));
    }
}

     Yine ilk satır ToList() metodunu desteklemez. Bunun nedeni ToList() metodu IEnumerable içinde tanımlanmış olmasıdır, fakat IQueryable, IEnumerable  implemente eder ve doğal olarak ona ait bütün özellikleri kullanabilir. Cast işlemi yapıldığı takdirde artık bu ve diğer metodları kullanabilir. Şunu da belirtmekte fayda var ki IEnumerable "System.Collections" namespace altında iken IQueryable "System.Linq" altındadır. (Ayrıca IList ve ICollection da IEnumerable implemente eder.)

     Bir başka bahsetmek istediğim konuyu ise aşağıdaki kod üzerinden anlatmak istiyorum.
string Id = "2";
IQueryable queryable = _dbContext.Set<User>().Where(p=>p.Id == Convert.ToInt32(Id));

      Burada kodu derlemek istediğimizde her hangi bir hata ile karşılaşmayız, fakat run time'de kod hata fırlatır. Bunun sebebi Tsql içinde Convert.ToInt32() metodunun olmayışıdır. Çünkü IQueryable sorguyu sunucuda derler demiştik. Bunun aşağıdaki şekilde çalıştırdığımızda her hangi bir hata ile karşılaşmayacağız.
string Id = "2";
IEnumerable queryable = _dbContext.Set<Customer>().AsEnumerable().Where(p=>p.Id == Convert.ToInt32(Id));

     Buradaki yazımda bu konuya kısaca değinmiştim, fakat bunun neden çalıştığını açıkçasını söylemek gerekirse o zaman tam olarak idrak edememiştim. Şimdi ise daha iyi anlıyorum. IEnumerable, IQueryable tersine sorguyu sunucuda çalıştırmaz. Önce tüm verileri belleğe alır demiştik, daha sonra alınan verilere filtreleme işlemi yapıyoruz. Doğal olarak artık Convert.ToInt32() ile her hangi bir problem yaşamıyoruz.

    IQueryable ile özel sql sorgularını yazabiliyor iken IEnumerable böyle bir şeyi desteklemez.
ObjectQuery<User> userQuery = yourContext.CreateQuery<User>(queryString);
   
     IEnumerable Linq => Object işlemlerinde daha yararlı iken IQueryable Linq => Sql işlemlerinde daha kullanışlıdır.

     IEnumerable, IQueryable  kendisini implemente etmesine rağmen daha generic bir yapıya sahiptir. Bunun sebebi ise bir çok extention metod IEnumerable için daha elverişli olmasındandır.

     İkisi de "Deferred Execution" destekler. Türkçesi ertelenmiş çalışmadır. Şöyle ki Linq sorguları hemen çalışmaz. Belirli bir komuta kadar bekler. Bunu tetikleyenlerden bir tanesi de ToList() metodudur. Sorgu düzenlenir, fakat çalıştırılmaz ta ki ToList() metodu çağrılır ve veri tabanına istek yapılıp veri getirilir. Bunun yanında "Immediate Execution" de var. O da sorgunun hemen çalıştırılmasıdır.

      IEnumerable lazy loading desteklemez iken IQueryable destekler. Linq'nun veri tabanından veri çekerken kullandığı iki yöntemden biridir. Birbirine bağlı tablolarda tüm veriyi çekmek yerine istemci talep ettikçe verinin gelmesidir. Diğer yöntem olan eager  loading ise tüm veriyi belleğe yükler. (Aslında burada şaşırtıcı bir durum yok, ilk açıkladığımız madde ile aynı doğrultuda çalışmaktadır.) Bu konuda daha ayrıntılı bilgi için buraya bakabilirsiniz.

     Sonuç olarak söylemek gerekirse biri diğerinden üstündür gibi bir söylemde bulunmak yanlış olur. Hangi amaçla kullanıldığına bağlı olarak iki yapının da bir birinden üstünlükleri var. Bizler kendi yapımıza uygun olanı seçmeliyiz.

     Kullandığımız yapıları bilerek kullanmak dileğiyle...



6 yorum:

  1. yazınız çok açıklayıcı ,anlaşılır ve etkiliydi bu ikisinin arasındaki farkı unutacağımı sanmıyorum artık teşekkürler başarılarınızın devamını dilerim.

    YanıtlaSil
    Yanıtlar
    1. Güzel dilekleriniz için teşekkür ederim. Faydalanmış olmanıza çok sevindim.

      Sil
  2. Gerçekten hayran kaldım, ellerinize sağlık.

    YanıtlaSil
    Yanıtlar
    1. Çok teşekkür ederim güzel düşünceleriniz için

      Sil
  3. Malikcim eline sağlık. Gayet açıklayıcı olmuş.

    Teşekkürler.

    YanıtlaSil