Hüseyin Çelik Blog Sayfasi

.Net, Java, Android, Progress 4gl Paylasimlarim

Nedir bu XPO nam-i digerle eXpressPersistent Objects

XPO yani express persistent objects nedir ne ise yarar anlatmaya çalisacagim.  XPO aslinda DexExpress tarafindan gelistirilen saglam bir data katmanidir. Yazacagimiz bir projede veritabani islerini bizim için yapan bir yardimci araçtir.

Normal bir projede ister prosedürel olsun ister çok katmanli, proje gelistirme asamalarindan biride veritabani sürecidir. Veritabani tasarimi, tablolar, viewler, fonksiyonlar, prosedürler, triggerlar, tablo iliskileri ve indsexler. Önce bunlarin tasarimi yapilmali. Tasarim yapildiktan sonra veritabani islemleri hazirlanmali. Yeni kayit ekleme, kayit silme, kayit güncelleme ve kayitlari sorgulama vb. Bu islemler için prosedürel projelerde bir class eklenir, çok katmanli projelerde bir proje olarak eklenir. Kabaca anlatmaya çalistigim proje gelistirirken veritabani is yükünü anlatmaya çalistim. XPO ile bu yük en aza iner. Yazdiginiz bir projede MSSQL kullandiysaniz ve kullanici sizden MySQL kullanmanizi isterse o zaman tüm tasarim ve kodlar yeniden ele alinmalidir. XPO ise detekledigi veritabanlarindan herhangi birini kullanmaniza imkan verir. Örnegin tümüyle yazip bitiridiginiz bir projeyi SQLite, MySQL, MSSQL ve ORACLE da rahatlikla çalistirabilirsiniz. Simdi XPO yu bir kaç örnekle anlatmaya çalisayim.

Öcelikle XPO kullanabilmek için bazi siniflari projeye eklemeniz gerekiyor.

DevExpress.Data.vXX.X.dll

DevExpress.Xpo.vXX.X.dll

Projenize bir config dosyasi ekleyin, connectionstring ve XPO nun olusturmus oldugu sqlleri görebilmek için. Config dosyaniza su kodu ekleyin.

<system.diagnostics>
		<switches>
			<!-- Use the one of predefined values: 0-Off, 1-Errors, 2-Warnings, 3-Info, 4-Verbose. The default value is 3. -->
			<add name="eXpressAppFramework" value="4" />
			<add name="XPO" value="4" />
</switches>
</system.diagnostics>

 

connectionstrings tagi altina ekleyebilirsiniz. Bu kod vs Output pneceresinden XPO nun olusturdugu sql sorgularini izlememizi saglayacak.

Örnek connectionstring olarak:

SQLite için

<add name="ConnectionString" connectionString="XpoProvider=SQLite;Data Source=C:\test\test.db3;BinaryGUID=Yes" providerName="System.Data.SQLite" />

MSAccess için

<add name="ConnectionString" connectionString="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\test.mdb;" providerName="System.Data.OleDb" />

MySQL için

<add name="ConnectionString" connectionString="XpoProvider=MySql;server=XXXXXXX;user id=XXXXXXX; password=XXXXXXX; database=XXXXXXX;persist security info=true;CharSet=utf8;" providerName="MySql.Data.MySqlClient" />

Sql Server için

<add name="ConnectionString" connectionString="XpoProvider=MSSqlServer;packet size=4096;data source=XXXXXXX;persist security info=False;initial catalog=XXXXXXX;Connect Timeout=50;User=XXXXXXX;Password=XXXXXXX;Pooling=False;" providerName="System.Data.SqlClient" />

Oracle için

<add name="ConnectionString" connectionString="XpoProvider=Oracle;Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=XXXXXXX)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=XXXXXXX)));User Id=XXXXXXX;Password=XXXXXXX; Pooling = false; Connection Lifetime = 0;" providerName="System.Data.OracleClient" />

Eger XPO nun destekledigi bir veritabani kullaniyorsaniz ve connection stringi bilmiyorsaniz xpo size bu konuda yardimci olabilir.

string connStr = DevExpress.Xpo.DB.MySqlConnectionProvider.GetConnectionString("ip", "kullanici", "parola", "veritabani");
string connStr = DevExpress.Xpo.DB.MSSqlConnectionProvider.GetConnectionString("server", "kullanici", "parola", "veritabani");
string connStr = DevExpress.Xpo.DB.OracleConnectionProvider.GetConnectionString("server", "kullanici", "parola");

