Skip to content

Repository files navigation

DSO.Core.DynamicLinq

Dinamik LINQ Filtreleme ve Sıralama Kütüphanesi

.NET uygulamalarında çalışma zamanında oluşturulan filtre ve sıralama ifadelerini LINQ Expression Tree'lerine dönüştürür.

.NET C# LINQ License


📖 Hakkında

DSO.Core.DynamicLinq, LINQ sorgularında filtreleme ve sıralama işlemlerini çalışma zamanında dinamik olarak oluşturmak için geliştirilmiş, hafif ve harici bağımlılık gerektirmeyen bir .NET kütüphanesidir.

Kütüphanenin temel amacı, uygulama içerisinde önceden tanımlanmamış filtre ve sıralama kurallarını basit string ifadelerinden gerçek LINQ Expression Tree yapılarına dönüştürmektir.

Örneğin aşağıdaki filtre:

Age>=18 && IsActive=true

çalışma zamanında aşağıdaki mantıktaki bir Expression'a dönüştürülür:

x => x.Age >= 18 && x.IsActive == true

Benzer şekilde:

Category ASC, Price DESC

ifadesi:

.OrderBy(x => x.Category)
.ThenByDescending(x => x.Price)

mantığında dinamik bir LINQ sıralamasına dönüştürülür.


✨ Temel Özellikler

  • 🔎 String tabanlı dinamik filtreleme
  • ↕️ String tabanlı dinamik sıralama
  • 🔗 && ile AND ifadeleri
  • 🔀 || ile OR ifadeleri
  • 🧱 Parantezli ifadeler
  • 🔢 Standart karşılaştırma operatörleri
  • 🔍 Contains, StartsWith, EndsWith
  • 🔠 Büyük/küçük harfe duyarlı ve duyarsız arama
  • 📋 IN benzeri liste kontrolü
  • ❓ Null / whitespace kontrolü
  • 🔷 Enum desteği
  • Nullable tip desteği
  • 🏷️ Büyük/küçük harfe duyarsız property çözümleme
  • ⚡ Expression cache mekanizması
  • 🚀 IQueryable<T> desteği
  • 📦 IEnumerable<T> desteği
  • 🌐 Entity Framework / LINQ provider senaryolarına uygun Expression üretimi
  • 🧩 Extension Method API
  • 🚫 Harici Dynamic LINQ framework'üne ihtiyaç duymaz

🚀 Neden DSO.Core.DynamicLinq?

Dinamik filtreleme ve sıralama gereken uygulamalarda her kombinasyon için ayrı LINQ sorgusu yazmak kısa sürede karmaşık hale gelebilir.

Örneğin sabit bir sorgu şu şekilde yazılabilir:

var result = users.Where(x =>
    x.Age >= 18 &&
    x.IsActive &&
    x.City == "Istanbul");

Ancak filtre bilgisi HTTP isteğinden, bir veri gridinden veya başka bir istemciden geliyorsa bu yaklaşım yeterli olmaz.

DSO.Core.DynamicLinq ile aynı sorgu çalışma zamanında oluşturulabilir:

var result = users.Where(
    "Age>=18 && IsActive=true && City=Istanbul");

Bu yaklaşım özellikle aşağıdaki senaryolarda kullanışlıdır:

  • REST API filtreleme
  • Dinamik arama ekranları
  • Yönetim panelleri
  • Veri gridleri
  • Dinamik raporlama
  • Generic repository yapıları
  • Kullanıcı tarafından belirlenen filtreler
  • Çalışma zamanında belirlenen sıralamalar

🧠 Expression Tree Tabanlı Yapı

Kütüphane string ifadeleri doğrudan çalıştırmaz.

Bunun yerine ifadeyi ayrıştırarak LINQ Expression Tree oluşturur.

Genel işlem akışı:

String Filtre
     │
     ▼
Filtre Parser
     │
     ▼
Filter Parameter
     │
     ▼
Expression Tree
     │
     ├───────────────┐
     ▼               ▼
IQueryable       IEnumerable
     │               │
     ▼               ▼
LINQ Provider    LINQ to Objects

Filtreleme motorunun temel metodu:

LinqDynamicFiltering.GetFilter<T>(...)

sonuç olarak:

Expression<Func<T, bool>>

döndürür.


