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.
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 == trueBenzer ş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.
- 🔎 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
- 📋
INbenzeri 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
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
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.
Filtreleme işleminin ana sınıfı:
LinqDynamicFilteringExtension 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");&& 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.
|| 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.
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)
)
| 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 |
| ` |
Sayısal ve diğer dönüştürülebilir tiplerde standart karşılaştırma operatörleri kullanılabilir.
Age=18
Age>=18
Price<100
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.
Kütüphanede string aramaları için özel operatörler bulunur.
Name*=john
*= operatörü Contains kullanır.
Name^=john
^= operatörü StartsWith kullanır.
Email$=@gmail.com
$= operatörü EndsWith kullanır.
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.
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.
İki özel operatör bulunur:
??
!!
Name??
aşağıdaki mantıkta Expression oluşturur:
string.IsNullOrWhiteSpace(x.Name)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.
[] 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 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]
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 araması:
BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.IgnoreCasekullanı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:
FirstNameise aşağıdaki kullanım çözümlenebilir:
firstname*=john
Property bulunamazsa açıklayıcı bir ArgumentException oluşturulur.
Dinamik sıralama işleminin ana sınıfı:
LinqOrderEngineExtension sınıfı:
LinqOrderEngineExtensionsşeklindedir.
Basit kullanım:
var result = products.OrderBy("Price DESC");Artan sıralama:
Price ASC
veya yön belirtilmeden:
Price
kullanılabilir.
Azalan sıralama:
Price DESC
şeklinde kullanılabilir.
ASC ve DESC kontrolü büyük/küçük harfe duyarsız yapılır.
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);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.
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.
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.
Cache mekanizmasında:
ConcurrentDictionarykullanı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.
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
String filtre sözdiziminin yanında filtre parametresi üzerinden de Expression oluşturulabilir.
Bunun için:
LingFilterParametersı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.
LinqDynamicFiltering.GetFilter<T>(string filterExpression)LinqDynamicFiltering.GetFilter<T>(LingFilterParameter parameter)source.Where("...");source.Where("...");source.OrderBy("...");source.OrderBy("...");var result = users.Where("Age>=18");var result = users.Where(
"Age>=18 && IsActive=true");var result = users.Where(
"City=Istanbul || City=Ankara");var result = users.Where(
"Age>=18 && (City=Istanbul || City=Ankara)");var result = users.Where(
"Name*=john");var result = users.Where(
"Name^=john");var result = users.Where(
"Email$=@gmail.com");var result = users.Where(
"Status[Active,Pending,Approved]");var result = users.Where(
"Email!!");var result = users.OrderBy(
"Name ASC");var result = users.OrderBy(
"Department ASC, Salary DESC");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.
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 | 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 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
NuGet üzerinden:
dotnet add package DSO.Core.DynamicLinqveya Visual Studio Package Manager üzerinden:
Install-Package DSO.Core.DynamicLinqArdından:
using DSO.Core.DynamicLinq;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.
DSO.Core.DynamicLinq
│
├── LinqDynamicFiltering.cs
├── LinqOrderEngine.cs
├── README.md
└── LICENSE
Temel bileşenler:
LinqDynamicFiltering
│
└── LinqDynamicFilteringExtensions
LinqOrderEngine
│
└── LinqOrderEngineExtensions
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.
DSO.Core.DynamicLinq özellikle şu projelerde kullanılabilir:
HTTP query parametrelerinden dinamik filtre ve sıralama oluşturma.
Kullanıcıların tablo kolonlarına göre dinamik filtre oluşturması.
Çoklu kolon sıralama ve dinamik filtreleme.
Farklı entity tipleri için ortak query altyapısı.
Çalışma zamanında belirlenen filtre ve sıralama kriterleri.
Kullanıcı tarafından belirlenen arama kriterlerinin LINQ sorgusuna dönüştürülmesi.
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.
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.
Ö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ı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
Bu proje MIT License altında lisanslanmıştır.
Detaylar için repository içerisindeki LICENSE dosyasına bakabilirsiniz.
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.
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.