Daha sonra veritabaninda tablolara karsilik gelen siniflari olusturmalisiniz. Daha önceki örnekte oldugu gibi ögrenci tablosuyla devam edelim.

Base tablo, bu sinifin amaci her obje içinde standart islemler için kod yazmamak. Burda çok basit bir örnek olarak olusturma ve güncelleme tarihi ekleme var. Bunun disinda olusturan kullanici vb. seyler için kullanilabilir.

[Persistent("T_Ogrenciler")]
[OptimisticLocking(false)]
[DeferredDeletion(false)]
public class Ogrenci : Tablo
{
    public Ogrenci() { }
    public Ogrenci(Session session) : base(session) { }

    [Size(100)]
    public string OgrenciAdi { get; set; }

    [Size(100)]
    public string OgrenciSoyadi { get; set; }

    [Size(15)]
    public string Telefon1 { get; set; }

    [Size(15)]
    public string Telefon2 { get; set; }

    [Size(SizeAttribute.Unlimited)]
    public string Adres { get; set; }

    [NoForeignKey]
    [Association("Belgeler.Ogrenciler", typeof(Belgeler)), DevExpress.Xpo.Aggregated]
    public XPCollection Belgeler
    {
        get { return GetCollection("Belgeler"); }
    }

    public override void AfterConstruction()
    {
        base.AfterConstruction();
    }

    protected override void OnSaving()
    {
        base.OnSaving();
    }

    protected override void OnDeleting()
    {
		base.OnDeleting();
    }
}
[Serializable]
[Persistent("T_Belgeler")]
[OptimisticLocking(false)]
[DeferredDeletion(false)]
public class Belgeler : Tablo
{
    public Belgeler() { }
    public Belgeler(Session session) : base(session) { }

    [Size(250)]
    public string Ad { get; set; }

    [Size(400)]
    [ReadOnly(true)]
    public string DosyaAdi { get; set; }

    [Size(50)]
    [ReadOnly(true)]
    public string Uzanti { get; set; }

    public long Boyut { get; set; }

    [XmlIgnore()]
    [Association("Belgeler.Ogrenciler")]
    public Ogrenci Ogrenci { get; set; }

    protected override void OnSaving()
    {
        if (IsDeleted == false)
        {
        }
    }

    protected override void OnDeleting()
    {
        base.OnDeleting();
    }
}

Burdaki bazi kodlarin açiklamasina geçmeden xpo nun aktif olmasi için gereken son kodu da yazalim. Bu kodu uygulamanin main metodu içine eklenmesi gerekiyor ilk çalisma sirasinda çalisacak ve tekrar çalismayacak bir kod.

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

string ConnStr = Test.Properties.Settings.Default.ConnectionString;

DevExpress.Xpo.Metadata.XPDictionary dictionary = new DevExpress.Xpo.Metadata.ReflectionDictionary();
XpoDefault.DataLayer = XpoDefault.GetDataLayer(ConnStr, AutoCreateOption.DatabaseAndSchema);
DevExpress.Xpo.XpoDefault.Session = new DevExpress.Xpo.Session(XpoDefault.DataLayer);

Application.Run(new Form1());

Bu kodla xpo nun schema yapisini bizim siniflarimizdan alacagini ve connection string bilgisini ayarliyoruz. Proje içindeki nesnelerimiz eger XPO base siniflarindan birinden türemisse xpo tarafindan bu siniflar tablo olarak görülür ve veritabanina tablo açilir. Tablolardaki alanlar ve bu alanlarin türü ise sinifimiz içindeki özelliklerdir (property). Örnegin string bir özellik veritabaninda nvarchar alana karsilik gelir uzunluk belirtilmezse xpo varsayilan olarak nvarchar(100) alan açar.

Eger bir isim belirtilmezse tablo adi sinifimizla ayni isimde açilir farkli bir isim belirtmek için Persistent attiribute kullanilir. Örnekte oldugu gibi. XPO bir sinifin tablo olabilmesi için.

XPBaseObject, XPCustomObject, XPLiteObject yada XPObject türetilmis olmasi gerekiyor. Peki biz hangisinden türetmeliyiz. Iste farklari.

XPBaseObject: Eger key alani vermeniz gerekiyorsa bu nesneden türetilmelidir. XPObject de OID otomatik olarak key atanir ve degistirilemez. Bu nesnede key alani istege bagli atanir. DeferredDeletion desteklemez. OptimisticLocking destekler.

XPCustomObject: Key alani verilebilir.  DeferredDeletion ve OptimisticLocking destekler.

XPObject: Key alani sabittir OID degistirilemez, baska key alani eklenemez. DeferredDeletion ve OptimisticLocking destekler.

XPLiteObject: DeferredDeletion ve OptimisticLocking desteklemez.

Daha genis bilgi için https://documentation.devexpress.com/#CoreLibraries/CustomDocument3311

Bu siniflardan türetip tablo olarak kullanmak istemediginiz siniflar olursa NonPersistent Attribute kullanmalisiniz tablo örneginde oldugu gibi. Bu attribute eger özellikler yani property üzerinde olursa o özellik veritabanina alan olarak açilmaz sadece kod tarafinda kullanilir. Bizim örnegimizde kapattigimiz OptimisticLocking ve DeferredDeletion özellikleri dedir.

DeferredDeletion: XPO tabloya GCRecord adinda bir alan daha ekler. Tüm sorgularini bu alana göre yapar bu alan eger NULL sa kayit gelir degilse kayit silinmis olarak kabul edilir. Yani bir kaydi sildiginizde XPO silmez bu alani doldurur. Istenildiginde geri getirilebilir. Söyle bir kötü tarafi vardir iliskili oldugu alanlar bosaltilir buda kaydi anlamsiz hale getirdigi için çok kullanmiyorum. Örnegin ögrenci tablomuz var bu tablomuzla iliskili Veli tablomuz var eger ögrenci silinirse XPO silmez ancak Veli bilgisini bosaltir.

OptimisticLocking: XPO tabloya OptimisticLocking adinda alan ekler. Kayit güncellenirken bu alan kontrol edilir öncekinden farkliysa kullanicinin kaydi degistirmesine izin verilmez. Çok kullanicili sistemlerde bir kayit üzerinde iki kullanicinin islem yapmasini engelleyen bir yapidir. Örnegin ali kullanicisi ogrenciye select çekti ayni zamanda veli kullanicisida select cekti. OptimisticLocking alaninin degeri 2 di.  Ikiside ogrenci bilgilerini degistirdi ve kaydet dedi.Ali kullanicis kaydetdiginde OptimisticLocking alani degisti. Veli kullanicis kaydederken OptimisticLocking degerini kontrol eder ve 2 olmadigi için kayit baska kullanici tarafindan degistirilmis hatasi firlatir. 

Size: string alanlarda uzunluk belirtmek için kullanilir.  [Size(60)] yada [Size(SizeAttribute.Unlimited)] seklinde kullanilir.

Association: Iki tablo arasinda iliski tanimlamak için foreignkey alan tanimi. Eger bu alan üzerine [NoForeignKey] eklerseniz XPO sql sorgularini LEFT OUTER JOIN olarak olusturur. Aggregated ise bir kayit silindiginde iliskili kayitlarida silmek için.

Indexed bir alana index eklemek için. [Indexed] eklendigi property için veritabaninda index olusturulur.  Uniq index için [Indexed(Unique = true)]. Bir den fazla alani indexlemek için [Indexed("Alan1;Alan2;Alan3;Alan4;Alan5", Unique = false)] gibi.

[DevExpress.Xpo.Indexed(new string[] { "Alan1", "Alan2", "Alan3", "Alan4" }, Name = "IDX_IALANLAR", Unique = true)]

Nesne üzerindeki propery ve veritabani alanini farkli isimlendirmek için, Nesnede property adi public string OgrenciAdi { get; set; } bu ozellik veritabaninda OgrenciAdi olarak olusturulur, farkli bir isimde olusturmak için:

[Persistent("OGRENCI_AD")]
public string OgrenciAdi { get; set; }

Veritabanina baktigimizda T_Ogrenciler tablosunda OgrenciAdi alaninin olmadigini görürüz bunun yerine OGRENCI_AD alanini görürsünüz. [NonPersistent] eklenirse bu alan veritabanina hiç açilmaz.