🔎 Dinamik Filtreleme

Filtreleme işleminin ana sınıfı:

LinqDynamicFiltering

Extension metotları ise:

LinqDynamicFilteringExtensions

üzerinden kullanılabilir.

En basit kullanım:

var result = users.Where("Age>=18");

Birden fazla koşul:

var result = users.Where(
    "Age>=18 && IsActive=true");

🔗 AND Operatörü

&& operatörü iki veya daha fazla koşulu AND mantığıyla birleştirir.

Age>=18 && IsActive=true

Mantıksal olarak:

x => x.Age >= 18 && x.IsActive == true

şeklinde Expression oluşturulur.

Parser içerisinde && operatörü parantez seviyesine göre değerlendirilir.


🔀 OR Operatörü

|| operatörü alternatif koşullar oluşturmak için kullanılabilir.

City=Istanbul || City=Ankara

Mantıksal karşılığı:

x => x.City == "Istanbul" || x.City == "Ankara"

şeklindedir.

|| operatörü de parantez seviyesine göre ayrıştırılır.


🧱 Parantez Desteği

Karmaşık filtre ifadelerinde parantez kullanılabilir.

Örneğin:

Age>=18 && (City=Istanbul || City=Ankara)

Daha karmaşık ifadeler de oluşturulabilir:

IsActive=true &&
(
    Age>=18 &&
    (City=Istanbul || City=Ankara)
)

Parser, parantezlerin eşleşmesini kontrol eder ve ifadeyi buna göre ayrıştırır.

📋 Filtre Operatörleri

