DevExpress XAF’te, bir nesnenin özelliğini, ilişkili olduğu bir koleksiyondaki verilere dayanarak hesaplamak sıkça karşılaşılan bir ihtiyaçtır. Örneğin, bir Müşteri’nin toplam sipariş sayısını veya bir Fatura’nın kalemleri üzerinden KDV toplamını göstermek isteyebilirsiniz.
Geleneksel C# yaklaşımı, bu değeri bir get metodu içinde koleksiyonu döngüye alarak hesaplamaktır. Ancak bu yöntem, özellikle büyük koleksiyonlarda veya liste görünümlerinde (Grid) sıralama/filtreleme yapıldığında ciddi performans sorunlarına ve hatta StackOverflowException gibi kritik hatalara yol açar.
Çözüm, bu hesaplamayı uygulama katmanından veritabanı katmanına taşımaktır. İşte bu noktada [PersistentAlias] özniteliği devreye girer.
PersistentAlias Nedir?
[PersistentAlias], bir özelliğin değerinin nasıl hesaplanacağını C# kodu yerine, XPO’nun anladığı bir “kriter dili” ile tanımlamanızı sağlar. XPO, bu ifadeyi analiz eder ve doğrudan sunucu tarafında çalışacak bir SQL sorgusuna (veya kullanılan veritabanına uygun sorguya) dönüştürür.
Avantajları:
- Üstün Performans: Hesaplama, binlerce kaydı saniyeler içinde işleyebilen veritabanı sunucusunda yapılır. Uygulama sunucusu gereksiz yere meşgul edilmez.
- Güvenilirlik: C# döngülerinden kaynaklanan sonsuz özyineleme ve StackOverflowException hataları ortadan kalkar.
- Sunucu Taraflı İşlemler: Bu özellikle oluşturulan alanlar üzerinde liste görünümlerinde sıralama (sorting), filtreleme (filtering) ve gruplama (grouping) işlemleri veritabanı seviyesinde, son derece hızlı bir şekilde yapılabilir.
Temel Sözdizimi ve Kavramlar
Hesaplamalara geçmeden önce, PersistentAlias içinde kullanılan temel yapı taşlarını anlamalıyız.
- Aggregate Fonksiyonları: Koleksiyonlar üzerinde çalışan temel fonksiyonlardır.
- Sum(AlanAdi): Belirtilen alanın toplamını alır.
- Count(): Koleksiyondaki eleman sayısını verir.
- Avg(AlanAdi): Ortalamayı hesaplar.
- Min(AlanAdi) / Max(AlanAdi): En küçük veya en büyük değeri bulur.
- Filtreleme ([…]): Bir koleksiyon içindeki belirli koşulları sağlayan elemanları seçmek için kullanılır.
- Mevcut Nesneye Referans (^): PersistentAlias ifadesi içinde, ifadenin uygulandığı mevcut nesneye referans vermek için ^ (caret) işareti kullanılır. Bu, en kritik operatörlerden biridir.
- Global Sorgu Başlatma (<TypeName>): Sorguya ilişkili bir koleksiyon üzerinden değil de, veritabanındaki bir tablonun tamamı üzerinden başlamak için <BusinessObjectName> sözdizimi kullanılır.
Senaryolar ve Örnekler
Konuyu daha iyi anlamak için basit bir veri modeli oluşturalım:
- Musteri: Müşteri kartları (Ad, Soyad).
- Siparis: Müşteriye ait siparişler (Tarih, Tutar, Durum (Enum: OnayBekliyor, Onaylandi, IptalEdildi)).
- SiparisDetay: Bir siparişin kalemleri (UrunAdi, Miktar, BirimFiyat).
Senaryo 1: Basit Koleksiyon Toplamları (Count ve Sum)
En temel kullanım, bir koleksiyondaki tüm elemanlar üzerinde işlem yapmaktır.
Örnek 1.1: Müşterinin Toplam Sipariş Sayısı
Musteri sınıfına, o müşteriye ait toplam sipariş sayısını gösteren bir alan ekleyelim.codeC#
public class Musteri : BaseObject
{
// ... diğer alanlar
[Association("Musteri-Siparisler")]
public XPCollection<Siparis> Siparisler
{
get => GetCollection<Siparis>(nameof(Siparisler));
}
[PersistentAlias("Siparisler.Count()")]
public int SiparisSayisi
{
get => Convert.ToInt32(EvaluateAlias(nameof(SiparisSayisi)));
}
}
- Açıklama: Siparisler.Count() ifadesi, XPO’ya “Bu müşteriye bağlı Siparisler koleksiyonundaki eleman sayısını say” talimatını verir. Bu, SQL’de COUNT(*) işlemi yapan bir alt sorguya dönüşür.
Örnek 1.2: Müşterinin Toplam Sipariş Tutarı
Musteri sınıfına, tüm siparişlerinin Tutar alanlarının toplamını gösteren bir alan ekleyelim.codeC#
public class Musteri : BaseObject
{
// ...
[PersistentAlias("Siparisler.Sum(Tutar)")]
public decimal ToplamSiparisTutari
{
get => Convert.ToDecimal(EvaluateAlias(nameof(ToplamSiparisTutari)));
}
}
- Açıklama: Siparisler.Sum(Tutar) ifadesi, Siparisler koleksiyonundaki her bir siparişin Tutar alanını toplayarak sonucu döndürür.
Senaryo 2: Koşullu Koleksiyon Toplamları (Filtreli Aggregates)
Genellikle bir koleksiyonun tamamını değil, sadece belirli koşulları sağlayan elemanları toplamak isteriz.
Örnek 2.1: Müşterinin “Onay Bekliyor” Durumundaki Sipariş Sayısı
Musteri sınıfına, sadece onay bekleyen siparişlerin sayısını gösterelim.codeC#
// SiparisDurum enum'ınızın olduğunu varsayalım:
public enum SiparisDurum { OnayBekliyor, Onaylandi, IptalEdildi }
public class Musteri : BaseObject
{
// ...
[PersistentAlias("Siparisler[Durum = 'OnayBekliyor'].Count()")]
public int BekleyenSiparisSayisi
{
get => Convert.ToInt32(EvaluateAlias(nameof(BekleyenSiparisSayisi)));
}
}
- Açıklama: Siparisler[Durum = ‘OnayBekliyor’] kısmı, koleksiyonu filtreler ve sadece Durum alanı OnayBekliyor olan siparişleri dikkate alır. Ardından .Count() bu filtrelenmiş küme üzerinde çalışır. Enum değerleri string olarak yazılır.
Örnek 2.2: Siparişin Yüksek Miktarlı Kalemlerinin Toplam Tutarı
Bir Siparis nesnesine, sadece miktarı 10’dan fazla olan SiparisDetay kalemlerinin toplam tutarını hesaplayan bir alan ekleyelim.codeC#
public class Siparis : BaseObject
{
// ...
[Association("Siparis-Detaylar")]
public XPCollection<SiparisDetay> Detaylar
{
get => GetCollection<SiparisDetay>(nameof(Detaylar));
}
[PersistentAlias("Detaylar[Miktar > 10].Sum(Miktar * BirimFiyat)")]
public decimal BuyukKalemlerTutari
{
get => Convert.ToDecimal(EvaluateAlias(nameof(BuyukKalemlerTutari)));
}
}
- Açıklama: Burada iki önemli nokta var:
- Detaylar[Miktar > 10] ile kalemler filtreleniyor.
- Sum() içinde Miktar * BirimFiyat gibi matematiksel bir ifade kullanılabiliyor. XPO bu işlemi de SQL sorgusuna dahil eder.
Senaryo 3: Kümülatif Bakiye (Running Total) Hesaplama
Bu, en gelişmiş ve en güçlü kullanım senaryosudur. Her satırın, kendisinden önceki satırların sonucuna göre hesaplandığı durumlarda kullanılır. Örnek olarak kasa hareketlerindeki bakiye hesaplamasını ele alalım.codeC#
public class KasaHareket : BaseObject
{
public DateTime Tarih { get; set; }
public decimal Tahsilat { get; set; }
public decimal Odeme { get; set; }
[Association("Kasa-Hareketler")]
public Kasa Kasa { get; set; }
[PersistentAlias("IsNull([<KasaHareket>][Kasa = ^.Kasa And (Tarih < ^.Tarih Or (Tarih == ^.Tarih And Oid < ^.Oid))].Sum(Tahsilat - Odeme), 0) + (Tahsilat - Odeme)")]
public decimal Bakiye
{
get
{
object result = EvaluateAlias(nameof(Bakiye));
return result == null ? 0m : Convert.ToDecimal(result);
}
}
}
- Açıklama (Detaylı):
- [<KasaHareket>]: Sorguya tüm kasa hareketleri tablosu üzerinden başlar. Bu, InvalidPropertyPathException hatasını önler.
- [Kasa = ^.Kasa And … ]: En önemli filtredir. Sadece mevcut hareketin (^) kasasıyla aynı olan diğer hareketleri dikkate alır.
- (Tarih < ^.Tarih Or (Tarih == ^.Tarih And Oid < ^.Oid)): Sıralamayı garanti eden filtredir. “Tarihi daha eski olanları VEYA tarihi aynıysa Oid’si (kayıt kimliği) daha küçük olanları bul” anlamına gelir. Bu, aynı gün içindeki işlemlerin doğru sıralanmasını sağlar.
- .Sum(Tahsilat – Odeme): Bu filtrelerden geçen tüm önceki hareketlerin bakiyesini (Tahsilat – Odeme) toplayarak “devreden bakiye”yi bulur.
- IsNull(…, 0): İlk harekette toplanacak önceki kayıt olmayacağı için Sum işlemi NULL döndürür. Bu fonksiyon NULL yerine 0 kullanılmasını sağlayarak hatayı önler.
- + (Tahsilat – Odeme): Son olarak, bulunan devreden bakiyenin üzerine mevcut hareketin kendi işlemini ekler.
Sonuç ve En İyi Pratikler
- Her Zaman Sunucuyu Tercih Edin: Bir koleksiyon üzerinde hesaplama yapmanız gerektiğinde, ilk düşünceniz her zaman PersistentAlias kullanmak olmalıdır.
- Tie-Breaker’ı Unutmayın: Kümülatif bakiye gibi sıralamaya dayalı hesaplamalarda, tarihin aynı olma ihtimaline karşı Oid veya başka bir benzersiz anahtar ile ikincil bir sıralama kriteri (tie-breaker) eklemek zorunludur.
- IsNull Kullanımı: Sum, Avg gibi fonksiyonlar boş kümeler için NULL döndürür. Bu da NullReferenceException hatasına neden olabilir. Sonucu IsNull() ile sarmalayarak bu sorunu proaktif olarak çözün.
- Hataları Anlayın: InvalidPropertyPathException genellikle yanlış sorgu bağlamından (context) kaynaklanır. Sorgunuza <TypeName> ile başlamak genellikle bu sorunu çözer.
PersistentAlias, XAF uygulamalarınızın performansını ve kararlılığını bir üst seviyeye taşıyan, mutlaka öğrenilmesi gereken bir araçtır. Bu örnekler ve açıklamalar, kendi senaryolarınızda bu güçlü özelliği etkin bir şekilde kullanmanız için sağlam bir temel oluşturacaktır.