PersistentAlias ile eklenen alan veritabanina açilmaz bunun yerine yazilan linq yada lambda sorgusu çalisir.

[PersistentAlias("Iif(Sinif is null, '', Sinif.SinifKod)")]
public string SinifKod { get { return Convert.ToString(EvaluateAlias("SinifKod")); } }
[PersistentAlias("Iif(RedMiktar > 0, (Miktar * 100) / RedMiktar, 0)")]
public decimal RedOran
{
    get
    {
        return Convert.ToDecimal(EvaluateAlias("RedOran"));
    }
}
[PersistentAlias("Belgeler[].Single()")]

[PersistentAlias("Belgeler[Durum != 'Pasif'].First()")]
[PersistentAlias("Iif(Sinif is null && Sinif.Ders is null, '', Sinif.Ders.DersAd)")]
[PersistentAlias("Belgeler.Count")]
public int BelgeSayisi
{
    get
    {
        return Convert.ToInt32(EvaluateAlias("BelgeSayisi"));
    }
    set
    {
    }
}
[PersistentAlias("Iif(Len(Adres) = 0, 0, StartsWith(Adres, 'Esen mah'), 1, StartsWith(Adres, 'Dere Mah'), 2, 0)")]
[PersistentAlias("Belgeler.Sum(Boyut)")]

XPO nesnelerinde OnSaving, OnSaved, OnDeleting gibi virtual metodlar vardir. Bu metodlari override ederek kullanabilirsiniz. Bu metodlar veritabanlarindaki triggerlara benzer. 

Son olarak XPO insert, update, delete ve select nasil yapiyoruz ona bakalim.

Insert yani yeni kayit:

using (UnitOfWork wrk = new UnitOfWork())
{
    Ogrenci ogrenci = new Ogrenci(wrk);
    ogrenci.OgrenciAdi = "Ali Veli";
    ogrenci.Telefon1 = "05555555555";
    ogrenci.Telefon1 = "09999999999";
    ogrenci.Adres = "Esen mah. Bey apartmani";
    ogrenci.Aciklama = "Test ogrencisi";
    ogrenci.Aciklama2 = " detaylar";
    ogrenci.Save();


    wrk.CommitChanges();
}

Ögrenci nesnemizi önce Tablo dan sonra XPObject türettik. Tablodaki aciklama alanlarini ve tarih alanlarini aliyor XPObject den ise Oid alanini aliyor. Tarih alanlarina deger girmiyorum, onlar otomatik doluyor. Oid ise Key alan oldugu için XPO otomatik dolduruyor. new le olusturdugumuzda -1 olarak ayarliyor save dedgimizde ise Oid degerini aliyor. save dedigimizde veritabanina yazilmiyor CommitChanges metodu çagrildiginda veritabanina kaydediliyor.

Update, kayit güncelleme:

using (UnitOfWork wrk = new UnitOfWork())
{
    Ogrenci ogrenci = wrk.GetObjectByKey<Ogrenci>(1);
    ogrenci.OgrenciAdi = "Ali Veli";
    ogrenci.Telefon1 = "05555555555";
    ogrenci.Telefon1 = "09999999999";
    ogrenci.Adres = "Esen mah. Bey apartmani";
    ogrenci.Aciklama = "Test ogrencisi";
    ogrenci.Aciklama2 = " detaylar";
    ogrenci.Save();


    wrk.CommitChanges();
}

Her hangi bir kaydi XPO nun update etmesi için mutlaka XPO tarafindan veritabanindan getirilmesi gerekir. Yani GetObjectByKey getirmek yerine new le olusturup Oid alani 1 setlenseydi XPO yinede insert etmeye çalisacakti ve sql hatasi olusacakti. Yani update için select sart. Burda eger yoksa insert varsa update seklinde bir kod gerekseydi.

using (UnitOfWork wrk = new UnitOfWork())
{
    Ogrenci ogrenci = wrk.GetObjectByKey<Ogrenci>(1);
    if (object.ReferenceEquals(ogrenci, null))
    {
        ogrenci = new Ogrenci(wrk);
    }
    ogrenci.OgrenciAdi = "Ali Veli";
    ogrenci.Telefon1 = "05555555555";
    ogrenci.Telefon1 = "09999999999";
    ogrenci.Adres = "Esen mah. Bey apartmani";
    ogrenci.Aciklama = "Test ogrencisi";
    ogrenci.Aciklama2 = " detaylar";
    ogrenci.Save();


    wrk.CommitChanges();
}