Operatör Açıklama
= Eşit
== Eşit
!= Eşit değil
> Büyük
>= Büyük veya eşit
< Küçük
<= Küçük veya eşit
*= Contains, büyük/küçük harfe duyarsız
^= StartsWith, büyük/küçük harfe duyarsız
$= EndsWith, büyük/küçük harfe duyarsız
*== Contains, büyük/küçük harfe duyarlı
^== StartsWith, büyük/küçük harfe duyarlı
$== EndsWith, büyük/küçük harfe duyarlı
?? Null veya whitespace
!! Null/whitespace değil
[] Liste içerisinde bulunma
&& AND
`

🔢 Standart Karşılaştırmalar

Sayısal ve diğer dönüştürülebilir tiplerde standart karşılaştırma operatörleri kullanılabilir.

Eşitlik

Age=18

Büyük veya eşit

Age>=18

Küçük

Price<100

Eşit değil

Status!=Deleted

Kütüphane hedef property'sinin gerçek tipini belirler ve verilen string değeri bu tipe dönüştürür. Nullable tiplerde underlying type dikkate alınır.


🔍 String Arama Operatörleri

Kütüphanede string aramaları için özel operatörler bulunur.

Contains

Name*=john

*= operatörü Contains kullanır.


StartsWith

Name^=john

^= operatörü StartsWith kullanır.


EndsWith

Email$=@gmail.com

$= operatörü EndsWith kullanır.


🔠 Büyük/Küçük Harfe Duyarlı Arama

Standart string arama operatörlerinin yanı sıra duyarlı arama operatörleri de bulunur.

Operatör Metot
*= Contains + duyarsız
^= StartsWith + duyarsız
$= EndsWith + duyarsız
*== Contains + duyarlı
^== StartsWith + duyarlı
$== EndsWith + duyarlı

Örneğin:

Name*=john

büyük/küçük harfe duyarsız arama yaparken:

Name*==John

duyarlı Contains araması gerçekleştirir.

Kütüphane duyarsız aramalarda hem property tarafına hem de aranan değere ToLower() uygular.

🛡️ String Aramalarında Null Kontrolü

String aramalarında doğrudan Contains, StartsWith veya EndsWith çağrılmadan önce null kontrolü oluşturulur.

Örneğin:

Name*=john

ifadesi kavramsal olarak:

x.Name != null &&
x.Name.ToLower().Contains("john")

mantığında oluşturulur.

Bu sayede bellekte çalışan LINQ sorgularında null string değerleri nedeniyle oluşabilecek doğrudan method çağrısı kaynaklı hataların önüne geçilir.


❓ Null ve Whitespace Kontrolleri

İki özel operatör bulunur:

??
!!

?? — Null veya Whitespace

Name??

aşağıdaki mantıkta Expression oluşturur:

string.IsNullOrWhiteSpace(x.Name)

!! — Null/Whitespace Değil

Name!!

aşağıdaki mantıkta çalışır:

!string.IsNullOrWhiteSpace(x.Name)

Bu özellik BuildNullOrWhiteSpaceExpression içerisinde doğrudan string.IsNullOrWhiteSpace kullanılarak oluşturulur.


📋 IN / Liste İçerisinde Bulunma

[] operatörü bir property'sinin verilen değerlerden biri olup olmadığını kontrol etmek için kullanılabilir.

Örneğin:

Status[Active,Pending,Approved]

Bu ifade verilen değerlerden oluşan generic bir List<T> oluşturur ve Contains ile kontrol gerçekleştirir.

Kütüphane listedeki değerleri hedef property'sinin gerçek tipine dönüştürür.


🔷 Enum Desteği

Enum değerleri doğrudan string ifadelerden çözümlenebilir.

Örneğin:

public enum OrderStatus
{
    Pending,
    Approved,
    Cancelled
}

Aşağıdaki filtre kullanılabilir:

Status=Approved

IN operatörü de enum değerleriyle kullanılabilir:

Status[Pending,Approved]

Enum dönüşümü Enum.Parse üzerinden hedef enum tipi kullanılarak gerçekleştirilir.

Nullable Tip Desteği

Nullable value type'lar için underlying type otomatik olarak belirlenir.

Örneğin:

int?
decimal?
DateTime?

gibi tipler dinamik değer dönüşümünde desteklenir.

Kütüphane:

Nullable.GetUnderlyingType(...)

kullanarak gerçek underlying type'ı belirler.


🏷️ Property Adlarında Büyük/Küçük Harf Duyarsızlık

Property araması:

BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.IgnoreCase

kullanılarak yapılır.

Bu nedenle property adının yazımındaki büyük/küçük harf farklılıkları sorun oluşturmaz.

Örneğin gerçek property:

FirstName

ise aşağıdaki kullanım çözümlenebilir:

firstname*=john

Property bulunamazsa açıklayıcı bir ArgumentException oluşturulur.


↕️ Dinamik Sıralama

Dinamik sıralama işleminin ana sınıfı:

LinqOrderEngine

Extension sınıfı:

LinqOrderEngineExtensions

şeklindedir.

Basit kullanım:

var result = products.OrderBy("Price DESC");

⬆️ ASC

Artan sıralama:

Price ASC

veya yön belirtilmeden:

Price

kullanılabilir.


⬇️ DESC

Azalan sıralama:

Price DESC

şeklinde kullanılabilir.

ASC ve DESC kontrolü büyük/küçük harfe duyarsız yapılır.


🔀 Çoklu Sıralama

Birden fazla property virgül ile ayrılabilir.

Category ASC, Price DESC, Name ASC

İlk property için:

OrderBy

veya:

OrderByDescending

kullanılır.

Sonraki property'ler için:

ThenBy

veya:

ThenByDescending

kullanılır.

Bu nedenle:

Category ASC, Price DESC

şu mantıktaki sorguyu oluşturur:

query
    .OrderBy(x => x.Category)
    .ThenByDescending(x => x.Price);

🚀 IQueryable Desteği

Kütüphane doğrudan IQueryable<T> için extension sağlar.

var result = context.Products
    .Where("Price>=100")
    .OrderBy("Price DESC");

Where tarafında Expression oluşturulur ve source.Where(lambda) ile mevcut IQueryable zincirine eklenir.

Sıralama tarafında ise oluşturulan Expression, mevcut query provider üzerinden tekrar query olarak oluşturulur:

sourceQuery.Provider.CreateQuery<T>(currentExpression)

Bu yapı IQueryable sorgusunun Expression Tree tabanlı yapısını korur.


📦 IEnumerable Desteği

Kütüphane IEnumerable<T> için de extension sağlar.

var result = users
    .Where("Age>=18")
    .OrderBy("Name ASC");

Filtreleme tarafında oluşturulan Expression compile edilerek LINQ to Objects üzerinde kullanılabilir.

Sıralama tarafında ise collection AsQueryable() üzerinden aynı sıralama motoruna aktarılır ve sonuç IEnumerable<T> olarak döndürülür.


⚡ Expression Cache

Kütüphanenin önemli özelliklerinden biri Expression cache mekanizmasıdır.

Filtreleme için:

ConcurrentDictionary<(Type Type, string Filter), object>

kullanılır.

Cache anahtarı:

T tipi + filtre ifadesi

şeklindedir.

Sıralama için de:

ConcurrentDictionary<(Type Type, string Order), object>

kullanılır.

Böylece aynı tip ve aynı dinamik ifade tekrar kullanıldığında Expression'ın yeniden oluşturulması yerine cache'deki yapı kullanılabilir.


🧵 Thread-Safe Cache

Cache mekanizmasında:

ConcurrentDictionary

kullanılması, cache erişiminin çoklu thread senaryolarında güvenli şekilde yapılmasını sağlar.

Bu özellikle ASP.NET Core gibi aynı anda çok sayıda isteğin işlendiği uygulamalarda önemlidir.


🎯 Cache Anahtarının Tip Bilgisi İçermesi

Sadece filtre metninin cache anahtarı olarak kullanılması farklı entity tipleri arasında çakışmalara neden olabilir.

DSO.Core.DynamicLinq bu problemi:

Type + Filter

kombinasyonuyla çözer.

Örneğin:

User + "Name*=john"
Product + "Name*=john"

iki farklı cache girdisi olarak değerlendirilir.

Aynı yaklaşım sıralama motorunda da uygulanır:

Type + Order

🧩 Parametre Nesnesi ile Filtreleme

String filtre sözdiziminin yanında filtre parametresi üzerinden de Expression oluşturulabilir.

Bunun için:

LingFilterParameter

sınıfı kullanılır.

Örneğin:

var parameter = new LingFilterParameter
{
    PropertyName = "Age",
    Operator = ">=",
    Value = "18"
};

var expression =
    LinqDynamicFiltering.GetFilter<User>(parameter);

Bu API doğrudan Expression<Func<T, bool>> oluşturur.

Not: Mevcut kaynak kodundaki sınıf adı LingFilterParameter şeklindedir ve README'de de mevcut API ile birebir uyumlu olarak bu isim kullanılmıştır.


🏗️ API Özeti

Filtreleme

LinqDynamicFiltering.GetFilter<T>(string filterExpression)
LinqDynamicFiltering.GetFilter<T>(LingFilterParameter parameter)

IQueryable Filtreleme

source.Where("...");

IEnumerable Filtreleme

source.Where("...");

IQueryable Sıralama

source.OrderBy("...");

IEnumerable Sıralama

source.OrderBy("...");

🧪 Kullanım Örnekleri

Basit Filtre

var result = users.Where("Age>=18");

Birden Fazla Koşul

var result = users.Where(
    "Age>=18 && IsActive=true");

OR

var result = users.Where(
    "City=Istanbul || City=Ankara");

Parantez

var result = users.Where(
    "Age>=18 && (City=Istanbul || City=Ankara)");

Contains

var result = users.Where(
    "Name*=john");

StartsWith

var result = users.Where(
    "Name^=john");

EndsWith

var result = users.Where(
    "Email$=@gmail.com");

IN

var result = users.Where(
    "Status[Active,Pending,Approved]");

Null / Whitespace

var result = users.Where(
    "Email!!");

Tekli Sıralama

var result = users.OrderBy(
    "Name ASC");

Çoklu Sıralama

var result = users.OrderBy(
    "Department ASC, Salary DESC");

🌐 ASP.NET Core API Örneği

Kütüphane özellikle dinamik API sorguları için kullanışlıdır.

Örneğin bir API aşağıdaki parametreleri alabilir:

filter=Age>=18 && IsActive=true
order=LastName ASC, FirstName ASC

Ardından:

public IQueryable<User> ApplyQuery(
    IQueryable<User> query,
    string filter,
    string order)
{
    if (!string.IsNullOrWhiteSpace(filter))
        query = query.Where(filter);

    if (!string.IsNullOrWhiteSpace(order))
        query = query.OrderBy(order);

    return query;
}

şeklinde generic bir query katmanı oluşturulabilir.

Bu sayede her filtre kombinasyonu için ayrı LINQ kodu yazmaya gerek kalmaz.


🗄️ Entity Framework Kullanımı

IQueryable<T> desteği sayesinde Entity Framework sorgularında kullanılabilir.

Örneğin:

var result = dbContext.Products
    .Where("IsActive=true && Price>=100")
    .OrderBy("Category ASC, Price DESC")
    .ToList();

Filtreleme Expression Tree olarak oluşturulur:

String Filter
     ↓
Expression<Func<T,bool>>
     ↓
IQueryable.Where(...)

Sıralama da Expression Tree üzerinden Queryable metodlarına dönüştürülür.


📊 Özellik Karşılaştırması

Özellik DSO.Core.DynamicLinq
Dinamik filtreleme
Dinamik sıralama
IQueryable<T>
IEnumerable<T>
Expression Tree
Expression Cache
Thread-safe cache
AND / OR
Parantez desteği
Eşitlik karşılaştırmaları
Sayısal karşılaştırmalar
Contains
StartsWith
EndsWith
Case-insensitive arama
Case-sensitive arama
IN / liste kontrolü
Enum
Nullable tipler
Null / whitespace kontrolü
Case-insensitive property çözümleme
Çoklu sıralama
ASC / DESC
Harici Dynamic LINQ framework'ü
Raw SQL zorunluluğu

🆚 Manuel Expression Yazımına Karşı

Manuel olarak dinamik Expression üretmek istediğinizde property çözümleme, operator seçimi, type conversion, Expression oluşturma ve sıralama zincirlerinin tamamını kendiniz yönetmeniz gerekir.

DSO.Core.DynamicLinq bu işlemleri tek bir API altında toplar.

Manuel yaklaşım
────────────────────────────
Property bul
      ↓
Tip belirle
      ↓
String değeri dönüştür
      ↓
Operator belirle
      ↓
Expression oluştur
      ↓
Lambda oluştur
      ↓
Queryable / Enumerable uygula

DSO.Core.DynamicLinq:

"Age>=18 && IsActive=true"
             │
             ▼
     DSO.Core.DynamicLinq
             │
             ▼
      Expression Tree

📦 Kurulum

NuGet üzerinden:

dotnet add package DSO.Core.DynamicLinq

veya Visual Studio Package Manager üzerinden:

Install-Package DSO.Core.DynamicLinq

Ardından:

using DSO.Core.DynamicLinq;

⚙️ Gereksinimler

Kütüphane modern .NET uygulamaları için tasarlanmıştır.

Önerilen hedef:

.NET 6.0+

Temel olarak .NET'in kendi:

  • LINQ
  • Expression
  • Reflection
  • Generic Collections
  • Concurrent Collections
  • Regular Expressions

API'lerini kullanır.

Harici bir Dynamic LINQ framework'üne bağımlı değildir.


📁 Proje Yapısı

DSO.Core.DynamicLinq
│
├── LinqDynamicFiltering.cs
├── LinqOrderEngine.cs
├── README.md
└── LICENSE

Temel bileşenler:

LinqDynamicFiltering
        │
        └── LinqDynamicFilteringExtensions

LinqOrderEngine
        │
        └── LinqOrderEngineExtensions

🔒 Dinamik Filtrelerde Güvenlik

Filtre ifadeleri dışarıdan, özellikle HTTP isteklerinden geliyorsa uygulama seviyesinde izin verilen property'lerin kontrol edilmesi önerilir.

Örneğin bir API'nin sadece şu alanları filtrelemesine izin verilebilir:

Name
Email
Age
Status

Böylece istemcinin sorgulayabileceği alanlar uygulama tarafından kontrol altında tutulabilir.

DSO.Core.DynamicLinq bir Expression üretim kütüphanesidir; uygulama seviyesindeki authorization ve field-level erişim kurallarını kendisi belirlemez.


🎯 Kullanım Alanları

DSO.Core.DynamicLinq özellikle şu projelerde kullanılabilir:

🌐 REST API

HTTP query parametrelerinden dinamik filtre ve sıralama oluşturma.

🖥️ Yönetim Panelleri

Kullanıcıların tablo kolonlarına göre dinamik filtre oluşturması.

📊 Veri Gridleri

Çoklu kolon sıralama ve dinamik filtreleme.

🗃️ Generic Repository

Farklı entity tipleri için ortak query altyapısı.

📈 Raporlama

Çalışma zamanında belirlenen filtre ve sıralama kriterleri.

🔎 Dinamik Arama

Kullanıcı tarafından belirlenen arama kriterlerinin LINQ sorgusuna dönüştürülmesi.


🧠 Tasarım Yaklaşımı

DSO.Core.DynamicLinq'in temel yaklaşımı mümkün olduğunca basittir:

Basit String
     ↓
Parser
     ↓
Expression
     ↓
LINQ

Kütüphane sorgu motorunun kendisi olmaya çalışmaz.

Bunun yerine mevcut LINQ altyapısının üzerine dinamik bir ifade oluşturma katmanı ekler.

Bu nedenle:

  • LINQ Expression Tree kullanır.
  • IQueryable<T> yapısını destekler.
  • IEnumerable<T> yapısını destekler.
  • LINQ Provider'ın yerine geçmez.
  • Veritabanına özel SQL üretmez.
  • Harici bir Dynamic LINQ framework'üne ihtiyaç duymaz.

⚡ Performans Yaklaşımı

Expression oluşturma işlemi tekrar tekrar yapılmaması için filtreleme ve sıralama tarafında cache kullanılır.

Filtreleme:

Type + Filter

Sıralama:

Type + Order

anahtarı üzerinden cache'lenir.

Bu yapı özellikle aynı uygulamada aynı dinamik sorguların tekrar tekrar çalıştırıldığı API ve web uygulamalarında gereksiz Expression oluşturma maliyetini azaltmayı amaçlar.


📝 Örnek Komple Sorgu

Örneğin bir ürün API'sinin aşağıdaki kuralları desteklediğini düşünelim:

Aktif ürünler
Fiyatı 100'den büyük veya eşit
Stokta bulunanlar
Kategoriye göre artan
Fiyata göre azalan

Tek bir dinamik sorguyla:

var result = dbContext.Products
    .Where(
        "IsActive=true && Price>=100 && Stock>0")
    .OrderBy(
        "Category ASC, Price DESC")
    .ToList();

Bu yaklaşım sabit bir sorgu tanımlamak yerine sorgu kriterlerini çalışma zamanında belirlemeyi sağlar.


🤝 Katkıda Bulunma

Katkılar, hata bildirimleri ve geliştirme önerileri memnuniyetle karşılanır.

Bir hata bildirirken mümkün olduğunca aşağıdaki bilgileri paylaşmanız önerilir:

  • .NET sürümü
  • Entity sınıfı
  • Kullanılan filtre
  • Kullanılan sıralama
  • Beklenen sonuç
  • Gerçek sonuç
  • Oluşan exception
  • Kullanılan LINQ provider

📄 Lisans

Bu proje MIT License altında lisanslanmıştır.

Detaylar için repository içerisindeki LICENSE dosyasına bakabilirsiniz.


⭐ Projeyi Destekleyin

DSO.Core.DynamicLinq işinize yarıyorsa:

  • ⭐ GitHub repository'sine yıldız verebilirsiniz.
  • 🐛 Hataları bildirebilirsiniz.
  • 💡 Yeni özellik önerileri gönderebilirsiniz.
  • 🔧 Pull Request gönderebilirsiniz.
  • 📢 Projeyi paylaşabilirsiniz.

🚀 Sonuç

DSO.Core.DynamicLinq, çalışma zamanında belirlenen filtreleme ve sıralama kurallarını standart LINQ Expression yapılarına dönüştürmek için geliştirilmiş hafif bir kütüphanedir.

Temel kullanım modeli oldukça basittir:

var result = source
    .Where("IsActive=true && Price>=100")
    .OrderBy("Category ASC, Price DESC");

Bunun arkasında ise:

String Expression
       ↓
Parser
       ↓
Type Resolution
       ↓
Expression Tree
       ↓
Cache
       ↓
IQueryable / IEnumerable

şeklinde çalışan bir yapı bulunur.

Dinamik filtreleme + dinamik sıralama + Expression Tree + IQueryable + IEnumerable + cache

hepsini tek ve hafif bir kütüphanede bir araya getirir.


DSO.Core.DynamicLinq
.NET için dinamik LINQ filtreleme ve sıralama.

About

DSO.Core.DynamicLinq

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages