İçeriğe geç

Linq ile Dinamik Sorgular

Elimizde bir personel listesi var ve her personelin kendine özgü yetenekleri olduğunu düşünelim. Senaryo gereği bu liste için bizden filtreleme özelliği istendiğini varsayalım.

Bu senaryoya göre elimizde 3 tip yeteneği(yüzme, tırmanış, basketbol) barındıran bir personel modelimiz olsun. Model tasarımı için çok özenmiyorum çünkü burada amaç dinamik lambda ifadelerini deneyimlemektir.

    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Surname { get; set; }
        public bool Swimming { get; set; }
        public bool Climbing { get; set; }
        public bool Basketball { get; set; }
    }

Şimdi listeden sadece yüzme yeteneğine sahip kişileri çekmek istediğimizi varsayalım. Bu basit bir taleptir ve bu durumda aşağıdaki linq ifadesi istediğimiz sonucu bize sağlayacaktır.

var result1 = personalList.Where(x => x.Swimming == true).ToList();

Şimdi de listede hem yüzme yeteneği hem de tırmanış yeteneği olanları listelemek istediğimizi varsayalım. Bu durumda koşulumuza bir yenisini eklememiz gerekecek ve sorgumuz aşağıdaki gibi olacaktır.

var result2 = personalList.Where(x => x.Swimming == true && x.Climbing).ToList();

Peki biz her istenilen filtreleme durumları için bu şekilde ayrı ayrı kod mu yazacağız? Hatta buradaki yeteneklerin ilerleyen zamanlarda artması olasıdır. Örneğin ilerleyen zamanlarda satranç bilenler, yemek pişirebilenler, şarkı söyleyebilenler gibi pek çok yeni yetenek daha eklenmesi olası bir durumdur. Bu durumda yeni eklenen yeteneklere göre de filtreleme yapabilmeliyiz. Bunu sağlayabilmek için tekrardan projemizi açıp buna yönelik bir kodlama yapmak bir yöntemdir fakat kötü bir yöntemdir. Bu durumda yukarıdaki oluşturduğumuz koşulları dinamik şekilde oluşturmak işimizi kolaylaştıracaktır.

Şimdi dinamik bir linq ifadesi yazabilmek için bazı noktalara değinelim.

ParameterExpression (x):
Sorgu hazırlamak için kullanacağınız parametredir.

MemberExpression (x.Swimming):
Sorgu için kullanacağınız koşul üyesidir.

ConstantExpression (true):
Koşul üyesine atayacağınız değerdir.

            var member = Expression.Property(parameter, "Swimming"); //x.Swimming
            var constant = Expression.Constant(true);
            var body = Expression.Equal(member, constant); //x.Swimming == true
            var finalExpression = Expression.Lambda<Func<Person, bool>>(body, parameter); //x => x.Swimming == true

Görebileceğimiz üzere dinamik bir expression hazırladık ve finalExpression değişkeninin sonucu olarak bize x=> x.Swimming == true değeri dönecektir. Peki bunu bir tane koşul için bu şekilde hazırladık fakat yeteneklerin listesinin artabileceğini düşünmüştük. O sebeple bu ifadeyi daha dinamik bir hale dönüştürmeliyiz. Burada hangi yetenekler için sorgulama yapacağımızı bilmeye ihtiyaç duyarız. Bunun için Filter isminde bir model oluşturalım(realitede db’den çektiğimizi düşünebiliriz!).

    public class Filter
    {
        public bool? Swimming { get; set; }
        public bool? Climbing { get; set; }
        public bool? Basketball { get; set; }
    }

Şimdi elimizdeki bu Filtre nesnesi yardımıyla neyi filtreleyebileceğimizi belirleyebileceğiz. Buradaki filtre değerleri dışarıya açtığınız bir servis vesilesiyle gelebileceği gibi bir web sayfası üzerinden kullanıcının belirlemesiyle de gelebilir.

Şimdi dinamik sorgu ile yukarıda yaptığımız “Yüzme” ve “Tırmanış” yeteneklerini döndüren bir sorgu yazalım fakat daha öncesinde reflection yardımıyla filtre nesnesi içindeki gönderilen değerleri tespit edeceğiz.

            var filterList = filter.GetType().GetProperties()
                  .Select(prop =>
                  new
                  {
                      name = prop.Name,
                      value = prop.GetValue(filter, null)
                  }).Where(x => x.value != null).ToList();

Şimdi artık elde ettiğimiz filtre değerlerine göre dinamik sorgumuzu hazırlayabiliriz. Burada her bir expression ifadelerini saklayacak olan BinaryExpression nesnesinden yararlanacağız.

                  filterList.ForEach(item =>
                  {
                      binaryExpression = Expression.AndAlso(binaryExpression == null ? defaultExpression : binaryExpression, Expression.Equal
                                                         (
                                                             Expression.PropertyOrField(parameter, item.name),
                                                             Expression.Constant(item.value)
                                                         ));
                  });

Son olarak elde ettiğimiz sorgu ifadesini where bloğu içine atarak ilgili kriterlere göre listenin gelmesini sağlıyoruz.

            var finalExpression = Expression.Lambda<Func<Person, bool>>(binaryExpression, parameter);
            var result3 = personList.AsQueryable().Where(finalExpression).ToList();

Sonuç itibariyle hali hazırdaki yeteneklere göre dinamik olarak sorgulama yapabilmekteyiz ve yeteneklerin durumunda değişme olması durumunda da ek bir geliştirme ihtiyacı doğmayacak. Buradaki yapının kullanımında külfete sebep olacak senaryolar da olabilir fakat bu senaryo açısından uygun bir yapıdır diyebiliriz veya veritabanındaki tabloları son kullanıcıya bir arayüz ile açarak onların filtreleme yapabilmesine olanak tanıyabilirsiniz. Dynamic expression kavramı bu tarz ihtiyaçlar için iyidir diyebiliriz.

Hazırladığımız uygulamaya ait kodları görmek için şuradaki adresi kullanabilirsiniz. Hepinize mutlu kodlamalar diliyorum.

Tarih:C#Linq

2 Yorum

  1. Merhabalar,
    Öncelikle güzel paylaşımınız için teşekkür ediyorum. Boolean kontrolleri için sağlıklı bir şekilde işlemler gerçekleşiyor. String sorgularında; içerme işlemlerini, Contains() gibi fonksiyonları nasıl kontrol edebilirim acaba, tek seferde finalExpression içerisinde göndermek istedim, denedim fakat yapamadım bir türlü. Teşekkürler şimdiden 🙂

  2. Yusuf Selam,

    Belirttiğin ihtiyaç için aşağıdaki gibi bir kod yaklaşım işini görecektir.

    var propertyExp = Expression.Property(parameter, item.name);
    MethodInfo method = typeof(string).GetMethod(“Contains”, new[] { typeof(string) });
    var value = Expression.Constant(item.value, typeof(string));
    expressionTemp = Expression.AndAlso(binaryExpression == null ? defaultExpression : binaryExpression, Expression.Call(propertyExp, method, value));

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.