Burda varsa update yoksa insert çalisir.

Delete, kayit silme:

using (UnitOfWork wrk = new UnitOfWork())
{
    Ogrenci ogrenci = wrk.GetObjectByKey<Ogrenci>(1);
    ogrenci.Delete();


    wrk.CommitChanges();
}

Eger DeferredDeletion kapatmadiysaniz sildiginiz kayitlar veritabanindan silinmeyecektir. Bu kayitlari silmek için wrk.PurgeDeletedObjects(); metodunu kullanmalisiniz yada [DeferredDeletion(false)] attribute eklemelisiniz. Silinen bu kayitlar veritabaninda kaldigi için tekrar getirilebilir ve SetMemberValue("GCRecord", null); ile silinmemis olur.

Id alanina göre degilde baska bir alana göre kayit getirme:

 using (UnitOfWork wrk = new UnitOfWork())
{
    Ogrenci ogrenci = wrk.FindObject<Ogrenci>(CriteriaOperator.Parse("OgrenciAdi = ? And Telefon1 = ?", "adem", "03333333333"));
    if (object.ReferenceEquals(ogrenci, null))
    {
        ogrenci = new Ogrenci(wrk);
    }
    ogrenci.OgrenciAdi = "Ali Veli";
    ogrenci.Telefon1 = "05555555555";
    ogrenci.Telefon1 = "09999999999";
    ogrenci.Adres = "Esen mah. Bey apartmani";
    ogrenci.Aciklama = "Test ogrencisi";
    ogrenci.Aciklama2 = " detaylar";
    ogrenci.Save();


    wrk.CommitChanges();
}

CriteriaOperator kullanarak kaydi buluyoruz. Burda sakincali bir durum var nesnedeki alanlari string içinde belirttigimiz için sakincali. Nesnedeki alanlarda degisiklik olursa derleyici bunu görmeyecek ve çalisma zamaninda hataya neden olacaktir. Bunu engellemenin çesitli yöntemleri var tabi lambda kullanarak sorgu hazirlanabilir. Son olarak tüm kayitlari getirmeye bakalim.

Select kayitlari sorgulama:

using (UnitOfWork wrk = new UnitOfWork())
{
    XPCollection<Ogrenci> ogrenciler = new XPCollection<Ogrenci>(wrk, CriteriaOperator.Parse("Adres LIKE 'Esen%'"),
        new SortProperty[]{new SortProperty(){PropertyName="OgrenciAdi"},
            new SortProperty(){PropertyName="Adres",Direction = DevExpress.Xpo.DB.SortingDirection.Descending}});

    if (ogrenciler.Count > 0)
    {
        foreach (Ogrenci ogrenci in ogrenciler)
        {
            Trace.WriteLine(new StringBuilder().AppendFormat("OgrenciAdi:{0},Adres:{1}\n", ogrenci.OgrenciAdi, ogrenci.Adres));
        }
    }

}

Linq kullanarak sorgulama:

using (UnitOfWork wrk = new UnitOfWork())
{
    var ogrenciler = from q in new XPQuery<Ogrenci>(wrk)
                     where q.OgrenciAdi.IndexOf("Ali") > -1
                     orderby q.OgrenciAdi, q.Adres descending
                     select q;


    if (ogrenciler.Count() > 0)
    {
        foreach (Ogrenci ogrenci in ogrenciler)
        {
            Trace.WriteLine(new StringBuilder().AppendFormat("OgrenciAdi:{0},Adres:{1}\n", ogrenci.OgrenciAdi, ogrenci.Adres));
        }
    }

}

XPQuery nin farkli bir güzelliginden de bahsederek yaziyi bitirmek istiyorum. Bir kaydin sadece bir alani gerekiyorsa XPCollection yada FindObject ile çektiginizde tablodaki tüm alanlara select çekilir. Size gereken sadece bir alan olsa bile tablodaki tüm alanlar getirilir. XPQuery ise linq kullanarak istediginiz alanlara select çekebilirsiniz. Buda performans açisindan önemlidir.

Umarim yardimci olabilmisimdir. Soru yada önerilerinizi bekliyorum.

 

 

 

 

 
Loading