Programlama Dili ve Yazılım Tasarımı

Ebat: px
Şu sayfadan göstermeyi başlat:

Download "Programlama Dili ve Yazılım Tasarımı"

Transkript

1 C# Programlama Dili ve Yazılım Tasarımı Cilt 2. İleri Düzey Programlama Ahmet KAYMAZ Yüksek (MSc)Bilgisayar Mühendisi Papatya Yayıncılık Eğitim İstanbul, Ankara, İzmir ve Adana

2 C# Programlama Dili ve Yazılım Tasarımı PAPATYA YAYINCILIK EĞİTİM Mart 2011 EĞİTİM BİLGİSAYAR SİS. SAN. VE TİC. A.Ş. Ankara Cad. Prof. F. Kerim Gökay Vakfı İşhanı No:31/3 Cağaloğlu/İstanbul Tel : , Faks: e-posta : bilgi@papatya.gen.tr Web : Dağıtım : Cağaloğlu (212) C# Programlama Dili ve Yazılım Tasarımı Ahmet KAYMAZ Cilt 2: İleri Düzey Programlama 2. Basım Mart 2011 Yayın Danışmanı Üretim Pazarlama Satış Sayfa Düzenleme Kapak Tasarım Basım ve Ciltleme : Dr. Cengiz UĞURKAYA : Olcay KAYA : Ziya ÇÖLKESEN : Mustafa DEMİR : Papatya - Kelebek Tasarım : Papatya - Kelebek Tasarım : Altan Basım Ltd.(Sertifika No: 11968) / İstanbul Bu kitabın her türlü yayın hakkı Papatya Yayıncılık Eğitim A.Ş. ye aittir. Yayınevinden yazılı izin alınmaksızın alıntı yapılamaz, kısmen veya tamamen hiçbir şekil ve teknikle ÇOĞALTILAMAZ, BASILAMAZ, YAYIMLANAMAZ. Kitabın, tamamı veya bir kısmının fotokopi makinesi, ofset gibi teknikle çoğaltılması, hem çoğaltan hem de bulunduranlar için yasadışı bir davranıştır. Kaymaz, Ahmet. C# Programlama Dili ve Yazılım Tasarımı / Ahmet Kaymaz - İstanbul: Papatya Yayıncılık Eğitim, xii, 400 s.; 24 cm. Kaynakça ve dizin var. ISBN Sertifika No: NET 2. ADO.NET 3. C# 4. VB.NET 5. SQL Server I. Title. II

3 Papatya Yayıncılık Eğitim Bu kitabımı, değerli eşime ithaf ediyorum. III

4 C# Programlama Dili ve Yazılım Tasarımı Teşekkür Hayatımın her karesinde sevgisini ve desteğini yanımda hissettiğim aileme, çalışmama katkıda bulunma nezaketini gösterdiği için Murat ÖNGÜDÜ ye, deneyimlerinden yararlandığım Serdar PEKÜN e, manevi desteklerinden dolayı dostlarıma, teknik önerilerinden dolayı sayın Prof. Dr. Esat HAMZAOĞLU na, sayın Ege KİPMAN a ve sayın Ahmet ACAR a, yardım ve katkılarıyla beni yönlendiren Papatya Yayıncılık Eğitim yayın danışmanı ve kitabımın editörü sayın Dr. Rifat ÇÖLKESEN e ve yayınevinin çalışanlarına şükranlarımı sunarım. Bu kitap sizlerin katkısıyla daha da iyi oldu. Ahmet KAYMAZ IV

5 Papatya Yayıncılık Eğitim İçindekiler Önsöz 11 Cilt 1: Programlama İlkeleri Bölüm 1..NET Framework Temel Kavramları 15 Bölüm 2. C# Programlama Diline Giriş 37 Bölüm 3. Veri Türleri ve Değişkenler 49 Bölüm 4. Operatörler 71 Bölüm 5. Kontrol Deyimleri 89 Bölüm 6. Diziler 105 Bölüm 7. Nesne Yönelimli Programlama 119 Bölüm 8. Statik Üyeler & Harici Sınıflar 143 Bölüm 9. OOP'nin Temel İlkeleri 159 Bölüm 10. Yapı, Numaralandırma ve Öznitelik 189 Bölüm 11. Aykırı Durum Yönetimi 203 Bölüm 12. NET te Koleksiyon Nesneleri 211 Bölüm 13. Delegeler ve Olaylar 257 Bölüm 14..NET te G/Ç Kullanımı 277 Bölüm 15. String/Sözce Türü ve Karakter Biçimlendirme İşlemleri 297 Bölüm 16. Operatörlerin Yeniden Yüklenmesi 325 Bölüm 17. Yansıma (Reflection) 335 Bölüm 18. Emniyetsiz Kod ve İşaretçiler 347 Bölüm 19. Çok Parçacıklı (Multithreading) Programlama 359 Bölüm 20. Bütünleştirilmiş (Assembly) Kod Türleri ve Kullanımı 383 Bölüm 21. Windows Form Uygulamaları 399 Cilt 2: İleri Düzey Programlama Bölüm 22. ADO.NET Mimarisi Veri Erişim Bileşenleri ADO.NET Nedir? ADO.NET Mimarisi Özet Sorular 489 V

6 C# Programlama Dili ve Yazılım Tasarımı Bölüm 23. ADO.NET Bağlantılı Sınıflar DbConnection Nesnesi Bağlantı Cümlesinin Saklanması DbCommand Nesnesi DbCommand Nesnesinin Yürütülmesi DbDataReader Nesnesi DbDataReader Sınıfının Üyeleri DbParameter Nesnesi DbTransaction Nesnesi ADO.NET 2.0 Transaction (İşlem-bilgi)Yönetimi Savepoint Kavramı Özet Sorular 526 Bölüm 24. ADO.NET Bağlantısız Sınıflar DbDataAdapter Nesnesi DataSet ve DataTable Nesneleri DataTable Nesnesinde Kayıt Arama ve Filtreleme Değişen Kayıtlar Hakkında Bilgi Almak Tablodaki Değişiklikleri İzlemek DataTable ile XML Okuma ve Yazma DataTable Olayları DataSet Nesnesinin Üyeleri LoadOption Numaralandırma DataTable Merge() Yordamı DataView Nesnesi Tablodan TOP N Kayıt Almak DataViewManager Nesnesi DataTableReader Nesnesi DataRelation Nesnesi Kısıtlama Kullanımı Hesaplanmış Kolon Oluşturulması Özet Sorular 577 Bölüm 25. DataSet Nesne Türleri Typed Dataset Oluşturulması Özet Sorular 590 Bölüm 26. DataAdapter Kullanımı Tablo Eleştirilmesi Fill() ve FillSchema() Yordamları Update() Yordamı DataAdapter Olayları Identity Bilgisinin Alınması 606 VI

7 Papatya Yayıncılık Eğitim AcceptChangesDuringFill ve AcceptChangesDuringUpdate Özellikleri Concurrency Violation Sorunu (Eş Zamanlı Uyumsuzluk) DbCommandBuilder Nesnesi DataAdapter UpdateBatchSize Özelliği Özet Sorular 625 Bölüm 27. İleri ADO.NET Konuları ADO NET te Bağlantı Havuzu ADO.NET te MARS Özelliği DataSet ile DataReader Arasındaki Fark Asenkron Veri Erişimi Havuz (Pooling) Modeli Bekleme (Wait) Modeli Geri-Çağırma (CallBack) Modeli SqlDependency ve SqlBulkCopy Nesneleri Data Provider Bağımsızlığı (DbProviderFactory) Özet Sorular 664 Bölüm 28. XML Nedir? XML Nedir? XML ve DTD Kullanımı DTD İçerisinde Elementlerin Tanımlanması DTD İçerisinde Özniteliklerin Tanımlanması DTD İçerisinde Varlıkların Tanımlanması XML İsim-uzayı XML Şeması Şemada Element ve Öznitelik Tanımlama Kullanıcı Tanımlı Veri Türleri XML Şema Belirteçleri (Adverb) XML Biçimlendirme Dili (XSL) XML Adresleme Dili (XPath) Özet Sorular 692 Bölüm 29..NET Framework te XML Programlama Kanal (Stream) Tabanlı XML İşlemleri XmlReader Sınıfının Kullanımı XmlReader ile Belgenin Doğrulanması XmlWriter Sınıfının Kullanımı DOM Tabanlı XML İşlemleri XmlDocument Sınıfının Kullanımı XML Dönüştürme (XslTransform Sınıfı) NET te XPath Uygulaması 709 VII

8 C# Programlama Dili ve Yazılım Tasarımı DataSet ve XML İlişkisi DataSet i XML Olarak Yazdırmak XML Belgesini DataSet e Yüklemek XmlDataDocument Sınıfı İlişkili Tablolar ve XML NET Serileştirme İşlemleri Özet Sorular 726 Bölüm 30. C# 3.0 Yenilikleri Bilinçsizce Türlendirilmiş Değişkenler Anonim/İsimsiz Veri Türleri Nesne ve Koleksiyon İlklendirme Genişletme Yordamları Lambda İfadeleri Parçalı Yordam Yeni Nesil Veri Programlama Modeli (ADO.NET 3.5) Varlık-İlişki Modellemesi Nesne-İlişki Haritalama ADO.NET Entity Framework Özet Sorular 744 Bölüm 31. LINQ Sorgulama Yöntemi Dil ile Bütünleşik Sorgulama LINQ Sorgu Sözdizimi ve Yürütülmesi LINQ Operatörleri Kısıtlama Operatörleri Seçme Operatörleri Bölümleme Operatörleri Birleştirme Operatörleri Ulama Operatörleri Sıralama Operatörleri Gruplama Operatörleri Küme Operatörleri Dönüştürme Operatörleri Eşitlik Operatörleri Eleman Operatörleri Üretim Operatörleri Ölçüm Operatörleri Gruplama Fonksiyonu Operatörleri İleri LINQ Örnekleri Dataset için LINQ Özet Sorular 786 VIII

9 Papatya Yayıncılık Eğitim Bölüm 32. SQL ve XML için LINQ Kullanımı SqlMetal Aracı O/R Tasarım Aracı Veri Çekme Sorguları Veri Güncelleme Sorguları DLINQ de SQL İfadelerinin Çalıştırılması Stored Procedure ve Function Kullanımı DLINQ de Transaction Desteği DataContext Sınıfının Veritabanı Yordamları XML için LINQ (XLINQ) XML Belgesini XLINQ ile Sorgulamak Alt veya Üst Düğümleri Seçme XML Belgesinde DML Örnekleri Özet Sorular 827 Ek A:.NET Framework Mimarisi ve Bileşenleri 829 A.1. Framework Class Library (FCL)'nin Önemli İsim-uzayları 829 A.2. Dosya Formatı (Portable Executable - PE) 830 A.3. NGEN mi JIT mi? 833 A.4..NET Yürütme Modeli (CLR Execution Model) 834 A.5. Bellek Yönetimi ve Çöp Toplama 837 A.5.1. Çöp Toplama Algoritması 843 A.5.2. Finalization 846 A.5.3. Finalization ve Çöp Toplayıcı 847 A.5.4. Çöp Toplayıcının Programlaması 848 A.5.5. Çöp Toplama Performansı 851 A.5.6. Güçlü ve Zayıf Referanslar 854 Kaynakça 855 Dizin 859 IX

10 C# Programlama Dili ve Yazılım Tasarımı Cilt 1 'de düzeltme Sayfa 48 ve 51'de "Tek-duyarlı (Single) türü" ifadesindeki Single sözcüğü float olarak değiştirilecek. Sayfa 55'de "şeklinde tanımlama yapıldığı zaman derleyici "The variable 'Pi' is assigned but its value is never used" hata mesajını verir." ifadesindeki hata mesajı "A const field requires a value to be provided" ile yer değiştirilecek. Aynı paragraftaki ikinci hata mesajı da "The left-hand side of an assignment must be a variable, property or indexer" olarak değiştirilmelidir. Sayfa 57'de "3.4. Veri Türleri Arasında Dönüşüm" konusundaki tabloda Integer-> int olacak ve SShort->short olacak, single ->float olmalı. Sayfa 77'de "Atama Operatörleri" bölümündeki operatör tablosundaki sondan 2'nci satır silinecek; ve hemen onun altında ele alınmış olan "=" operatöründeki örnek VB.NET'a ait kalmış. Bu örnek tablonun ilk kolonu silinecek. 79'uncu sayfada "/=" operatörünün açıklaması yanlış olmuş. Oradaki satır şu şekilde değiştirilecek. " X /= Y şeklindeki tanımlamada X in değeri Y e bölünür ve sonuç X e atanır.". Sayfa 111'de " System.Array Özellik ve Yordamları" ayrıtındaki "GetValue()" başlığında VB.NET örneği kalmış; 111'inci sayfasındaki ilk tablo silinecek. Sayfa 325'de "Operatörlerin Yeniden Yüklenmesi" bölümünde "C# dilinde yalnızca aşağıdaki operatörler yeniden yüklenebilir:" ayrıtının altındaki operatörler C#'ın değil VB.NET'in operatörleridir. Doğrusu şöyle olacak: C# da yalnızca aşağıdaki operatörler yeniden yüklenebilir: Binary operatörler : +, -, *, /, %, &,, <<, >> Unary operatörler : +, -,!, ~, ++, --, true, false İlişkisel operatörler : ==,!=, <, >, <=, >= Sayfa 330'da "Bu şekilde yapılan atama hatalı olur. Derleyici, Value of type 'BirSayi' cannot be converted to 'String' hata mesajını verir." cümlesi silelim. Bu cümle VB.NET'e ait. Sayfa 415'de Windows bölümünde "Bu yordam form kontrolüne ait MouseDouble- Click olayını Handles komutu aracılığıyla Form2_MouseDoubleClick isimli bir yordama bağlamış olur. Yordamın içeriğini aşağıdaki gibi düzenleyelim." paragrafı silinecek. X

11 Papatya Yayıncılık Eğitim Önsöz.NET Framework hakkındaki en son güncel bilgileri içeren bu kitabın Microsoft yazılım platformunu öğrenmek isteyen yazılımcılara ve sektöre girmek üzere hazırlanan öğrenci arkadaşlarımıza yol gösterici olacağını düşünüyorum. Türkçe teknik içerik yazılım sektöründe önemli bir eksiklik olarak durmaktadır. Bu çalışmanın da böylesi önemli bir açığı da kapatacağına inanıyoruz. Kitabın içeriğinin sadece teorik bilgi ile sınırlı kalmaması ve yazarın profesyonel hayattaki deneyimlerini de okuyucu ile paylaşıyor olması zengin içeriğine daha da fazla değer katmaktadır. Bu kitapla Ahmet KAYMAZ'ın sektörümüze önemli bir katkı sağladığını düşünüyor ve kendisini böylesi ayrıntılı ve titiz çalışması dolayısı ile kutluyorum. Mehmet EMRE Microsoft Türkiye XI

12 C# Programlama Dili ve Yazılım Tasarımı Terimler İngilizce Türkçe Türkçe İngilizce Abstract Soyut Anahtar sözcük Keyword Array Dizi Arabirim/Arayüz Interface Assembly Bütünleştirilmiş-kod / b-kod Bütünleştirilmiş-kod/ b-kod Assembly Class Sınıf Çok-şekillilik Polymorphism Constructor Yapıcı Çöp Toplama Garbage Collection Debug Hata Ayıklama Delege/Temsilci Delegate Delegate Delege/Temsilci Deyim Statement Destructor Yıkıcı Dizi Array Event Olay Emniyetsiz Unsafe Garbage Collection Çöp Toplama Geçersiz Kılınmış Override Inheritance Kalıtım/Miras Hareket, İşlem-bilgi Transaction Interface Arabirim/Arayüz Hata Ayıklama Debug Keyword Anahtar sözcük İsim-uzayı Namespace Metadata Üst-veri İşaretçi Pointer Method Yordam Kalıtım/Miras Inheritance Namespace İsim-uzayı Kanal Stream Object Nesne Kaynak Resource Overload Yeniden Yüklenme Kod Parçacığı Thread Override Geçersiz Kılınmış Nesne Object Property Özellik Olay Event Pointer İşaretçi Özellik Property Polymorphism Çok-şekillilik Sınıf Class Reflection Yansıma Soyut Abstract Resource Kaynak Sözce String Statement Deyim Üst-veri Metadata Stream Kanal Yansıma Reflection String Sözce Yapı Struct Struct Yapı Yapıcı Constructor Thread Kod Parçacığı Yeniden Yüklenme Overload Transaction Hareket, İşlem-bilgi Yıkıcı Destructor Unsafe Emniyetsiz Yordam Method XII

13 22. ADO.NET Mimarisi Günümüz uygulamalarında en çok yazılan alanların başında hiç şüphesiz veritabanı işlemleri gelmektedir. Bu işlemlerin temel amacı, kullanıcı/müşteri bilgileri depolamak, verilere daha hızlı ulaşmak, veri arama, indeksleme ve istatistiksel analiz gibi kazançları sağlamaktır. Veritabanı işlemlerinin temelinde birisi veri (data) diğeri veritabanı (database) olmak üzere iki kavram yatar. Veri sözcüğü her türlü sayısal, mantıksal ve sözel değeri temsil eder. Yani, yalnızca sayılar değil ses, resim ve görüntü gibi nesneler de veri olabilir. Veritabanı ise birbiriyle ilişkili veya ilişkisiz verilerin belirli bir tarzda bir arada biriktirilmesidir. Verilerin biriktirilme tarzı, o veri tabanının kullanım amacı, çalışma mantığı, performans ve güvenliğiyle ilgilidir. Bu tarzın sonucunda network, hiyerarşik, ilişkisel veya nesnesel veritabanı modelleri tasarlanır. Veritabanı modeli temelde veritabanının kendisini ve bu veritabanını tanımlayan şemayı barındırır. Veritabanının şeması veri türleri, ilişkileri ve kuralları gibi veritabanı yapısını oluşturan standartları bildirir. Bir veritabanının, birbiriyle ilişkili verileri tekrara yer vermeden, çok amaçlı kullanıma izin verecek şekilde depolaması onun iyi bir veritabanı olduğunun göstergesidir. Bir veritabanını kurgulamak, oluşturmak, saklamak, çoğaltmak, güncellemek veya yönetmek için kullanılan programlara Veritabanı Yönetim Sistemi-VTYS (Database Management System-DBMS) denilir. Günümüzde en çok kullanılan veritabanı sistemleri IBM DB2, Oracle, MSSQL, MySql, Sybase ve Firebird yazılımlarıdır. Veritabanı sistemlerinde üç tür dil kullanılır: DDL (Data Definition Language-Veri Tanımlama Dili) DML (Data Manipulation Language-Veri Düzenleme Dili) DCL (Data Control Language-Veri Kontrol Dili) Veritabanı yönetim sistemlerinde bu dillere ait deyimler SQL (Structured Query Language-Yapısal Sorgulama Dili) dilinde toplanmıştır. SQL dili bir programlama dili olmayıp VTYS lerde veri sorgulama, düzenleme ve yönetme işlemleri için kullanılır. SQL dili, tüm veritabanı yönetim sistemlerinde standart bir dil olsa da bazı sistemler bu dili biraz daha geliştirip özelleştirmiştir. Bu amaçla Microsoft

14 482 C# Programlama Dili tarafından, SQL Sunucu sisteminde kullanılmak üzere T-SQL dili aynı şekilde, Oracle tarafından Oracle veritabanı sistemlerine özel PL/SQL dili geliştirilmiştir. Verinin işlenmiş ve bir anlam ifade eden hali bilgi (information) olarak tanımlanır. Bugün birçok sektörde Tera Byte ları aşan veriler depolanmaktadır. Fakat bu verilerin yarar ve kazanç getirebilmesi için doğru bir şekilde analiz edilmesi ve işlenmesi gerekir. Bu işleme Veri Madenciliği (Data Mining) denilir. Bazı işalanlarında veritabanı programlayıcısından ziyade bu tür çözümler daha öncelikli olmaktadır. Bir veritabanı verilerin depolandığı satır ve sütunlardan oluşan table (tablo) denilen yapıları barındırır. Tablonun satırları ve sütunları vardır; sütununa alan/kolon da (field) denilir Veri Erişim Bileşenleri Kullanıcı/istemciler, ancak, bir veritabanı arabirimi aracılığıyla veritabanlarıyla haberleşir. Veritabanlarının karmaşık ve riskli yazılımlar olmasından dolayı bir kullanıcının doğrudan veritabanıyla konuşması doğru değildir. Kullanıcı Uygulaması Veritabanı Arabirimi Veritabanı ODBC, DAO, RDO, OLE DB, ADO, ADO.NET ISAM Dosyası Şekil Veri erişim modeli Veritabanı yönetim tarihçesini incelediğimizde 90 lı yılların başında C ve COBOL tabanlı uygulamalardan SQL tabanlı veritabanı sistemlerini sorgulamak ve sorgu sonucu dönen verileri işlemek amacıyla The Open Group tarafından bir erişim standardının (Call Level Interface-CLI) geliştirildiğini görmekteyiz. Daha sonra farklı firmalar tarafından bu CLI standardına uygun arabirimler geliştirildi. Veri sağlayıcısı olan bilinen bu arabirimlerden en yaygın olanı ODBC (Open Database Connectivity-Açık Veritabanı Bağlantısı) birimidir. Windows ortamında veritabanı bağlantı standartı olan ODBC, herhangi bir uygulama içerisinden değişik veritabanı yönetim sistemlerine erişmek için kullanılan kütüphaneler ve sürücüler topluluğudur (odbc32.dll). Her veritabanı sisteminin kendine özgü ODBC sürücüsü bulunur. Papatya Yayıncılık Eğitim

15 ADO.NET Mimarisi 483 Hangi veritabanı sistemi üzerinde çalışılacaksa ODBC üzerinden o sistemin sürücüsü (köprüsü) kullanılır. Aynı şekilde Java dünyasında Java uygulamalarıyla veritabanı sistemleri arasında sürücülük yapan JDBC (Java Database Connectivity) standardı geliştirilmiştir. Oracle veritabanı sistemiyle iletişim kurabilmesi için de Oracle tarafından OCI (Oracle Call Interface) API si geliştirilmiştir. Kullanıcı Uygulaması ODBC Sürücü Yönetimi Sürücü Sürücü Veritabanı ISAM Dosyası Şekil ODBC arabirim modeli Bir kullanıcının bir veritabanı sistemine ODBC ile bağlanabilmesi için kullanıcı üzerinde o veritabanı sistemine ait veri kaynak tanımı yapılmalıdır. Her işletim sisteminin kendine özgü ODBC sürücü tanımlama yöntemi vardır. Windows işletim sistemine sahip bir sistemde yüklü olan ODBC sürücülerini Control Panel» Administrative Tools» Data Sources ODBC programındaki Drivers sekmesinde görebiliriz. Microsoft, veritabanı işlemleri için Jet Database Engine adı altında çeşitli bileşenler geliştirdi. Bu bileşenlerin ilki Access ve Visual Basic uygulamalarının ilk uyarlamalarında geliştirilen DAO (Data Access Objects) birimidir. DAO, VB uygulamalarının Jet Engine aracılığıyla doğrudan Access veritabanına erişmesine Bölüm 22

16 484 C# Programlama Dili olanak sağladı. Aynı şekilde diğer veritabanı sistemleri için ODBC iletişimi kullandı. Kullanıcı Uygulaması DAO Access Jet Veritabanı Makinası MDB Dosya ODBC Sürücü Yönetimi Sürücü Sürücü RDBMS Veritabanı ISAM Dosya Şekil DAO arabirim modeli Fakat bu teknolojinin beklenilen performansı sağlayamaması ve sadece tekkatmanlı (1-tier) uygulamayı destekliyor olması RDO (Remote Data Objects) biriminin çıkarılmasını sağlamıştır. RDO, Visual Basic 4.0 (Enterprise Edition) ile birlikte, ODBC teknolojisinin üst katmanında yerleşik olarak sunuldu. RDO, uygulamaların ODBC aracılığıyla ilişkisel veritabanı sistemleriyle (Relational DBMS) konuşmasına olanak sağlamış SQL Sunucu, Oracle gibi veritabanlarına erişmeyi biraz daha hızlandırmıştı. DBMS lere uzaktan erişmesiyle iki-katmanlı (2-tier) uygulama mimarisini destekliyordu. Papatya Yayıncılık Eğitim

17 ADO.NET Mimarisi 485 Özellikle veritabanı sistemlerinin gelişiyor olması ve uygulamalarda yoğun kullanılıyor olması Microsoft u ODBC ye alternatif yeni bir sağlayıcı geliştirmeye zorlamıştır. OLE-DB olarak tanımlanan bu yeni teknoloji aracılığıyla farklı modellerdeki (relational-rdbms ve nonrelational) veri kaynaklarına daha az bellek ve disk harcanarak erişilmekteydi. Buradaki nonrelational kavramından kasıt MySQL, Access, Paradox, Dbase, Active Directory veya CSV format gibi veritabanı modeleridir. Uygulama geliştiriciler için veritabanı işlemlerini hızlandırmış olan OLE-DB aracı, ODBC ye göre dosya sistemi, metin ve grafik gibi daha fazla veri türünü desteklemektedir. ODBC den farklı olarak API yerine COM tabanlı çalışmaktadır. Microsoft, kısa bir süre sonra DAO ve RDO bileşenlerini değiştirip uygulamaların farklı veri kaynaklara erişmesine izin veren ve OLEDB veri sağlayıcısını kullanan ADO (ActiveX Data Objects) bileşenini geliştirdi. Tek ve daha fazla uygulama katmanını (1 to n-tier) destekleyen ADO bileşeni, DAO/RDO dan hem kullanımı daha kolay hem de daha güçlü işlemler yapabilmektedir. ADO, Web uygulamaları dahil olmak üzere kullanıcı/sunucu uygulamalarını geliştirmek için birçok özellik sunmaktadır. Ayrıca OOP yazılım yaklaşımını destekliyor olması da önemli bir özelliktir. ADO nesne modeli altı temel nesne içermektedir: Connection object Recordset object Command object Error object Field object Parameters collection Microsoft, bu paragraflarda anlatılan arabirim ve sağlayıcıları MDAC (Microsoft Data Access Components-MS Veri Erişim Bileşenleri) adı altında program grubunda toplamıştır. MDAC in üst sürümlerinde eski veri erişim arabirimleri bulunmamaktadır. Örneğin MDAC 2.8 de Microsoft Jet, Microsoft Jet OLE DB Sağlayıcısı, Desktop Database Drivers ODBC Sürücüsü veya Visual FoxPro ODBC Sürücüsü bulunmamaktadır. Bu bölümde tanımlarını verdiğimiz veri erişim bileşenlerini Şekil-22.4'te gösterildiği gibi ilişkilendirebiliriz. Bölüm 22

18 486 C# Programlama Dili Kullanıcı Uygulaması DAO RDO ADO JET ODBC OLE DB İlişkisel Veri Kaynağı İlişkisel- Olmayan Veritabanı Şekil Veri erişim arabirimleri ADO.NET Nedir? Microsoft firmasının veri erişim konusundaki son teknolojisi ADO.NET (ActiveX Data Object for.net) bileşenidir. Bu bileşen ADO nun.net Framework mimarisine uyarlanmış ve yeni özellikler kazandırılmış sürümü olup uygulama ve veri kaynakları arasındaki bağlantıyı yöneten birçok sınıf içermektedir. Yüksek performans ve uyumluluk sağlayan ADO.NET, özellikle çok katmanlı uygulama modelini desteklemesi ve XML ile bütünleştirilmesiyle günümüz kurumsal ihtiyaçları karşılayacak niteliktedir. ADO.NET in en önemli özelliği bağlantısız veri erişim modelini destekliyor olması ve programcı açısından veritabanı işlemlerini basitleştirmiş olmasıdır. ADO.NET, ADO dan gelme Connection ve Command nesnelerini desteklediği gibi DataSet, DataReader ve DataAdapter gibi yeni nesneler sunmaktadır. Papatya Yayıncılık Eğitim

19 ADO.NET Mimarisi 487 ADO.NET sınıfları System.Data.dll kütüphanesinde bulunmaktadır. Ayrıca ilişkili olduğu XML sınıfları System.Xml.dll kütüphanesinde bulunur. Bu yüzden, System.Data isim-uzayının kullandığı projelere bu.dll dosyalarının da referans olarak eklenmesi gerekir. System.Data aşağıdaki isim-uzayları içerir: Microsoft.SqlServer.Server System.Data System.Data.Common System.Data.Odbc System.Data.OleDb System.Data.Sql System.Data.SqlClient System.Data.SqlTypes System.Xml System.Data, ADO.NET in veri kaynağından bağımsız nesnelerini (DataTable, DataSet, DataRelation, DataView) içerir. System.Data.SqlClient, Microsoft SQL Sunucu (7.0 ve üstü) ile çalışmak için gerekli nesneleri (SqlCommand, SqlConnection, SqlParameter, SqlDataAdapter, SqlDataReader) içerir. System.Data.Odbc, ODBC ile veritabanlarına bağlanmak için gerekli nesneleri içerir. System.Data.OleDbClient, OLEDB veri sağlayıcısını kullanan veri kaynakları (SQL Server 7.0 ve altı, Oracle, Excel, Access) üzerinde çalışmak için gerekli nesneleri içerir. Bu nesneler SqlClient altındaki nesneler de aynı isim ve yapıya sahiptir. OleDbClient ve Odbc bileşenlerini, SQL sunucu erişimi için kullanabiliriz; fakat SQL sunucunun mimarisine uygun olarak geliştirilmiş SqlClient bileşenini tercih etmemiz işlemleri hızlandırır ve SQL sunucusuna özgü işlemleri yapabilme fırsatı elde etmiş oluruz. Aynı şekilde Microsoft tarafından geliştirilmiş System.Data.OracleClient kütüphanesi de (System.Data.OracleClient.dll) Oracle sistemine özgü işlemler için kullanılır. Bu bileşenin de sorgu çalıştırma ve sorgu sonucu yönetme nesneleri (OracleConnection, OracleDataAdapter, OracleCommand ve OracleDataReader) bulunur ADO.NET Mimarisi ADO.NET veritabanı erişim ve yönetimi için iki temel bileşen içerir:.net Framework veri sağlayıcı (.NET Framework Data Provider) VeriKümesi (DataSet) Bu hiyerarşik sınıflandırmayı bağlantılı ve bağlantısız şeklinde de yapabiliriz. Aşağıdaki diyagram ADO.NET in mimarisini göstermektedir. ADO.NET in yeni Bölüm 22

20 488 C# Programlama Dili sürümlerinde mimarinin değişmesinden ziyade yeni yordam ve özellikler eklenmiştir. Şekil ADO.NET mimarisi.net Framework veri sağlayıcı, veritabanı sistemleriyle bağlantı kurmak ve veri yönetimini hızlı bir şekilde gerçekleştirmek üzere tasarlanmıştır. Bu bölümdeki nesneler önceki sayfada bahsettiğimiz veri sağlayıcılara (OLEDB, ODBC, SQL Server, Oracle) bağımlıdır. OLE DB ve ODBC standartlarına ait.net Framework Data Provider için MDAC 2.6 veya üst sürümlerinin kurulmuş olması gerekir. Connection nesnesi farklı veri kaynaklarına bağlanmayı destekler. Command nesnesi veritabanından veri çekmek, veritabanındaki veriyi düzenlemek, stored procedure çalıştırmak gibi sorgu işlemleri için kullanılır. DataReader nesnesi veri kaynağından hızlı bir şekilde veri aktarımı gerçekleştirir. DataAdapter nesnesi veri kaynağı ile DataSet nesnesi arasında köprü işlevi görür. DataAdapter DataSet nesnesini doldurmak veya bu nesnedeki veri değişikliklerini veri kaynağına yansıtmak üzere veri kaynağında çalıştırılacak SQL sorgularını Command nesneleri aracılığıyla gerçekleştirir. DataSet bileşeni herhangi bir veri kaynağına bağımlı olmadan veri erişimi ve yönetimi sağlar. Her türlü veri kaynağını destekliyor olması kullandığı XML veri yapısına borçludur. Aynı şekilde veriyi yerele çekip veri yönetimi yapabiliyor olması da önemli yeteneğidir. DataSet nesnesi teknik olarak bir ve daha fazla DataTable veri kümelerinden oluşur. DataTable, veritabanı sisteminden bildiğimiz kolon ve satırlara sahip, gerektiğinde birincil anahtar (primary key) ve ikincil anahtar (foreign key), kısıt (constraint) ve ilişki (relation) gibi nesneleri içerebilen nesnelerdir. Papatya Yayıncılık Eğitim

21 ADO.NET Mimarisi 489 Uygulamalarımızda DataReader mi yoksa DataSet mi tercih etmeliyiz sorusunun cevabı uygulamada yapılmak istenen işleme bağlıdır. Aşağıdaki durumlarda DataSet nesnesi tercih edilmelidir: Veriyi topluca daha hızlı güncellemek için yerelde önbelleğe (cache) almak. Eğer sadece okuma amaçlı veri sorgulanıyorsa DataReader tercih edilmelidir. Veriyi XML Web servisinden çekmek. Birbirleriyle ilişkili çoklu veri kaynaklarını kullanarak birden fazla Windows kontrolünü ilişkilendirmek. Fakat performans ve kaynak kullanımı açısından DataReader nesnesi DataSet nesnesine göre daha önceliklidir. DataReader aracılığıyla sadece ileri-doğru (forward-only) veya salt-okunur (readonly) tarzı işlem yapılabilir. Yani veriler arasında sadece ileri-doğru ilerlenebilir ve alınan veriler üzerinden herhangi bir güncelleme yapılamaz. ADO.NET mimarisi bağlantılı ve bağlantısız olmak üzere iki bölüme ayrılabilir. Önceki diyagramda sol taraf bağlantılı sınıfları, sağ taraf bağlantısız sınıfları göstermektedir. Diğer sınıflardan farklı olarak DataAdapter nesnesi hem bağlantılı hem de bağlantısız tarafta rol oynar ve bu iki katman arasındaki geçişi sağlar Özet Microsoft firmasının veri erişim konusundaki son teknolojisi ADO.NET bileşenidir. Bu bileşen ADO nun.net Framework mimarisine uyarlanmış ve yeni özellikler kazandırılmış sürümü olup uygulama ve veri kaynakları arasındaki bağlantıyı yöneten birçok sınıf içermektedir. Yüksek performans ve uyumluluk sağlayan ADO.NET, özellikle çok katmanlı uygulama modelini desteklemesi ve XML ile entegrasyonuyla günümüz kurumsal ihtiyaçları karşılayacak niteliktedir. System.Data.dll kütüphanesini kullanan ADO.NET, ADO dan gelme Connection ve Command nesnelerini desteklediği gibi DataSet, DataReader ve DataAdapter gibi yeni nesneler sunmaktadır Sorular 22.1) Veritabanı nedir ve ne amaçla kullanılmaktadır? Örnek vererek açıklayınız. 22.2) DAO, RDO, ADO ve ADO.NET veri sağlayıcı arabirimlerini açıklayınız? 22.3) ADO.NET biriminin mimarisini ve bileşenlerini açıklayınız? 22.4) Hangi durumlarda DataSet veya DataReader nesnesi tercih edilmelidir? Bölüm 22

22 490 C# Programlama Dili Papatya Yayıncılık Eğitim

23 23. ADO.NET Bağlantılı Sınıflar ADO.NET Connected Classes ADO.NET bağlantılı sınıflar veri kaynağı ile kullanıcı uygulaması arasında veri aktarımını tanımlı sağlayıcılara dayanarak gerçekleştirir..net Framework varsayılan olarak ODBC, OLEDB, SQL Sunucu ve Oracle sağlayıcılarını destekler. Bunların haricinde DB2 ve MySQL de kullanılabilir. Aşağıdaki tabloda ADO.NET bünyesinde bulunan veri sağlayıcı sınıfları listelenmiştir. Sağlayıcı sınıfları, taban sınıflardaki Db ön-ekinin yerine sağlayıcı ön-ekinin (Sql, Oracle, Odbc, OleDb gibi) konulması şeklinde isimlendirilir. Burada örnek olarak SQL Sunucuya ait sınıflar gösterilmiştir. Temel Sınıf SqlClient Sınıf Temel Arabirim DbConnection SqlConnection IDbConnection DbCommand SqlCommand IDbCommand DbDataReader SqlDataReader IDataReader/IDataRecord DbTransaction SqlTransaction IDbTransaction DbParameter SqlParameter IDbDataParameter DbParameterCollection SqlParameterCollection IDataParameterCollection DbDataAdapter SqlDataAdapter IDbDataAdapter DbCommandBuilder DbConnectionStringBuilder DbDataPermission SqlCommandBuilder SqlConnectionStringBuilder SqlPermission.NET in OOP desteğini kullanarak ADO.NET içindeki temel sınıfları kendi ihtiyacımıza göre kişiselleştirebiliriz. ADO.NET nesnelerini işlemeden önce üzerinde çalışacağımız ortak bir veritabanı oluşturalım. Kitaptaki örneklerin geneli SQL Sunucu 2005 üzerinde uygulanmıştır.

24 492 C# Programlama Dili SQL Sunucu 2005 üzerinde Kitap isminde bir veritabanı ve içerisinde Musteri tablosu oluşturalım. Aşağıdaki tabloda bu veritabanına ait SQL kod gösterilmiştir. CREATE DATABASE [Kitap] GO USE Kitap GO CREATE TABLE [dbo].[musteri]( [MusteriId] [int] IDENTITY(1,1) NOT NULL, [AdSoyad] [varchar](50) NULL, [Telefon] [char](15) NULL, [DogumTarih] [smalldatetime] NULL, [AktifPasif] [bit] NULL, CONSTRAINT [PK_Musteri] PRIMARY KEY CLUSTERED ( [MusteriId] ASC )WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY] ) ON [PRIMARY] Musteri tablosuna aşağıdaki kayıtları girelim. INSERT Musteri VALUES('Ali Korkmaz','(212) ','01/01/1978',1) INSERT Musteri VALUES('Veli Geçmiş','(212) ','01/01/1982',0) INSERT Musteri VALUES('Ayşe','(212) ','01/01/1980',1) Şekil Musteri tablosu DbConnection Nesnesi System.Data.Common isim-uzayı altında bulunan DbConnection sınıfı, IDbConnection arabirimini uygulamış olup veritabanı sistemleriyle bağlantı kuracak ADO.NET sınıflarının türediği temel sınıftır. DbConnection sınıfının en çok kullanılan üyeleri şunlardır: ConnectionString: Bir VTYS ile bağlantı kurmak için doğru bir bağlantı cümlesi oluşturmak gerekir. Bu cümle veri sağlayıcı, veri kaynağı, şifre ve kullanıcı adı gibi veritabanı bağlantısıyla ilgili bilgileri içerir. Bu bilgiler hangi sağlayıcıyı kullanarak hangi veritabanı sistemine hangi güvenlik bilgileriyle bağlanacağımızı bildirir. Papatya Yayıncılık Eğitim

25 ADO.NET Bağlantılı Sınıfları 493 Şekil DbConnection sınıfından türemiş bağlantı sınıfları Aşağıdaki tabloda SQL sunucuya erişirken kullanılacak sağlayıcılar için bağlantı cümlesi (connection string) örnekleri gösterilmiştir. ODBC [Standard Security] Driver={SQL Server;Server=<Sunucu Adresi>;Database=<Veritabanı A- dı>;uid=<kullanıcı Adı>;Pwd=<Şifre>; [Trusted connection] Driver={SQL Server;Server=<Sunucu Adresi>;Database=<Veritabanı A- dı>;trusted_connection=yes; OLE DB, OleDbConnection(.NET) [Standard Security] Provider=sqloledb;Data Source=<Sunucu Adresi>;Initial Catalog=<Veritabanı Adı>;User Id=<Kullanıcı Adı>;Password=<Şifre>; [Trusted connection] Provider=sqloledb;Data Source=<Sunucu Adresi>;Initial Catalog=<Veritabanı Adı>;Integrated Security=SSPI; [IP Adresiyle Bağlanma] Provider=sqloledb;Data Source=<IP>,<Port>;Network Library=DBMSSOCN;Initial Catalog=<Veritabanı Adı>;User ID=<Kullanıcı A- dı>;password=<şifre>; SqlConnection(.NET) [Standard Security] Data Source=<Sunucu Adresi>;Initial Catalog=<Veritabanı Adı>;User Id=<Kullanıcı Adı>;Password=<Şifre>; Server=<Sunucu Adresi>;Database=<Veritabanı Adı>;User ID=<Kullanıcı A- dı>;password=<şifre>;trusted_connection=false; Bölüm 23

26 494 C# Programlama Dili [Trusted connection] Data Source=<Sunucu Adresi>;Initial Catalog=<Veritabanı Adı>;Integrated Security=SSPI; Server=<Sunucu Adresi>;Database=<Veritabanı A- dı>;trusted_connection=true; [IP Adresiyle Bağlanma] Data Source=<IP>,<Port>;Network Library=DBMSSOCN;Initial Catalog=<Veritabanı Adı>;User ID=<Kullanıcı Adı>;Password=<Şifre>; SQL Sunucu 2005 ile birlikte MDAC in yerini istemciyle iletişim sağlayan SQL Native Client programı aldı. Ayrıca yeniliklerden birisi de MARS özelliğidir. MARS (Multiple Active Result Set- Çoklu Aktif Sonuç Kümeleri), SQL sunucunun aynı anda aynı bağlantı üzerinden birden fazla isteğe yanıt verebiliyor olmasıdır. Yani paralel işlem yapabilme kabiliyetidir. Örneğin ADO.NET 2.0 dan önceki sürümlerde DataReader ile veri çekmek için kullandığımız aynı bağlantı nesnesiyle başka bir sorgu çalıştıramıyorduk. Söz konusu bağlantıyı kapatıp yeniden açmalıyız. ADO.NET 2.0 da SQL Sunucu 2005 teki bu yenilikler için aşağıda bağlantı cümlesi formatları kullanılabilir. SQL Native Client ODBC Driver [Standard Security] Driver={SQL Native Client;Server=<Sunucu Adresi>;Database=<Veritabanı Adı>;Uid=<Kullanıcı Adı>;Pwd=<Şifre>; [Trusted connection] Driver={SQL Native Client;Server=<Sunucu Adresi>;Database=<Veritabanı Adı>;Trusted_Connection=yes; [MARS özelliğini kullanmak] Driver={SQL Native Client;Server=<Sunucu Adresi>;Database=<Veritabanı Adı>;Trusted_Connection=yes;MARS_Connection=yes; Driver={SQL Native Client;Server=<Sunucu Adresi>;Database=<Veritabanı Adı>;Trusted_Connection=yes;MultipleActiveResultSets=true; SQL Native Client OLE DB Provider [Standard Security] Provider=SQLNCLI;Server=<Sunucu Adresi>;Database=<Veritabanı Adı>;Uid=<Kullanıcı Adı>;Pwd=<Şifre>; [Trusted connection] Provider=SQLNCLI;Server=<Sunucu Adresi>;Database=<Veritabanı Adı>;Trusted_Connection=yes; [MARS özelliğini kullanmak] Provider=SQLNCLI;Server=<Sunucu Adresi>;Database=<Veritabanı Adı>;Trusted_Connection=yes; MARS_Connection=yes; Provider=SQLNCLI;Server=<Sunucu Adresi>;Database=<Veritabanı Adı>;Trusted_Connection=yes;MultipleActiveResultSets=true; Papatya Yayıncılık Eğitim

27 ADO.NET Bağlantılı Sınıfları 495 SqlConnection(.NET) [Standard Security] Data Source=<Sunucu Adresi>;Initial Catalog=<Veritabanı Adı>;User Id=<Kullanıcı Adı>;Password=<Şifre>; Server=<Sunucu Adresi>;Database=<Veritabanı Adı>;User ID=<Kullanıcı A- dı>;password=<şifre>;trusted_connection=false; [Trusted connection] Data Source=<Sunucu Adresi>;Initial Catalog=<Veritabanı Adı>;Integrated Security=SSPI; Server=<Sunucu Adresi>;Database=<Veritabanı A- dı>;trusted_connection=true; [MARS özelliğini kullanmak] Server=<Sunucu Adresi>;Database=<Veritabanı A- dı>;trusted_connection=true;multipleactiveresultsets=true; SQL Sunucu 2005 e özel olan diğer özellik te context connection ifadesidir. SQL Sunucu 2005 üzerinde çalışacak CLR stored procedure veya fonksiyon nesneleri ya harici bir veritabanına (başka bir SQL Sunucu, Access, Oracle gibi) erişir (Out-of-Proc) ya da doğrudan içinde bulunduğu SQL Sunucu sistemine erişir (InProc). InProc türde bağlantılar için context connection=true ifadesi kullanılır. Böylece o yordam veya fonksiyonun çalışması için içinde bulunduğu sunucunun adresini ve güvenlik bilgilerini vermemiş oluruz. using (SqlConnection mycnn = new SqlConnection("context connection=true")) { connection.open(); Şu ana kadar örneklerini verdiğimiz bağlantı cümlesi (connection string) ifadeleri, SQL sunucu sistemleri için geçerliydi. Aynı şekilde diğer veritabanlarına da bu şekilde erişilebilir. Aşağıdaki tabloda MS Access ve Oracle için kullanılabilecek formatlar sunulmuştur. ODBC [MS Access] Driver={Microsoft Access Driver (*.mdb);dbq=<mdb Dosyasının Adresi>; Uid=<Kullanıcı Adı>;Pwd=<Şifre>; [Oracle] Driver={Microsoft ODBC for Oracle;Server=<Sunucu Adresi>;Uid=<Kullanıcı Adı>;Pwd=<Şifre>; Driver={Microsoft ODBC Driver for Oracle;ConnectString=OracleServer.world;Uid=<Kullanıcı A- dı>;pwd=<şifre>; Bölüm 23

28 496 C# Programlama Dili OLE DB, OleDbConnection(.NET) [MS Access] Provider=Microsoft.Jet.OLEDB.4.0;Data Source=<MDB Dosyasının Adresi>;User Id=<Kullanıcı Adı>;Password=<Şifre>; [Oracle] Provider=msdaora;Data Source=<Sunucu Adresi>;User Id=<Kullanıcı A- dı>;password=<şifre>; OracleConnection, Oracle Data Provider, ODP.NET Data Source=<Sunucu Adresi>;Integrated Security=yes; Data Source=<Sunucu Adresi>;User Id=<Kullanıcı A- dı>;password=<şifre>;integrated Security=no; ODBC sağlayıcı kullanılırken bağlantı cümlesi iki şekilde tanımlanabilir: DSN ve DSN-siz. ODBC mimarisinde istemci uygulama ODBC Sürücü Yönetimi programına bağlanır; onun içindeki ODBC sürücüsünü (Microsoft SQL ODBC driver gibi) kullanarak ilgili veri kaynağına (SQL Sunucu içinde bir veritabanı gibi) erişir. Bu güzergahı, doğrudan bağlantı cümlesi içerisinde belirtebildiğimiz gibi (DSN-less yöntemi) ODBC Data Source Administrator programı içerisinde DSN (Data Source Name-Veri Kaynağı Adı) tanımlayarak ta yapabiliriz. Bu işlemi yapmak için öncelikle Windows işletim sisteminde Control Panel» Administrative Tools» Data Sources ODBC programında bir System DSN, User DSN veya File DSN tanımlanır. Daha sonra bağlantı cümlesi içerisinde DSN adı verilerek istemcinin ilgili veri kaynağına erişmesi sağlanır. [DSN] DSN=<DSN Adı>;Uid=<Kullanıcı Adı>;Pwd=<Şifre>; [File DSN] FILEDSN=<DSN Dosyasının Adresi>;Uid=<Kullanıcı Adı>;Pwd=<Şifre>; ADO.NET (2.0) SQL Sunucu 2005 ile bütünleştirilmiş olup veritabanı yansıtma (database mirroring) yönetimini destekler. Bağlantı cümlesine eklenecek Failover Partner anahtar sözcüğü aracılığıyla birincil veritabanıyla bağlantı kurulamadığı zamanlarda ek bir kodlama veya yapılandırmaya gerek kalmadan ikinci veritabanına yönlendirme yapılır. Bunun için veritabanının yansıtılmış şekilde ayarlanmış olması gerekir. Aşağıdaki cümlede belirlenmiş bağlantı süresinde, eğer birinci veritabanı olan DW1 sunucusuyla bağlantı kurulamazsa, onun yansıması olarak yapılandırılmış olan ikinci veritabanıyla (failover partner) DW2 ile bağlantı kurulmaya çalışılır. [Database Mirroring] Data Source=DW1;Failover Partner=DW2;Initial Catalog=Kitap;Integrated Security=True; Papatya Yayıncılık Eğitim

29 ADO.NET Bağlantılı Sınıfları 497 ConnectionTimetout: Bu özellik bir bağlantının başarısız olmadan ve hata vermeden önce ne kadar süre (saniye) beklemesi gerektiğini bildirir. Varsayılan değeri, kullanılan bağlantı türüne göre değişir. SQL Sunucu sistemi için varsayılan değer 15 saniyedir. Bu sürede ilgili sunucuyla bağlantı kurulamazsa program hataya düşer. Database: O anda bağlı bulunduğumuz veritabanı sisteminin adını bildirir. DataSource: O anda bağlı bulunduğumuz veri kaynağını yani sunucuyu bildirir. Database ve DataSource özelliklerini, bağlantı açıldıktan sonra okumak daha doğru olacaktır. Aşağıda SQL sunucu için tanımlanmış bağlantı cümlesinde, DataSource özelliği AKAYMAZ, Database özelliği Kitap değerini döndürür. Driver={SQL Server;Server=AKAYMAZ;Database=Kitap;Uid=sa;Pwd=123; Eğer uygulamanın çalıştığı sistemde SQL Sunucu default instance olarak kurulmuşsa Server=. veya Server=(local) ifadesi kullanılabilir. ServerVerison: Üzerinde çalışılan veritabanı sunucusunun sürümünü bildirir. State: VTYS ile yapılmak istenen veya yapılmış bağlantının durumunu sözce/ string türünde (Open, Closed) bildirir. BeginTransaction(): Veritabanı etkileşim/hareket işlemini başlatır. ChangeDatabase(): Üzerinde çalışılan veritabanını değiştirir. Close(): Bu yordam aktif bağlantıyı kapatır. Open(): VTYS ile kurulacak bağlantıyı açar. Şu ana kadar gördüğümüz özellik ve yordamları aşağıdaki örnekte uygulayalım. using System; using System.Data.SqlClient; class Program { static void Main(string[] args) { string CnnStr = "Server=AHMETKAYMAZ;Database=Kitap;Uid=sa;Pwd=123;"; SqlConnection Cnn = new SqlConnection(CnnStr); Console.WriteLine("Bağlantı durumu(1): " + Cnn.State); Cnn.Open(); Console.WriteLine("Sunucu Adı: " + Cnn.DataSource); Console.WriteLine("Sunucu Sürümü: " + Cnn.ServerVersion); Console.WriteLine("Veritabanı Adı: " + Cnn.Database); Console.WriteLine("Bağlantı durumu(2): " + Cnn.State); Cnn.Close(); Console.WriteLine("Bağlantı durumu(3): " + Cnn.State); Bağlantı durumu(1): Closed Sunucu Adı: AHMETKAYMAZ Sunucu Sürümü: Veritabanı Adı: Kitap Bağlantı durumu(2): Open Bağlantı durumu(3): Closed Bölüm 23

30 498 C# Programlama Dili Veri kaynağına bağlanma işlemi, hata vermeye meyilli bir durum olduğu için (network kesintisi, sunucunun hazır olmaması...) bağlantıyı try-catch bloğunda açmak programımızın çalışma esnasında hata vermesini engellemiş oluruz. String CnnStr ="Server=AHMETKAYMAZ;Database=Musteri;Uid=sa;Pwd=123;"; SqlConnection Cnn = new SqlConnection(CnnStr); try { Cnn.Open(); catch(exception ex) { Console.WriteLine("Bağlantı hatası : "+ ex.message); finally { if (Cnn.State == ConnectionState.Open) Cnn.Close(); Konuyla ilgili diğer bir önemli unsur da bağlantı (DbConnection) nesnesinin Dispose() veya Close() edilmesi arasındaki farktır. Bu farkı şöyle açıklanabilir: Close() yordamı bağlantı nesnesinin özelliklerini yok etmeyip sadece o anda veritabanı bağlantısını keser. Nitekim bu yordam çalıştırıldıktan sonra hala bağlantı nesnesinin tüm özelliklerine erişilebilir ve gerektiğinde Open() yordamı ile aynı nesne üzerinden bağlantı yeniden kurulur. Dispose() yordamı öncelikle Close() yordamını çağırarak açık olan bağlantıyı keser; ardından bağlantı nesnesinin özelliklerini sıfırlar. Bu yüzden eğer daha sonraki satırlarda yeniden bağlantı kurulacaksa bağlantı nesnesinin ConnectioString özelliğinin yeniden düzenlenmesi gerekir. Bununla birlikte bağlantı nesnesi bağlantı havuzu üzerinde canlı kalır Bağlantı Cümlesinin Saklanması Bağlantı cümlesinin (connection string) doğrudan derlenmiş kod içerisine gömülmesi yerine dışardan parametrik yapılması yeni değerlerin girilmesini sağlar. Bunun en kullanışlı yeri uygulamalara ait düz metin tabanlı.config dosyalarıdır. Bağlantı cümlesinin Windows veya konsol uygulamalarında App.Config, Web uygulamalarında Web.Config dosyasında saklanması sunucu, veri kaynağı veya güvenlik bilgilerinin değiştirildiği durumda basitçe revize edilmesini sağlar. VS.NET de üzerinde çalıştığımız konsol projesini sağ tıklayıp Add» New Item bölümünde Application Configuration File dosyası ekleyelim. Varsayılan adı App.config olan bu dosyaya aşağıdaki bir anahtar ekleyelim. [App.Config] <?xml version="1.0" encoding="utf-8"?> <configuration> <appsettings> <add key="cnnstr" value="server=ahmetkaymaz;database=musteri;uid=sa;pwd=123;" /> </appsettings> </configuration> Papatya Yayıncılık Eğitim

31 ADO.NET Bağlantılı Sınıfları 499 Konfigurasyon dosyasındaki bu bölümü okumak için System.Configuration.ConfigurationManager (System.Configuration.dll) sınıfı kullanılır. using System.Configuration;... static void Main(string[] args) { String CnnStr = ConfigurationManager.AppSettings["CnnStr"]; SqlConnection Cnn = new SqlConnection(CnnStr);....NET 2.0 ile birlikte konfigürasyon dosyasına eklenmek üzere doğrudan bağlantı cümlesine özel connectionstrings isimli sekme sunulmaktadır. [App.Config] <?xml version="1.0" encoding="utf-8"?> <configuration> <connectionstrings> <add name="cnnstr" connectionstring="server=ahmetkaymaz;database=musteri;uid=sa;pwd=123;" providername="system.data.sqlclient"/> </connectionstrings> </configuration> Bu sekmeyi okumak için System.Configuration.ConfigurationManager sınıfının ConnectionStrings özelliği kullanılır. using System.Configuration;... static void Main(string[] args) { String CnnStr; CnnStr= ConfigurationManager.ConnectionStrings["CnnStr"].ConnectionString; SqlConnection Cnn = new SqlConnection(CnnStr);... Bu yöntemin dışında bağlantı bilgisinin sunucunun Registry alanında veya.ini dosyasında tutulması gibi alternatifler de kullanılabilir. Ama en kullanışlısı tabiî ki.config dosyalarıdır. PÜF! Bağlantı cümlesi içerisinde birçok parametre bulunmaktadır. Bu parametrelerin hatırlanması zor olduğu için bağlantı cümlesini DbConnectionStringBuilder sınıfı a- racılığıyla dinamik olarak oluşturabilir veya düzenleyebiliriz. Aşağıdaki örnekte bu sınıfı temel almış SqlConnectionStringBuilder sınıfı kullanılarak SQL sunucusuna özel bağlantı cümlesi oluşturulmuştur. Bölüm 23

32 500 C# Programlama Dili SqlConnectionStringBuilder osqlcnn; osqlcnn = new SqlConnectionStringBuilder(); osqlcnn.datasource = "AHMETKAYMAZ"; osqlcnn.initialcatalog = "Musteri"; osqlcnn.userid = "sa"; osqlcnn.password = "123"; string CnnStr; CnnStr = osqlcnn.connectionstring; SqlConnection Cnn = new SqlConnection(CnnStr); DbCommand Nesnesi System.Data.Common isim uzayı altında bulunan DbCommand sınıfı, IDbCommand arabirimini uygulamış olup veri kaynağı üzerinde çalışacak SQL sorgusunun düzenler. Connection nesnesiyle ilişkili çalışan Command nesnesi, DDL ve DML türünde sorgu deyimlerini çalıştırabilir. DbCommand sınıfının en çok kullanılan üyelerini inceleyelim: CommandText: Veri kaynağına gönderilen sorgu cümlesini düzenler. CommandTimeout: Sorgunun hata verinceye kadar ne kadar süre bekleyebileceğini bildirir. CommandType: Veri kaynağına gönderilen sorgunun orada nasıl yorumlanacağını bildirir. Command nesnesi aracılığıyla klasik SQL deyimlerini (SELECT, INSERT, UPDATE, DELETE ve diğer DDL komutları, CREATE, ALTER...) ve saklı prosedürleri de çalıştırabildiğimiz gibi doğrudan table veya view türünde bir üyenin adını da vererek bir kayıt dizisi çekebiliriz. System.Data.CommandType türünde değer alan bu özellik Text, StoredProcedure, TableDirect türlerinden birisini alır. Sorgular varsayılan olarak Text tipinde yorumlanır. NOT TableDirect seçeneği sadece.net Framework Data Provider for OLE DB tarafından desteklenir. Command nesnesi, üzerinde koşacağı bağlantı nesnesi ve çalıştıracağı SQL sorgusunun tanımlanma şekline göre aşağıdaki farklı biçimlerde tanımlanabilir. OleDbCommand ocmd = new OleDbCommand(); ocmd.commandtext = "SELECT * FROM Musteri"; //Düz SQL scripti. ocmd.commandtype = CommandType.Text; ocmd.commandtext = "SP_GetMusteri"; //Stored Procedure tipinde yorumlanır. ocmd.commandtype = CommandType.StoredProcedure; ocmd.commandtext = "Musteri"; //Direk Musteri tablosunu çeker. ocmd.commandtype = CommandType.TableDirect; Papatya Yayıncılık Eğitim

33 ADO.NET Bağlantılı Sınıfları 501 Connection: Sorgunun kullanacağı bağlantı nesnesini (DbConnection) belirtir. SqlConnection ocnn = new SqlConnection(CnnStr); SqlCommand ocmd = new SqlCommand(); ocmd.connection = ocnn; Parameters: Komut nesnesinde en çok kullanılan üyelerden birisi olan bu özellik, sorguyla ilişkili parametreleri tanımlamak için kullanılır. Bu özellik DbParameter- Collection türünde değer alır. DbParameterCollection sınıfı, IList, ICollection, IEnumerable arabirimlerini uygulamış bir koleksiyon sınıfı olduğu için Add() ve Remove() yordamları kullanılarak birden fazla parametre CommandText özelliğinde tanımlanmış sorguyla ilişkilendirilebilir. string AdSoyad="Ali Korkmaz"; int AktifPasif = 1; string SqlStr = "SELECT * FROM Musteri WHERE AdSoyad='" + AdSoyad + "' AND AktifPasif=" + AktifPasif; SqlCommand ocmd = new SqlCommand(); ocmd.commandtext = SqlCmd; Örnekteki sorguyu veri kaynağına gönderdiğimizde uygulamamız tarafından birleştirilmiş aşağıdaki sql cümlesi çalıştırılır. SELECT * FROM Musteri WHERE AdSoyad='Ali Korkmaz' AND AktifPasif=1 Fakat bu birleştirme işlemi parametre sayısı artıkça kullanımı zorlaşan bir yöntem olduğu için bunun yerine sorguyu veri kaynağı tarafında parametrik yapmamız daha doğru olacaktır. Özellikle Web uygulamalarında kullanıcıdan beklenen verilerin doğru tip ve formatta olmayışı bu yöntemde veri kaynağı tarafında kötü niyetli sorguların çalışmasına neden olabilir. Çünkü bu yöntemde kullanıcıdan gelecek veriler üzerinde doğrulama işlemi yapılmamaktadır. Oysa parametrik yapıda sorguyla ilişkilendirilecek argümanların tip ve boyutları da belirtilir; bu da sorgu cümlesinin doğru bir şekilde yorumlanmasını sağlar. DbParameter nesnesini ayrıca işleyeceğiz şimdilik aşağıdaki tabloda basit bir parametre tanımlamasını bulabilirsiniz SqlCommand ocmd = new SqlCommand(); ocmd.commandtext="select * FROM Musteri WHERE MusteriId>@MusteriId"; //Int türünde bir parametre tanımlayalım. SqlParameter p1 = new SqlParameter("MusteriId",SqlDbType.Int); //Parametrenin değeri 1 olacak. p1.value = 1; //Bu parametreyi ocmd nesnesiyle ilişkilendirelim. ocmd.parameters.add(p1); Bölüm 23

34 502 C# Programlama Dili Bu durumda oluşturduğumuz sorgu SQL sunucu tarafından aşağıdaki gibi çalıştırılır: exec sp_executesql N'SELECT * FROM Musteri WHERE MusteriId int',@musteriid=1 Transaction: Komut nesnesiyle ilişkili olacak DbTransaction nesnesini bildirir. Cancel(): Veri kaynağında DbCommand nesnesinin çalışmasını iptal eder. CreateParameter(): DbParameter türünde nesne oluşturur. SqlParameter p1 = ocmd.createparameter(); p1.parametername = "AdSoyad"; p1.dbtype = DbType.String; ocmd.parameters.add(p1); DbCommand nesnesi tanımlanırken DbCommand sınıfının yeniden yüklenmiş yapıcı yordamı da kullanılabilir. Örneğin en çok kullanılan yapıcı prototipinde CommandText ve Connection bilgileri parametre olarak girilir. string Qry = "SELECT * FROM Musteri"; SqlCommand ocmd = new SqlCommand(Qry,oCnn); DbCommand nesnesinin Close() yordamı bulunmayıp Dispose() yordamı bulunur. SqlCommand temelde yönetilemeyen kaynak (un-managed resource) içermese de OleDbCommand, OdbcCommand gibi nesneler yönetilmeyen nesne içerdiği için en mantıklısı dispose edilmeleridir DbCommand Nesnesinin Yürütülmesi DbCommand nesnesiyle tanımlanmış bir sorgu veri kaynağı üzerinde dört yürütme yordamından biriyle gerçekleştirilir: ExecuteNonQuery() ExecuteReader() ExecuteScalar() ExecuteXmlReader(). DbCommand nesnesine ait bu yürütme yordamlarını çalıştırmak ve geri dönüşlerini yakalamak için daha sonraki satırlarda veritabanı işlemlerini sorunsuz devam ettirmek için öncelikle bağlantı açılır ve yürütme bittikten sonra bağlantı kapatılır. ExecuteNonQuery(): INSERT, UPDATE ve DELETE gibi geriye herhangi bir değer döndürmeyen işlemler için kullanılır. Bu yordam geriye, int türünde değer döndürür. Bu değer veri kaynağına gönderilmiş sorgudan kaç kayıdın etkilendiğini bildirir. Papatya Yayıncılık Eğitim

35 ADO.NET Bağlantılı Sınıfları 503 Aşağıdaki örnekte MusteriId kolonu 3 olan satırın AdSoyad kolonu Ayşe Güler olarak güncellenmiştir. // Bağlantı cümlesi. string CnnStr = "Server=.;Database=Kitap;Uid=sa;Pwd=123;"; // Connection nesnesi tanımlayalım. SqlConnection ocnn = new SqlConnection(CnnStr); // Çalıştırılacak SQL cümlesi. string Qry = "UPDATE Musteri SET AdSoyad='Ayşe Güler' WHERE MusteriId=3"; // Command nesnesi tanımlayalım. SqlCommand ocmd = new SqlCommand(Qry,oCnn); // Bağlantıyı açalım. ocnn.open(); // ExecuteNonQuery(), sorgudan kaç kayıdın etkilendiğini döndürür. int KayitSayisi = ocmd.executenonquery(); // Bağlantıyı kapatalım. ocnn.close(); if (KayitSayisi > 0) Console.WriteLine(KayitSayisi +" kayıt güncellendi."); else Console.WriteLine("Güncelleme işlemi başarısız oldu."); 1 kayıt güncellendi. ExecuteScalar(): Tek alanlı değer döndüren sorgular için tercih edilir. Geriye object türünde değer döndürür. Örneğin grupsal fonksiyonların sonuçları için bu yordam tercih edilebilir. Aşağıdaki tabloda Musteri tablosunda kaç kaydın olduğu sorgulanmıştır. // Bağlantı cümlesi. string CnnStr = "Server=.;Database=Kitap;Uid=sa;Pwd=123;"; // Connection nesnesi tanımlayalım. SqlConnection ocnn = new SqlConnection(CnnStr); // Verikaynağında gönderilecek SQL sorgu cümlesi. string Qry = "SELECT COUNT(*) FROM Musteri"; // Command nesnesi tanımlayalım. SqlCommand ocmd = new SqlCommand(Qry,oCnn); // Bağlantıyı açalım. ocnn.open(); // ExecuteScalar() yordamı tek alanlık veri döndürür. int KayitSayisi = (int)ocmd.executescalar(); // Bağlantıyı kapatalım. ocnn.close(); Console.WriteLine("Tabloda "+ KayitSayisi +" kayıt bulunmaktadır."); Tabloda 3 kayıt bulunmaktadır. Bölüm 23

36 504 C# Programlama Dili Eğer veritabanı sunucusu gönderilen sorgu kayıt dizisini döndürüyorsa ExecuteScalar() yordamı ilk satır ve sütunun kesişim değerini döndürecektir. Yani yine tek kayıt döndürecektir. ExecuteReader(): DbCommand sınıfının en çok kullanılan yordamlardan biri olup veri kaynaklarından kayıt dizisi çekmek için kullanılır. System.Data.Common.DbDataReader nesnesi türünde değer döndürür. ADO.NET kayıt dizisi türündeki sorgu sonuçları için iki tür nesne sunar: DataReader ve DataSet. Burada ele alınacak olan DataReader nesnesi forward-only (ileri-doğru) ve uneditable (sadece okunabilir, güncellenemez) özelliğine sahiptir. ExecuteXmlReader(): Veritabanı sunucusundan çekilmiş XML formatındaki içeriği System.Xml.XmlReader sınıfı türündeki bir değişkene aktarır. XmlReader sınıfı XML formatındaki veriyi ileri yönlü hızlı bir şekilde okumak için kullanılır. SQL sunucu tarafında veriyi XML formatında döndürmek için FOR XML ifadesi kullanılır. string CnnStr = "Server=.;Database=Kitap;Uid=sa;Pwd=123;"; SqlConnection ocnn = new SqlConnection(CnnStr); // Sorgunun sonucunu XML formatında döndürelim. string Qry = "SELECT MusteriId, AdSoyad FROM Musteri FOR XML AUTO, XMLDATA"; SqlCommand ocmd = new SqlCommand(Qry, ocnn); ocnn.open(); // ExecuteXmlReader() yordamı XmlReader türünde değer döndürür. System.Xml.XmlReader oxr = ocmd.executexmlreader(); // XML içeriği aktaracağımız bir DataSet nesnesi tanımlayalım. DataSet ods = new DataSet(); ods.readxml(oxr, XmlReadMode.Fragment); oxr.close(); ocnn.close(); // DataSet nesnesinin ilk tablosundaki kayıtları listeleyelim. Console.WriteLine("MusteriId\tAdSoyad"); Console.WriteLine(" \t "); foreach (DataRow odr in ods.tables[0].rows) Console.WriteLine("{0\t\t{1\t", odr["musteriid"], odr["adsoyad"]); MusteriId AdSoyad Ali Korkmaz 2 Ayşe Korkmaz 3 Mert Şensoy DbDataReader Nesnesi DataReader, verileri salt-okunur olarak sıralı şekilde okuyan bir ADO.NET nesnesidir. Birim zamanda bellek üzerinde yalnızca bir kayıt satırı içerir ve yalnızca bir Papatya Yayıncılık Eğitim

37 ADO.NET Bağlantılı Sınıfları 505 sonraki satıra yönlendirilebilir. Daha ileriye atlama veya geri gelme imkânı bulunmamaktadır. DataReader nesnesi ile oluşturulan veri kanalı etkin olduğu sürece veri kaynağıyla olan bağlantısının açık olması gerekir. DataReader nesnesi kullanılmaya başlandığında bu nesnenin bağlı olduğu Connection nesnesi meşgule düşer ve DataReader nesnesi kapatılıncaya kadar bu Connection nesnesiyle başka bir işlem yapılamaz. DataReader nesnesinin kullanıldığı durumlarda t zamanında bellek üzerinde yalnızca bir satır bulunur. Sorgu, yürütüldüğü zaman kanal aracılığıyla veri kaynağı tarafındaki ilk satır DataReader nesnesine gönderilir. Bu arada kanal sonraki kaydı okumak ve taşımak üzere bağlantıyı açık bırakır. DataRaeder nesnesini döngüye sokarak, her defasında bir adım ilerlemesini sağlarız. Birim zamanında tek bir satır taşıdığı için kayıtlar arasında geri dönme işlemi mümkün değildir. Bu yüzden ExecuteReader() aracılığıyla bir sorgu yürüttüğümüzde sorgunun kaç kayıt döndüreceğini bilemeyiz. DataReader nesnesi bir değer değişkeni gibi çalıştığı için new anahtar sözcüğüyle kendisinden nesne oluşturulamaz. DataReader nesne mimarisi, bu nesnenin veritabanı türlerine göre değişkenlik gösterdiğini işaret eder. Bu nedenle DataReader nesneleri doğrudan System.Data kütüphanesinin altında bulunmak yerine farklı veritabanları için hazırlanmış isimuzayları altında bulunur. En çok kullanılan DataReader türleri şunlardır: System.Data.SqlClient.SqlDataReader System.Data.OleDb.OleDbDataReader System.Data.Odbc.OdbcDataReader Oracle.OracleClient.OracleDataReader. Bu sınıflar DbDataReader sınıfından türemiş olup IDataReader arabirimini uygulamıştır. DataReader içindeki kayıtları okumak için DataReader sınıfının indeksleyici üyesi kullanılabildiği gibi Get ile başlayan kolon seçici yordamları da kullanılabilir. İndeksleyici yapısı kullanılırken kolon indisi veya adı verilir. DataReader nesnesine ait indeksleyici object türünde değer döndürür. Adı ordr olan bir DataReader nesnesinde tabloda ikinci sırada bulunan ve türü VarChar olan AdSoyad isimli kolonu okumak için aşağıdaki seçenekler kullanılır: // Kolona ait index verilerek. Console.WriteLine(oRdr[1]); // Kolonun adı verilerek(daha performanslı çalışır). Console.WriteLine(oRdr["AdSoyad"]); // GetValue yordamı. Console.WriteLine(oRdr.GetValue(1)); // Sözce türünde değer döndüren GetString yordamı. Console.WriteLine(oRdr.GetString(1)); Bölüm 23

38 506 C# Programlama Dili Veritabanındaki bir sonraki kaydı DataReader nesnesine aktarmak için nesnenin Read() yordamı kullanılır; bool türünde değer döndüren bu yordamın true döndürmesi okunacak satır olduğu anlamına gelir. Dolayısıyla gönderilen sorgu sonucunun kayıt içerip içermediği bu yordam aracılığıyla öğrenilebilir. DataReader kayıtlarını listelemek için bu yordam aracılığıyla döngü kurulur. Read() yordamı true döndürdüğü sürece program döngüye girer. Aşağıdaki tabloda Musteri tablosundaki satırların AdSoyad kolonu listelenmiştir: string CnnStr = "Server=.;Database=Kitap;Uid=sa;Pwd=123;"; SqlConnection ocnn = new SqlConnection(CnnStr); string Qry = "SELECT * FROM Musteri"; SqlCommand ocmd = new SqlCommand(Qry,oCnn); ocnn.open(); // DataReader nesnesi tanımlayalım. SqlDataReader ordr = ocmd.executereader(); //DataReader içinde kayıt olduğu sürece while (ordr.read()) { Console.WriteLine(oRdr["AdSoyad"]); // DataReader nesnesini kapatalım. ordr.close(); // Bağlantıyı kapatalım. ocnn.close(); Ali Korkmaz Veli Geçmiş Ayşe Güler Bu şekilde her nesnenin kapatılmasıyla uğraşılmak istenmiyorsa alternatif olarak nesne yaratmalarının using bloğu içerisinde yapılması önerilir. string CnnStr = "Server=.;Database=Kitap;Uid=sa;Pwd=123;"; using (SqlConnection ocnn = new SqlConnection(CnnStr)){ //1 ocnn.open(); string Qry = "SELECT * FROM Musteri"; SqlCommand ocmd = new SqlCommand(Qry, ocnn); using (SqlDataReader ordr = ocmd.executereader()){ //2 while(ordr.read()) Console.WriteLine(oRdr["AdSoyad"]); //2 ocmd.dispose(); //1 ExecuteReader() yordamı yeniden yüklenmiştir. Örnekteki parametresiz kullanımı dışında System.Data.CommandBehavior türünde parametre alan ikinci kullanım şekli bulunur. CommandBehavior sorgunun çalışma davranışını belirlemek için kullanılır. Ayrıca sorgunun performansını artırdığı da varsayılır. Papatya Yayıncılık Eğitim

39 ADO.NET Bağlantılı Sınıfları 507 CommandBehavior numaralandırmanın çok kullanılan değeri CloseConnection seçeneğidir. CloseConnection seçeneği DataReader nesnesi kapatıldığı zaman bu nesnenin ilişkili olduğu Connection nesnesinin de otomatik olarak kapatılmasını sağlar. Bu seçenek özellikle DataReader türünde değer döndüren fonksiyonlarda anlam kazanır. Aşağıdaki örnekte gösterildiği gibi ExeSqlReturnDatareader() fonksiyonundan DataReader nesnesini döndürmek için veritabanı bağlantısının açık bırakmak gerekir. Fonksiyondan çıkıldıktan sonra söz konusu bağlantıyı kapatmak mümkün olmadığı için DataReader nesnesiyle işimiz bittikten sonra bağlantının kesilmesi gerektiğini fonksiyon içerisinde belirtmemiz gerekir. public SqlDataReader ExeSqlReturnDatareader(string SqlQuery) { SqlCommand ocmd = new SqlCommand(SqlQuery, ocnn); SqlDataReader ordr = null; try { ocnn.open(); ordr = ocmd.executereader(commandbehavior.closeconnection); // Bu fonksiyon dışarıdan çağrılıp DataReader nesnesi kullanıldıktan sonra bağlantı kendiliğinden kapatılır. catch (Exception ex) { // Hata Yönetimi. return ordr; //ExeSqlReturnDatareader. SingleRow seçeneği tek satır döndüren kayıtlarda performans açısından tercih edilir. Bu durumda herhangi bir döngü işlemi kurmaya gerek kalmayıp Read() yordamı kullanılarak söz konusu tek satıra erişilir. SingleResult seçeneği ExecuteScalar() yordamı gibi çalışıp tek değer döndüren kayıtlarda performansı iyileştirmek için tercih edilir. Örneğin grupsal fonksiyon sonucunu bu yöntemle alabiliriz. SchemaOnly seçeneği sorgudaki kolonlara ait şema bilgisini (alan bilgisini) döndürür. Bu seçenekte DataReader nesnesi herhangi bir veri veya tabloya ait anahtar bilgisi içermez sadece tablonun alan bilgisini içerir. Beş kolonlu Musteri tablomuzun şema bilgisini alalım. string CnnStr = "Server=.;Database=Kitap;Uid=sa;Pwd=123;"; SqlConnection ocnn = new SqlConnection(CnnStr); Bölüm 23

40 508 C# Programlama Dili string Qry = "SELECT * FROM Musteri"; SqlCommand ocmd = new SqlCommand(Qry, ocnn); ocnn.open(); SqlDataReader ordr = ocmd.executereader(commandbehavior.schemaonly); // DataReader içerisine veri aktaralım. ordr.read(); for (int x = 0; x<5; x++) Console.WriteLine(oRdr.GetName(x) +" : "+ ordr.getfieldtype(x) ); ordr.close(); ocnn.close(); MusteriId : System.Int32 AdSoyad : System.String Telefon : System.String DogumTarih : System.DateTime AktifPasif : System.Boolean Bu sorgu SQL Sunucu tarafında FMTONLY ifadesini aktifleştirir. Bu durumda sunucu sorguyu çalıştırmaz; istemci tarafında herhangi bir veri göndermez sadece tablo yapısını bildirir. SET FMTONLY ON;SELECT * FROM Musteri SET FMTONLY OFF; SequentialAccess seçeneği büyük miktarda kolon içeren tabloları sorgularken performansı artırmak için kullanılır. DataReader nesnesi mimarisi gereği sorgu sonucundaki kayıtları veritabanının belleğinden programın bulunduğu sistemin belleğine satır satır taşır. Normal şartlar altında DataReader, bir satırı tümüyle belleğe yüklediği için ve büyük nesnelerin gigabyte lara varan boyutlara varabileceği ihtimalinden dolayı bu satırları olduğu gibi belleğe taşımak performans kaybına neden olacaktır. Bu yüzden bu tür satırları tümüyle değil parça parça okumak daha olumlu bir sonuç doğuracaktır. Buna sırasal bilgi okuma yöntemi denilir. DataReader, o anki satırdaki büyük boyutlu nesneyi okurken bir kanal (stream) oluşturur. Bu kanaldan dolayı kolonların sıralı bir şekilde okunması gerekir, o satıra ait kolon sadece bir kere okunabilir. Ön-bellekleme (buffering) yapılmadığı için kolon okunduktan sonra bellekten temizlenir. Kolonu ikinci kez okumaya çalıştığımızda Invalid attempt to read from column ordinal '0'. With CommandBehavior.SequentialAccess, you may only read from column ordinal '2' or greater. hatasıyla karşılaşırız. BLOB türündeki kolonlar GetBytes() ve GetChars() yordamıyla okunur. Bu yordamlar kolondaki veriyi karakter veya Byte düzeyinde okur. Makale isminde bir tablomuz olduğunu düşünelim. CREATE TABLE [dbo].[makale]( [MakaleId] [int], [YazarAd] [char](20), [MakaleText] [varchar](max) ) Papatya Yayıncılık Eğitim

41 ADO.NET Bağlantılı Sınıfları 509 Bu tablodaki 1 nolu makalenin bilgilerini çekelim. SqlConnection ocnn = new SqlConnection(CnnStr); string Qry = "SELECT YazarAd,MakaleText FROM Makale WHERE MakaleId=1"; SqlCommand ocmd = new SqlCommand(Qry, ocnn); // Bağlantıyı açıp veriyi DataReader içerisine okuyalım. ocnn.open(); SqlDataReader ordr = ocmd.executereader(commandbehavior.sequentialaccess); // Tek kayıt olduğu için While döngüsüne ihtiyaç duyulmaz. ordr.read(); // YazarAd sorguda MakaleText den önce olduğundan önce o okunmalı. Console.WriteLine(oRdr["YazarAd"]); // Aynı kolonu ikinci kez okuyamayız. // Console.WriteLine(oRdr["YazarAd"]); HATALI DURUM. int buffersize = 250; char[] arrchr = new char[buffersize]; byte startindex = 0; // İlk parametredeki 1 değeri ikinci kolon olduğunu bildirir. // 3.parametredeki 0 değeri, dizinin kaçıncı elemanından itibaren aktarılacağını bildirir. ordr.getchars(1, startindex, arrchr, 0, buffersize); // Dizideki elemanları yazdıralım. for (int x = 0; x < arrchr.length; x++){ Console.Write(arrChr[x]); //Bu noktadan sonra önceki kolona erişilemez. //Console.WriteLine(oRdr["YazarAd"]); HATALI DURUM. ordr.close(); ocnn.close(); Son seçenek olan KeyInfo DataReader nesnesinin kayıtlarla birlikte tabloya ait anahtar bilgileri içermesini de sağlar. DataReader içindeki kolonlara ait üst-veri (metadata) bilgilerini almak için DataReader in GetSchemaTable() yordamı kullanılır. Bu yordam her kolona ait özellikleri bir satırda verecek şekilde DataTable türünde nesne döndürür; kolon isimleri DataReader nesnesindeki kolonlara ait özelliklerden (ColumnName, DataType, ColumnSize, IsKeyColumn, IsAutoIncrement) oluşur. Bu özellikleri kullanarak ilgili kolonun tanıtıcı bilgilerine erişebiliriz. Aşağıdaki tabloda DataReader içerisindeki kolon isimleri, türleri ve anahtar alan olup olmadığı listelenmiştir: SqlConnection ocnn = new SqlConnection(CnnStr); string Qry = "SELECT * FROM Musteri"; SqlCommand ocmd = new SqlCommand(Qry, ocnn); ocnn.open(); SqlDataReader ordr = ocmd.executereader(commandbehavior.keyinfo); //DataTable nesnesini daha sonra işleyeceğiz DataTable tbl = ordr.getschematable(); Bölüm 23

42 510 C# Programlama Dili ordr.close(); ocnn.close(); foreach (DataRow dr in tbl.rows) Console.WriteLine("{0,{1", dr["basecolumnname"], dr["datatype"]); MusteriId,System.Int32,True AdSoyad,System.String,False Telefon,System.String,False DogumTarih,System.DateTime,False AktifPasif,System.Boolean,False Kolonlara ait tüm özellikleri listelemek için DataTable nesnesinin kolonları aşağıdaki gibi döngüye sokulabilir: SqlConnection ocnn = new SqlConnection(CnnStr); string Qry = "SELECT * FROM Musteri"; SqlCommand ocmd = new SqlCommand(Qry, ocnn); ocnn.open(); SqlDataReader ordr = ocmd.executereader(commandbehavior.keyinfo); DataTable tbl = ordr.getschematable(); ordr.close(); ocnn.close(); // Tablo içindeki her kolon için. foreach (DataRow mydr in tbl.rows) { //Kolona ait her özellik için Console.WriteLine(" "); foreach (DataColumn Dc in tbl.columns) { // Kolon adını ve değerini yazdır. Console.WriteLine(Dc.ColumnName +"="+ mydr[dc].tostring()); ColumnName = MusteriId ColumnOrdinal = 0 ColumnSize = 4 NumericPrecision = 10 NumericScale = 255 IsUnique = False IsKey = True BaseServerName = BaseCatalogName = BaseColumnName = MusteriId BaseSchemaName = BaseTableName = Musteri DataType = System.Int32 AllowDBNull = False ProviderType = 8 IsAliased = False IsExpression = False IsIdentity = True IsAutoIncrement = True IsRowVersion = False IsHidden = False IsLong = False IsReadOnly = True ProviderSpecificDataType = System.Data.SqlTypes.SqlInt32 Papatya Yayıncılık Eğitim

43 ADO.NET Bağlantılı Sınıfları 511 DataTypeName = int XmlSchemaCollectionDatabase = XmlSchemaCollectionOwningSchema = XmlSchemaCollectionName = UdtAssemblyQualifiedName = NonVersionedProviderType = 8 Eğer bu örneklerde ExecuteReader() yordamı CommandBehavior.KeyInfo parametresiyle çalıştırılmamış olsa idi, kolonlara ait birçok özelliğinin değeri boş olacaktı. Bu parametre SQL Sunucu tarafında aşağıdaki sorgu çalıştırır. SET FMTONLY OFF; SET NO_BROWSETABLE ON;SELECT * FROM Musteri SET NO_BROWSETABLE OFF; CommandBehavior numaralandırmasına ait seçeneklerden iki tanesini aynı anda da kullanabiliriz. Bunun için bu seçeneklerin int değerinin toplamını vermemiz yeterlidir. Aşağıdaki örnekte hem CloseConnection hem de SequentialAccess seçeneği kullanılmıştır. SqlDataReader ordr = ocmd.executereader( CommandBehavior.KeyInfo CommandBehavior.CloseConnection ); DbDataReader Sınıfının Üyeleri Önceki sayfada gördüğümüz üyeler dışında DbDataReader sınıfının en çok kullanılan üyeleri şunlardır: Close(): DataReader nesnesini kapatmak için kullanılır. Veritabanı işlemlerinde kullandığımız nesneleri işimiz bittikten sonra kapatmak, serbest bırakmak, programın kaynak yönetimi açısından dikkat edilmesi gereken bir adımdır. ordr.close(); ordr = null; ocmd.dispose(); ocmd = null; ocnn.close(); ocnn = null; FieldCount: Okunan satıra ait kolon sayısını bildirir. Varsayılan olarak -1 değerine sahiptir. DELETE, UPDATE gibi sorgularda 0 değerini, kayıt dizisi döndüren sorgularda dizinin alan/kolon sayısını bildirir. HasRows: DataReader nesnesinin bir veya daha fazla satır içerip içermediğini bool türünde belirtir. DataReader nesnesinin mimarisi gereği kaç kayıt içerdiğini bilemeyeceğimizi söylemiştik. En azından HasRows özelliği kullanılarak listenin boş olup olmadığı öğrenilebilir. Aşağıdaki tabloda iki sorgu çalıştırılmış. Eğer sorgunun sonucunda Bölüm 23

44 512 C# Programlama Dili bir kayıt listesi dönüyorsa onu ekranda listeleyecek yoksa ekrana kayıt bulunamadı mesajı verecek. string CnnStr = "Server=.;Database=Kitap;Uid=sa;Pwd=123;"; SqlConnection ocnn = new SqlConnection(CnnStr); string Qry = "SELECT * FROM Musteri"; SqlCommand ocmd = new SqlCommand(Qry, ocnn); ocnn.open(); SqlDataReader ordr = ocmd.executereader(); Console.WriteLine("-- "+ ocmd.commandtext +" --"); // DataReader nesnesi herhangi bir kayıt içeriyorsa. if (ordr.hasrows) while (ordr.read()) { Console.WriteLine(oRdr["AdSoyad"]); else Console.WriteLine("Kayıt bulunamadı."); Console.WriteLine(); ordr.close(); // Aynı bağlantıyla yeni bir sorgu çalıştıralım. ocmd.commandtext = "SELECT * FROM Musteri WHERE 1=2"; Console.WriteLine("-- " + ocmd.commandtext + " --"); ordr = ocmd.executereader(); if (ordr.hasrows) while (ordr.read()) { Console.WriteLine(oRdr["AdSoyad"]); else Console.WriteLine("Kayıt bulunamadı."); ordr.close(); ocnn.close(); -- SELECT * FROM Musteri -- Ali Korkmaz Veli Geçmiş Ayşe Güler -- SELECT * FROM Musteri WHERE 1=2 -- Kayıt bulunamadı. IsDBNull(): Parametre olarak kolon numarası alarak o kolondaki değerin null olup olmadığını bool türünde değerle döndürür. while (ordr.read()) { if (ordr.isdbnull(1)) Console.WriteLine("NULL değer içermektedir."); else Console.WriteLine("NULL değer içermemektedir."); NextResult(): DataReader nesnesinin varsa bir sonraki kayıt kümesine ilerlemesini sağlar. Bu yordam sayesinde veri kaynağına gönderilen birden fazla sonuç kümesini her Papatya Yayıncılık Eğitim

45 ADO.NET Bağlantılı Sınıfları 513 defasında yeni bir komut nesnesi oluşturmaksızın işleme imkânı elde etmiş oluruz. NextResult() yordamı kayıt kümesi olduğu sürece mantıksal olarak true döndürür. SqlConnection ocnn = new SqlConnection(CnnStr); string Qry = "SELECT * FROM Musteri;SELECT * FROM Makale"; SqlCommand ocmd = new SqlCommand(Qry, ocnn); ocnn.open(); SqlDataReader ordr = ocmd.executereader(); while (ordr.read()) { Console.WriteLine(oRdr["AdSoyad"]); Console.WriteLine("----Sonraki Kayıt Kümesi----"); // Bir sonraki kayıt kümesine geçelim. ordr.nextresult(); while (ordr.read()) { Console.WriteLine(oRdr["YazarAd"]); ordr.close(); ocmd.dispose(); ocnn.close(); Ali Korkmaz Veli Geçmiş Ayşe Güler ----Sonraki Kayıt Kümesi---- Ahmet Kaymaz RecordsAffected: Bu özellik, yürütülen sorgu sonucunda kaç kaydın değiştirildiğini, eklendiğini veya silindiğini bildirir. SELECT türü sorgularda -1 değerini döndürür DbParameter Nesnesi Veri kaynağı üzerinde çalıştırılacak sorguya ait parametreleri düzenleyen, yöneten DbParameter sınıfı parametre adı, türü, uzunluğu, değeri ve yönünün tanımlandığı birçok özellik içermektedir. DbCommand nesnesinin parametre koleksiyonuna parametre eklemek için Add() yordamı kullanılır; geriye DbParameter türünde değer döndürür. DbParameter özellikleri satır satır atanabileceği gibi sınıfın yapılandırıcısı kullanılarak ta düzenlenebilir. Bu amaçla DbParameter sınıfının yapıcı yordamı yeniden yüklenmiştir. Aşağıdaki tabloda bir komut nesnesi için parametre tanımlama formatları gösterilmiştir: SqlParameter p1 = new SqlParameter(); p1.parametername = "MusteriId"; // p1.parametername = "@MusteriId"; p1.sqldbtype = SqlDbType.Int; p1.value = 1; ocmd.parameters.add(p1); Bölüm 23

46 514 C# Programlama Dili // Parametre adı ve değeri. SqlParameter p1 = new SqlParameter("MusteriId",1); p1.sqldbtype = SqlDbType.Int; ocmd.parameters.add(p1); // Parametre adı, türü ve büyüklüğü(int=4 byte). SqlParameter p1 = new SqlParameter("MusteriId",SqlDbType.Int,4); p1.value = 1; ocmd.parameters.add(p1); //Daha kısa bir ifade de kullanılabilir ocmd.parameters.add("musteriid",sqldbtype.int,4).value=1; DbParameter sınıfının diğer önemli üyesi Direction özelliğidir. Bu özellik DbCommand nesnesinin parametre koleksiyonuna eklenecek parametrenin yönünü ve davranışını bildirir. Bilindiği gibi veritabanı sistemleri üzerinde geliştirdiğimiz saklı yordam ile istemci uygulamamız arasında her iki yönde parametre tanımlayabiliriz yani programımız içerisinden yordama parametre gönderebildiğimiz gibi yordamdan programımıza parametre gönderilebilir. Direction belirteci parametrenin bu yönünü bildirir. System.Data.ParameterDirection türünde olan bu özellik aşağıdaki değerleri alır: Input: Parametrenin istemciden stored procedure e gönderilecek türden olduğunu belirtir. DbParameter nesnesinin varsayılan değeri bu seçenektir. Output: Parametrenin stored procedure den istemci uygulama yönüne aktarılacağını belirtir. InputOutput: Parametrenin hem input hem de output davranışına sahip olduğunu belirtir. ReturnValue: Veritabanı üzerinde bulunan stored procedure ve function nesnelerinden return ifadesiyle geri döndürülen değeri yakalar. SQL Sunucusundaki Musteri veritabanında GetMusteri isminde bir yordamın olduğunu düşünelim. Bu yordam dışarıdan aldığı MusteriId parametresine uygun müşteri kaydını getirmektedir. Ayrıca geriye müşterinin kaydının olup olmadığına daire 1 ve 0 değerini döndürmektedir. CREATE PROCEDURE varchar(50) OUTPUT AS FROM Musteri WHERE MusteriId=@MusteriId is null return 0 else return 1 Bu prosedürü SQL Sunucu üzerinde müşteri kodu 1 olan kayıt için çalıştıralım. Papatya Yayıncılık Eğitim

47 ADO.NET Bağlantılı Sınıfları 515 int int varchar(50) --Değişkene değer verelim. ID'si 1 olan müşteri gelsin. OUTPUT print 'Dönen değer :'+ CAST(@Sonuc as char) Ali Korkmaz Dönen değer :1 Kullanıcı/istemci uygulamamızda bu yordamı kullanmak için aşağıdaki gibi bir parametre yapısı tanımlanır. SqlConnection ocnn = new SqlConnection(CnnStr); SqlCommand ocmd = new SqlCommand(); ocmd.commandtext = "GetMusteri"; ocmd.commandtype = CommandType.StoredProcedure; ocmd.connection = ocnn; SqlParameter p1 = new SqlParameter("@MusteriId", SqlDbType.Int,4); p1.direction = ParameterDirection.Input; p1.value = 1; ocmd.parameters.add(p1); SqlParameter p2 = new SqlParameter("@AdSoyad",SqlDbType.VarChar,50); p2.direction = ParameterDirection.Output; ocmd.parameters.add(p2); SqlParameter p3 = new SqlParameter("@DonenSonuc",SqlDbType.Int, 4); p3.direction = ParameterDirection.ReturnValue; ocmd.parameters.add(p3); ocnn.open(); ocmd.executenonquery(); string AdSoyad = ocmd.parameters["@adsoyad"].value.tostring(); int Sonuc =(int)ocmd.parameters["@donensonuc"].value; Console.WriteLine(AdSoyad); Console.WriteLine("Dönen Değer : " + Sonuc); Ali Korkmaz Dönen Değer : DbTransaction Nesnesi DbTransaction nesnesi veritabanı sistemine gönderdiğimiz sorguya ait işlem-bilgi (transaction) yönetimini sağlamak için kullanılır. Bilindiği gibi transaction (işlembilgi) kavramı, veritabanı yönetim sistemlerinde önemli bir yere sahip olup birbirleriyle ilişkili iş parçacıklarının bulunduğu mantıksal bir birim olarak düşünülebilir. İşlem yönetimi, aynı iş parçacığında seri bir şekilde yürütülecek sorguların hatasız bir şekilde sonuçlanıp sonuçlanmadığının kontrolünü sağlar. Klasik bir örnek olarak Bölüm 23

48 516 C# Programlama Dili banka sisteminde havale uygulamasını ele aldığımızda ilk önce havaleyi yapan kişinin hesabından ilgili miktar düşürülür ve havale yapılan kişinin hesabına o kadar miktar eklenir. Dışarıdan basit görünen bu sürecin herhangi bir halkasında meydana gelecek bir hata kaynak veya hedef siteminde bilgi tutarsızlığına neden olacaktır. Bu yüzden bu işlemlerin işlem-bilgi yönetiminde gerçekleştirilmesi gerekir. Ya hep ya hiç mantığıyla çalışan işlem-bilginin esas amacı veri bütünlüğü olup süreç içerisinde istenmeyen bir durum olduğu zaman önceki iş parçacıklarının geri alınmasıdır. Yani süreç, başlangıç noktasına geri döndürülür ve süreç içindeki hiçbir iş parçacığının gerçekleşmemiş olduğu kabul edilir. Büyük ölçekli VTYS lerde dâhili işlem-bilgi yönetimi bulunmaktadır. Örneğin SQL Sunucusunda çalıştırdığımız bir UPDATE veya DELETE işlemi esnasında herhangi bir kesinti olduğu zaman o zamana kadar gerçekleştirilmiş güncelleme veya silme işlemleri geri alınır ve veriler eski haline geri dönmüş olur. Bu geri alma işlemine ROLLBACK (geri sarmak), işlemler hatasız çalıştığı durumda değişikliklerin kalıcı hale getirilmesine COMMIT (işlemek/onaylamak) denilir. ADO.NET te işlem-bilgi (transaction) yönetimini sağlayan temel sınıf System.Data. Common.DbTransaction sınıfıdır. ADO.NET 2.0 dan önceki sürümlerde bu sınıf temel alınarak sağlayıcılara bağlı transaction sınıfları oluşturulmuştur. ADO.NET 2.0 ile birlikte transaction yönetimi, sağlayıcılardan bağımsız da yapılabilir hale getirildi ve bu amaçla System.Transactions.dll kütüphanesi geliştirildi. Öncelikle sağlayıcılara bağımlı yapı üzerinde çalışacağız. DbTransaction sınıfının üyelerini listeleyelim: Connection: Transaction nesnesinin ilişkili olduğu Connection nesnesini belirtir. Commit(): Transaction sürecinin başarı olması durumunda verileri, veritabanı üzerinde kalıcı kılar. IsolationLevel: İşlem-bilgilerin birbirileriyle olan yalıtım seviyesini belirtir (ReadUncommitted, ReadCommitted, RepeatableRead...). Rollback(): Hatalı iş parçacığına kadar yapılmış işlemleri geri sarıp, süreci işlenmemiş kılar. Sorgularımızı işlem yönetiminde gerçekleştirmek için öncelikle Connection ve Com-mand nesnesi tanımlanır. Veri kaynağıyla bağlantı kurmak amacıyla Connection nesnesinin Open() yordamı çağrılır. Ardından Connection nesnesinin Begin-Transaction() yordamı kullanılarak bir Transaction nesnesi oluşturulur. Bu nesne Command nesnesiyle ilişkilendirilir. Sorgumuzun başarılı olup olmadığını yakalamak için try-catch bloğu kullanılır. Eğer sorgu başarılı olursa yani try bloğu tümüyle işlendiği zaman Transaction nesnesi Commit() edilir. Sorgunun başarısız olması durumunda, yani program catch bloğuna girerse Transaction nesnesi Rollback() edilir. Aşağıdaki tabloda basit bir işlem-bilgi yönetimini bulabilirsiniz. Papatya Yayıncılık Eğitim

49 ADO.NET Bağlantılı Sınıfları 517 SqlConnection ocnn = new SqlConnection(CnnStr); string Qry = "UPDATE Musteri SET AdSoyad='Ayşe Güler' WHERE MusteriId=3;"; SqlCommand ocmd = new SqlCommand(Qry, ocnn); ocnn.open(); //Transaction nesnesi oluşturalım. SqlTransaction otrs = ocnn.begintransaction(); //Transaction nesnesini Command nesnesiyle ilişkilendirelim. ocmd.transaction = otrs; try { ocmd.executenonquery(); //Sorgu başarıyla yürütüldüğü için işlemi kalıcı yapalım otrs.commit(); catch { //Sorgu başarısız olduğu için işlemi geri alalım otrs.rollback(); finally { //Her durumda bağlantıyı kapat ocmd.dispose(); ocnn.close(); Görünürde bir hata olmadığı için bu şekilde UPDATE cümlesi başarıyla sonuçlanır. SQL Server Profiler aracında işlem-bilgi (transaction) olaylarını incelediğimizde veritabanı sunucusu tarafından da işlemin uygulandığını görmüş oluruz. Gerçekten transaction yönetimini kullanıp kullanmadığını sınamak için sorgu yürütüldükten sonra sisteme hata verdirelim! try { ocmd.executenonquery(); //Burada sistem hata versin. throw new Exception("Hata Oluştu."); //Sorgu başarıyla yürütüldüğü için işlemi kalıcı yapalım otrs.commit(); catch { //Sorgu başarısız olduğu için işlemi geri alalım otrs.rollback(); Bölüm 23

50 518 C# Programlama Dili Örneğimizi biraz daha geliştirip aynı transaction bünyesinde çalışacak iki Command nesnesi kullanalım. İlk Command nesnesi Musteri tablosundaki 1 numaralı, ikinci Command nesnesi Makale tablosundaki 1 numaralı makalenin kaydını güncelleyecek. SqlConnection ocnn = new SqlConnection(CnnStr); string Qry1 = "UPDATE Musteri SET AdSoyad='Ayşe Güler1' WHERE MusteriId=3"; string Qry2 = "UPDATE Makale SET YazarAd='Ahmet Kaymaz1' WHERE MakaleId=1"; SqlCommand ocmd1 = new SqlCommand(Qry1, ocnn); SqlCommand ocmd2 = new SqlCommand(Qry2, ocnn); ocnn.open(); //Transaction nesnesi oluşturalım. SqlTransaction otrs = ocnn.begintransaction(); //Her iki command nesnesini otrs ile ilişkilendirelim ocmd1.transaction = otrs; ocmd2.transaction = otrs; try { ocmd1.executenonquery(); ocmd2.executenonquery(); otrs.commit(); catch { otrs.rollback(); finally { ocmd1.dispose(); ocmd2.dispose(); ocnn.close(); Bu iki sorgu bu haliyle sorunsuz çalışacaktır. İşlem-bilgi yönetiminin etkisini görmek için ikinci sorgumuzda bilinçli bir hata yapalım. Makale tablosunda olmayan bir kolonu güncelleyemeye çalışalım. string Qry1 = "UPDATE Musteri SET AdSoyad='Ayşe Güler1' WHERE MusteriId=3"; string Qry2 = "UPDATE Makale SET OlmayanKolon='Ahmet Kaymaz' WHERE MakaleId=1"; Bu iki sorgu aynı işlem-bilgi bünyesinde çalıştığı için ikinci sorgunun başarısız olmasından dolayı ilk sorgu geri alınır. Papatya Yayıncılık Eğitim

51 ADO.NET Bağlantılı Sınıfları 519 Farklı veritabanı sunucusu üzerinde yapılan işlemler için dağıtık işlem-bilgi (distributed transaction) yönetimi kullanılır. Bu yönetimi Microsoft un COM+ teknolojisinin Component Services servisi tarafından gerçekleştirilir. Bu yöntem programatik olarak zor olduğu için burada buna alternatif olarak kullanılan ADO.NET 2.0 ile birlikte gelmiş işlem yaklaşımını inceleyeceğiz ADO.NET 2.0 Transaction (İşlem-bilgi) Yönetimi ADO.NET 2.0 ile birlikte programcının yerel ve dağıtık işlem-bilgi daha kolayca yönetebilmesi için System.Transactions.dll kütüphanesi sunuldu. ADO.NET 2.0 da LightWeight Transaction (LT) ve OLE Transaction (OleTx) olmak üzere iki işlem-bilgi modeli geliştirildi. LightWeight Transaction tek veri kaynağı üzerinde sorgulama yapılırken kullanılan işlem-bilgi modeli OleTx Transaction ise dağıtık işlem-bilgi yapısında olup ayrı veri kaynakları üzerinde işlem yapılırken kullanılan modeldir. Bu yeni sistemi kullanabilmek için projemize System.Transactions.dll dosyasını referans olarak eklememiz gerekir. İşlem yönetimi için TransactionScope sınıfı kullanılır. Bu sınıfı kullanarak işlem yönetiminde çalışacak nesneler için bir kapsama alanı oluşturulur. İşlem bloğu.net 2.0 ile birlikte gelmiş olan using ifadesiyle oluşturulur. Önceki sayfalarda anlatıldığı üzere ADO.NET te klasik işlem-bilgi yönetimi aşağıdaki gibi yapılır: SqlConnection ocnn = new SqlConnection(CnnStr); string Qry = "UPDATE Musteri SET AdSoyad='Ayşe Güler' WHERE MusteriId=3;"; SqlCommand ocmd = new SqlCommand(Qry, ocnn); ocnn.open(); // Transaction nesnesi oluşturalım. SqlTransaction otrs = ocnn.begintransaction(); Bölüm 23

52 520 C# Programlama Dili // Transaction nesnesini Command nesnesiyle ilişkilendirelim. ocmd.transaction = otrs; try { ocmd.executenonquery(); // Sorgu başarıyla yürütüldüğü için işlemi kalıcı yapalım. otrs.commit(); catch { // Sorgu başarısız olduğu için işlemi geri alalım. otrs.rollback(); finally { // Her durumda bağlantıyı kapat. ocmd.dispose(); ocnn.close(); Bu işlemi TransactionScope sınıfını kullanarak daha az kodla gerçekleştirebiliriz. İşlem-bilgi başarılı olduğunda işlemleri kalıcı yapmak için TransactionScope sınıfının Complete() yordamı kullanılır. TransactionScope nesnesinin kullanım biçimi şu şekildedir: using(transactionscope scope = new TransactionScope()) { /* Transaction tabanlı işlemler*/ //Herhangi bir hata oluşmazsa Transaction'ı commit et scope.complete(); Önceki örneği bu yeni yöntemle yazalım. using System.Transactions;... using (TransactionScope otrs = new TransactionScope()) { string CnnStr = "Server=.;Database=Kitap;Uid=sa;Pwd=123;"; SqlConnection ocnn = new SqlConnection(CnnStr); string Qry = "UPDATE Musteri SET AdSoyad='Ayşe Güler1' WHERE MusteriId=3;"; SqlCommand ocmd = new SqlCommand(Qry, ocnn); ocnn.open(); try { ocmd.executenonquery(); //Transaction'ı COMMIT et otrs.complete(); catch(exception ex) { Console.WriteLine("HATA OLUŞTU : "+ ex.message); Aynı şekilde iki farklı Command nesnesini de işlem-bilgi yönetimine alabiliriz. Bu örnekte tek veri kaynağı üzerinde işlem yapıldığı için LightWeight Transaction tipi kullanılmış oldu. TransactionScope nesnesinin asıl kolaylığını OleTx Papatya Yayıncılık Eğitim

53 ADO.NET Bağlantılı Sınıfları 521 Transaction tipi üzerinde görebiliriz. Aşağıdaki örnekte iki farklı veritabanı sunucusu üzerinde işlem yapılarak dağıtık transaction ortamı oluşturulmuştur. İki farklı veritabanı sunucusu üzerinde işlem yapıldığı için ADO.NET, programcının ek bir şey yapmasına fırsat vermeyerek bu işlem dağıtık olarak kullanacaktır. using (TransactionScope otrs = new TransactionScope()) { string CnnStr1 = "Server=DW1;Database=DB;Uid=sa;Pwd=sa;"; string CnnStr2 = "Server=DW2;Database=DB;Uid=sa;Pwd=sa;"; SqlConnection ocnn1 = new SqlConnection(CnnStr1); SqlConnection ocnn2 = new SqlConnection(CnnStr2); string Qry1 = "UPDATE Musteri SET AdSoyad='Ayşe Güler' WHERE MusteriId=3;"; string Qry2 = "UPDATE Urun SET Fiyat='35' WHERE UrunId=12;"; SqlCommand ocmd1 = new SqlCommand(Qry1, ocnn1); SqlCommand ocmd2 = new SqlCommand(Qry2, ocnn2); try { ocnn2.open(); ocnn1.open(); ocmd1.executenonquery(); ocmd2.executenonquery(); otrs.complete(); catch (Exception ex) { Console.WriteLine(ex.Message); //using Dağıtık işlem-bilgi Microsoft Distributed Transaction Coordinator (MSDTC) tarafından yönetilir. Bu servisi doğru bir şekilde yapılandırdıktan sonra aynı uygulama alanı (application domain) içerisindeki iki farklı sunucu bağlantısı üzerinde etkin bir transaction yönetimi gerçekleştirmiş oluruz. DTC, her iki veritabanı sunucu üzerindeki DTC lerden işlemleri doğru yapılıp yapılmadığına dair geri bildirim bekler. Gelen sonuç olumluysa TransactionScope nesnesi, Complete() edilir. Görüldüğü gibi ADO.NET 2.0 daki bu özellik, programcının COM+ programlamayla uğraşmasına gerek kalmadan System.Enterprise servislerini kullanmadan dağıtık transaction işlemini daha az kodla kolaylıkla ve daha performanslı gerçekleştirmeyi sağlamıştır. TransactionScope sınıfının yapıcı yordamı yeniden yüklenmiş (overload) olup aktif işlem-bilginin davranış biçimini ve zaman aşım süresini parametre olarak alır. Bu parametreler daha çok içiçe kullanılmış işlem-bilgi yönetiminde işlev kazanmaktadır; İçiçe işlem-bilgi, birinin başarıyla sonuçlanması için başka bir işlem-bilginin başarıyla bitmesini beklemesidir. ADO.NET ile bu içiçe transaction işlemleri de kolaylıkla yapılabilmektedir. Bölüm 23

54 522 C# Programlama Dili [Dolaysız] using (TransactionScope scope1 = new TransactionScope()) { using (TransactionScope scope2 = new TransactionScope()) { scope2.complete(); scope1.complete(); [Dolaylı] void Main() { using(transactionscope scope = new TransactionScope()) { /* Transaction tabanlı işlemler */ AraYordam(); scope.complete(); //Main void AraYordam() { using(transactionscope scope = new TransactionScope()) { /* Transaction tabanlı işlemler */ scope.complete(); //AraYordam TransactionOptions sınıfı kullanılarak işlem-bilgi bloğu için yalıtım seviyesi ve zaman aşımı süresi de tanımlanabilir. Aynı zamanda içiçe işlem-bilgide içteki işlem blokları için hangi kapsam alanında etkili olacağını belirleyebiliriz. Bunun için TransactionScopeOption numaralandırması kullanılır. IsolationLevel, kullanıcı işlemlerin veya başka işlem-bilginin o anki işleme olan duyarlılığını ve verilere nasıl davranacağını bildirir. İşlem/etkileşimdeki yalıtım seviyeleri, aynı verilere yapılan ortak zamanlı erişim sorunlarını çözmek için kullanılır. Burada bu seçeneklerle ilgili bir iki cümle yazmak yararlı olacaktır: Papatya Yayıncılık Eğitim

55 ADO.NET Bağlantılı Sınıfları 523 Chaos: Değiştirmeye çalıştığımız veriler üzerinde başkası güncelleme yapıyorsa bizim değiştirmemize izin verilmez. Bu seçenek, SQL Sunucu tarafından desteklenmemektedir. ReadCommited: Okumaya çalıştığımız veriler üzerinde o anda başkası güncelleme yapıyorsa o kişinin ancak COMMIT veya ROLLBACK ettiği verilerini okuyabiliriz. Örneğin bir tablodaki X satırını okuduk ve okuma işlemini açık bıraktık. Bu arada başka bir işlem-bilgi bu satırı güncelledi. Fakat COMMIT etmedi. Bu durumda o- kuma işlemi aynı satırı okumaya çalıştığında hata oluşur (Dirty Read sorunu). İşlem/etkileşim, varsayılan olarak ReadCommited seçeneğine sahiptir. ReadUncommited: En düşük yalıtım seviyesi olup diğer işlem-bilgilerin henüz COMMIT edilmemiş verilerini de okuyabilmeyi sağlar. İkinciişlem tarafından yapılmış değişikliklerin birincisi tarafından okunabilmesi için ikincisinin COMMIT veya ROLLBACK yapması beklenmez. RepeatableRead: Diğer işlem-bilgilere ait COMMIT edilmiş verilere erişilir; ancak, veriler okunurken diğer kullanıcıların o veriler üzerinde değişiklik yapmasını engeller. Yani SELECT işlemi çalışırken o anda başka bir kullanıcının UPDATE, DELETE işlemi yapması engellenir fakat INSERT yapmasına izin verilir. Serializable: En yüksek yalıtım seviyesi olup verileri okurken veritabanını kilitleyerek diğer kullanıcıların hiçbir şekilde hiçbir ekleme ve güncelleme yapmaması sağlar. Bir işlem-bilgiin ekleme, güncelleme veya silme yapabilmesi için aktif işlemin bitmesini bekler. UPDATE, DELETE veya INSERT işlemleri yapılırken hiçbir şekilde o anda başka bir kullanıcı aynı verileri SELECT edemez. Bu seçenek performans açısından tercih edilmez ve aktif transaction uzun sürdüğü zaman deadlock denilen ölümcül kilitlenme sorunu yaşanabilir. Diğer konu, içiçe olan işlem-bilgi bloklarının faaliyet alanlarının belirlenmesidir. Bunun için TransactionScopeOption seçenekleri kullanılır. Bu seçenekler içteki işlemin üstteki işlemin etki alanına eklenip eklenmeyeceğini bildirir: Required: Var ise önceki işlem-bilgiye ait kapsama alanına ekler; yoksa yeni bir kapsama alanı yaratır. Yani daha önce oluşturulmuş bir işlem-bilgi varsa yeni işlemi ona dâhil eder; yoksa yeni bir işlem-bilgi başlatır. RequiresNew: Yeni bir işlem-bilgi kapsama alanı yaratır; varsa öncekini askıya a- lır. Ana kapsama alanı (root scope) kendisi olmuş olur. Suppress: Herhangi bir işlem-bilgi içerisine dâhil etmez. Aşağıdaki tabloda TransactionOptions sınıfının kullanımı gösterilmiştir: TransactionOptions oopts = new TransactionOptions(); oopts.isolationlevel = IsolationLevel.ReadCommitted; oopts.timeout = new TimeSpan(0, 0, 60); using (TransactionScope otrs = new TransactionScope( TransactionScopeOption.RequiresNew,oOpts)) {... İçiçe işlem-bilgilerin kapsama alan belirlemelerini bir şekil üzerinde gösterelim. Bölüm 23

56 524 C# Programlama Dili static void Main(string[] args){ Yordam1(); Yordam3(); //Main public static void Yordam1() { using (TransactionScope scope1 = new TransactionScope(TransactionScopeOption.Required)) { Yordam2(); scope1.complete(); //Yordam1 public static void Yordam2() { using (TransactionScope scope2 = new TransactionScope(TransactionScopeOption.RequiresNew)) { Yordam3(); using (TransactionScope scope4 = new TransactionScope(TransactionScopeOption.Suppress)) { scope4.complete(); scope2.complete(); //Yordam2 public static void Yordam3() { using (TransactionScope scope3 = new TransactionScope(TransactionScopeOption.Required)) { scope3.complete(); //Yordam3 Bu örnek, simpleisbest.net ten alınmıştır. Main() Yordam1() Scope1[Required] Transaction A Transaction B Scope2[RequiresNew] Scope3[Required] Scope4[Suppress] Yordam2() Scope3[Required] Transaction C Şekil İçiçe işlemlerin kapsama alanları Papatya Yayıncılık Eğitim

57 ADO.NET Bağlantılı Sınıfları Savepoint Kavramı Gelişmiş veritabanı sistemleri, işlem-bilgilerde Savepoint denilen saklama noktaları tanımlama imkanı verir. İşlem birden fazla iş parçacığından oluştuğu için başarılı biten her iş parçacığının bitiş noktasını savepoint olarak işaretlemek, özellikle karmaşık işlem-bilgilerde rollback işleminin yükünü hafifletir. Çünkü bu durumda işlem içerisinde rollback yapmak gerektiği zaman işlem, en başa geri sarılmak yerine, sadece belirli bir savepoint noktasına geri döndürülür. İşlem/etkileşim içerisinde bir kaydetme noktası tanımlamak için SqlTransaction (veya OracleTransaction) sınıfının Save() yordamı kullanılır. Bu yordam oluşturacağı saklama-noktasının adını string türünde parametre olarak alır. Saklanmış olan noktaya geri dönmek için RollBack() yordamına parametre olarak o noktanın adı geçilir. İşlem/etkileşim içerisinde herhangi bir saklama noktasına rollback işlemi yapıldığı zaman işlemin başından o noktaya kadar yapılan işler COMMIT edilir. Aşağıdaki örneği inceleyelim: SqlConnection ocnn = new SqlConnection(CnnStr); string Qry1 = "UPDATE Musteri SET AdSoyad='Ayşe Güler1' WHERE MusteriId=3"; string Qry2 = "UPDATE Makale SET YanlisKolon='Ahmet Kaymaz' WHERE MakaleId=1"; SqlCommand ocmd = ocnn.createcommand(); ocnn.open(); //Transaction nesnesi oluşturalım SqlTransaction otrs = ocnn.begintransaction(); //Command nesnesini otrs ile ilişkilendirelim ocmd.transaction = otrs; try { ocmd.commandtext = Qry1; ocmd.executenonquery(); //Eğer ilk sorgu sorunsuz çalıştırdıysa otrs.save("query1");//gelinen bu noktayı kaydet ocmd.commandtext = Qry2; ocmd.executenonquery(); otrs.commit();//1 catch(exception ex) { Console.WriteLine("HATA OLUŞTU: "+ ex.message); //otrs.rollback(); Tüm transaction'ı rollback eder //Tüm transaction'ı değil "Query1" ismindeki noktaya kadar geri al otrs.rollback("query1"); //Transaction "Query1" noktasında bulunmaktadır. Başından o noktaya kadar COMMIT et. otrs.commit();//2 finally { //Bu örnekte sadece 2.sorguda hata yapılacağını bildiğimiz için her durumda COMMIT işlemi yapılacağından üstteki 1 ve 2 olarak işaretlenmiş COMMIT satırlarını iptal edip finally bloğuna ekleyebilirdik. Bölüm 23

58 526 C# Programlama Dili //otrs.commit(); ocmd.dispose(); ocnn.close(); HATA OLUŞTU: Invalid column name 'YanlisKolon' Özet ADO.NET bağlantılı sınıfları veri kaynağı ile kullanıcı/istemci uygulaması arasında veri aktarımını tanımlı sağlayıcılara bağlı olarak gerçekleştirir. Örneğin SQL Sunucusuna bağlanmak için SqlConnection nesnesi, Oracle sistemine bağlanmak için OracleConnection nesnesi kullanılır. Veri sağlayıcılarına göre değişen bu nesneler temelde System.Data.Common altındaki taban sınıfları temel almışlardır. DbConnection, DbCommand, DbDataReader sınıfları bu temel sınıflardan bazılarıdır. Herhangi bir veri kaynağıyla iletişim kurmak için DbConnection, kaynak üzerinde sorgu yürütmek için DbCommand, sorguya parametre atamak için DbParameter, kaynaktan kayıt listesi elde etmek için DbDataReader ve sorguları işlem-bilgi yönetiminde işlemek için DbTransaction sınıfı kullanılır Sorular 23.1) Bağlantı cümlesi (connection string) hangi bilgileri içerir ve ne amaçla kullanılır? 23.2) DbConnection, DbCommand ve DbDataReader sınıflarını örneklendiriniz? 23.3) ADO.NET te işlem-bilgi (transaction) yönetimi nasıl gerçekleştirilir? Açıklayınız. 23.4) SavePoint kavramı nedir ve hangi amaçla kullanılır? 23.5) Transaction yönteminden yalıtım seviyelerini örnekle açıklayınız. Papatya Yayıncılık Eğitim

59 24. ADO.NET Bağlantısız Sınıfları (DisConnected Classes) ADO.NET in en önemli özelliği bağlantısız veri katmanı sunuyor olmasıdır. Bağlantılı (connected) kavramıyla verinin asıl bulunduğu yer olan veritabanı, bağlantısız (disconnected) kavramıyla da kullanıcının/istemcinin verileri geçici olarak tuttuğu bellek kastedilmektedir. Bağlantısız katmanında bağlantı kapalıyken de veriye ulaşılır; veriler üzerindeki değişiklik yerel bellek üzerinde yapılır ve daha sonra veri kaynağıyla bağlantı kurularak değişiklikler kaynağa aktarılır. Bağlantılı ve bağlantısız katman arasındaki geçişi bağdaştırıcı (Data Adapter) nesneleri sağlar. Bağlantısız katmanda veriler DataSet ve DataTable nesneleri üzerinde tutulur. Bu bölümde bu nesneler ayrıntılı olarak ele alınmaktadır DbDataAdapter Nesnesi DataAdapter nesnesi bağlantılı ve bağlantısız yapılar arasında geçit görevi görür. Veri sağlayıcısına bağlı olduğu için OleDbDataAdapter, SqlDataAdapter ve OracleDataAdapter gibi değişik sağlayıcılara özgü bir bağdaştırıcı bulunur. System.Data.Common.DbDataAdapter sınıfından türemiş bu bağdaştırıcılar bağlantısız ile bağlantılı katmanlar arasındaki geçişi, veri kaynağıyla DataSet nesnesi arasında çift yönlü iletişim kurarak sağlar. Yani verileri kaynaktan alarak istemcinin belleğinde DataSet ve DataTable gibi bağlantısız nesneler içerisinde saklar; ve aynı şekilde istemci tarafında yapılan veri güncellemeleri de aynı yoldan kaynağa yansıtılır. Bağlantısız mimarinin iki önemli nesnesi mevcuttur: DataAdapter ve DataSet. DataAdapter nesnesi veri çekilmesi ve üzerinde değişiklik yapılabilmesi amacıyla SelectCommand, InsertCommand, UpdateCommand ve DeleteCommand olarak adlandırılan dört tane komut nesnesi içerir. DataAdapter nesnesinin en önemli üyeleri olan bu özellikler, bağdaştırıcının veri çekme, ekleme, güncelleme ve silme durumunda çalıştıracağı komut nesnelerini ve ifadelerini temsil ederler. DataAdapter

60 528 C# Programlama Dili nesnesinin mutlaka bir SelectCommand tanımlaması olmak zorundadır. Nitekim yeniden yüklenmiş DataAdapter yapıcı yordamı, kullanacağı Connection ve SelectCommand nesnelerini parametre olarak alır. Bunların haricinde DataAdapter sınıfının Fill() ve Update() adlı iki önemli üyesi daha bulunur: Fill(): Bu yordam aktif bağlantı nesnesi üzerinden SelectCommand sorgusunun döndürdüğü kayıt dizisini DataSet veya DataTable içerisine aktarır. Daha sonraki o- kuma veya güncellemeler doğrudan buradan gerçekleşir. Yeniden veri kaynağıyla bağlantı kurulmaz. DataAdapter nesnesinin en önemli özelliği, bağlantı nesnesini, ihtiyaç duyduğu zaman kendisinin aktifleştirmesidir. Update(): Yerel bellekte DataSet içerisinde bulunan veriler üzerinde yapılan değişiklikleri veri kaynağına yansıtır. Bunların dışında eğer sadece sorgunun şeması DataSet e aktarılacaksa FillSchema() yordamı kullanılır. Bu durumda DataSet herhangi bir veri içermez. DataAdapter nesnesinin SelectCommand özelliğine atanmış sorgular parametre içerebilir. Bu parametreleri elde etmek için GetFillParameters() yordamı kullanılır. Bu yordam IDataParameter arayüzü türünde dizi döndürür. Bağlantısız veri işleme süreci aşağıdaki gibi temsil edilebilir: Veri Sağlayıcı Bağlantı Komut Command Builder DataAdapter Fill() Update() DataSet DataTable Yerel veritabanı Kullanıcı Belleği Fiziksel Veritabanı (Sunucu Disk) Şekil DataAdapter nesnesinin veri işleme adımları Fill() çağrıldığında veritabanındaki kayıtlar okunur ve istemci tarafındaki yerel bellek üzerinde bir DataTable nesnesine aktarılır. Bu veriler üzerinde ekleme, silme veya güncelleme yapıldıktan sonra bu değişiklikleri aktif sistem üzerinde kalıcı yapmak için Update() yordamı çağrılır; öncelikle yereldeki tablo taranır ve değişikliğe uğramış satırlar için yerleştir (insert), güncelle (update) veya sil (delete) sorgularını çalıştırır. Dolayısıyla DataAdapter ın verilerin tarihçesini tuttuğu söylenebilir. Papatya Yayıncılık Eğitim

61 ADO.NET Bağlantısız Sınıflar 529 PÜF! DataAdapter sınıfının Update() yordamı değişikliğin başarıyla yapılabilmesi için veritabanı üzerinde birincil anahtar olmasını şart koşar DataSet ve DataTable Nesneleri ADO.NET in DataSet ve DataTable olarak adlandırılan iki temel bağlantısız sınıfı vardır. DataSet, içerisinde veri tablolarını barındıran, ana veri kaynağından bağımsız olarak istemcinin yerel belleğinde yaşayan bir nesnedir. İçerisinde birbirleriyle ilişkili birden fazla tablo içerebiliyor olması; constraint, relationship ve key gibi kavramları destekliyor olması; ve farklı veri kaynaklarından beslenebiliyor olması sebebiyle küçük bir veritabanı olarak tanımlayabileceğimiz DataSet nesnesi teknik olarak DataTable kümelerinden oluşur ve veriyi XML formatında saklar. Aşağıda Msdn den alınmış DataSet nesne modeli görülmektedir: Şekil DataSet nesne modeli DataSet eleman olarak nesne koleksiyonlarından oluşur. Bunların başında da DataTableCollection gelir; bu, DataSet içerisinde bulunan DataTable nesnelerini içerir. DataTable nesnesi kolon ve satırlara sahip tipik bir veritabanı tablosu özelliğine sahiptir ve DataSet deki verilerin bulunduğu asıl yerdir. Bu ikisi veri sağlayıcılarından bağımsız çalışır. Bu yüzden her iki nesne de doğrudan System.Data isimuzayı altında bulunur. DataTable nesnesi DataSet yapısından bağımsız çalışır; yani, uygulama içerisinde DataSet olmadan da bellek üzerinde bir DataTable nesnesi oluşturulabilir. Bölüm 24

62 530 C# Programlama Dili DataTable nesnesinin içerdiği kolonlar DataColumnCollection, içerdiği veri satırları DataRowCollection, içerdiği kısıtlar da ConstraintCollection tarafından temsil edilir. Tablo içerisindeki bir satıra erişmek için DataRow sınıfı kullanılır. DataSet birden fazla tablo içerdiği gibi bu tablolar arasında ilişki kurulmasını da sağlar. Tablolar arasındaki UniqueKeyConstraint ve ForeignKeyConstraint tabanlı ilişkiler DataRelationCollection sınıfı tarafından temsil edilir. Aşağıdaki kodlarda en basit haliyle DataAdapter ve DataSet tanımlama örneği gösterilmiştir: string CnnStr SqlConnection ocnn = new SqlConnection(CnnStr); SqlCommand ocmd = new SqlCommand("SELECT * FROM Musteri",oCnn); // Sql Provider için bir DataAdapter tanımlayalım. SqlDataAdapter oda = new SqlDataAdapter(); // DataAdapter'in select komutunu belirleyelim. oda.selectcommand = ocmd; // Verikaynağından çekeceğimi verileri tutacak DataSet. DataSet ods = new DataSet(); //DataAdapter'in aldığı verileri DataSet'e aktaralım. oda.fill(ods); // Verileri doğrudan bir DataTable'e de aktabiliriz. Örnekten de görüleceği gibi SQL Sunucu ile bağlantı kurulması için herhangi bir yerde bağlantıyı Open() ile açmıyoruz. DataAdapter ün özelliği olarak gerektiği zaman (Fill() yordamı çağrıldığı zaman) ilişkili olduğu bağlantı nesnesi Open() ile açılır ve işlem bittikten sonra Close() ile kapatılır. Eğer bağlantı DataAdapter den bağımsız olarak açılmışsa işlem bitirildikten sonra açık bırakılır. DataSet ve DataTable nesnelerini daha verimli ve yerinde kullanmak için üyelerini tanımak yararlı olacaktır. Öncelikle DataTable sınıfının önemli üyelerini yazalım: CaseSensitive: Karakter karşılaştırmalarında büyük küçük harf duyarlılığın olup olmayacağını bildirir. Columns: Kolonlara ait bilgileri DataColumnCollection koleksiyonu türünde döndürür. Belirli bir kolona erişmek için Columns özelliğine o kolonun adı veya indis bilgisi parametre olarak gönderilir. Constraints: Kısıtları yönetir. DataSet: Varsa DataTable in ait olduğu DataSet i bildirir. DefaultView: Bu özellik System.Data.DataView türünde değer döndürür. HasErrors: Tablonun güncellenmesi aşamasında hata bilgisi döndürür. Locale: Metinsel verileri karşılaştırmada kullanılacak yerel-kültür bilgisini yerleştirir veya döndürür. System.Globalization.CultureInfo türünde değer döndürür. Bilindiği gibi CultureInfo sınıfı dil, tarih, saat ve sayı gibi bölgesel ayarları etkileyen kültürel bilgiye erişmemizi veya onu yönetmemizi sağlar. Papatya Yayıncılık Eğitim

63 ADO.NET Bağlantısız Sınıflar 531 DataTable odt = new DataTable(); System.Globalization.CultureInfo oci; oci = odt.locale; Console.WriteLine(oCi.DisplayName, oci.englishname); Turkish (Turkey) ParentRelations: Tablonun diğer tablolarla ilişkisini içerir. Prefix: DataTable içerisindeki verinin dönüştürüldüğü XML e ait isimuzayının adını belirtir. PrimaryKey: Veri tablosu için birincil anahtar tanımı/erişilmesi için kullanılır. RemotingFormat: ADO.NET 2.0 ile birlikte gelmiş olan bu özellik serileştirme işleminde tablonun dönüştürüleceği formatı (Binary veya XML) bildirir. Rows: DataTable deki satırları DataRowCollection koleksiyon türünde döndürür. AcceptChanges(): Veri tablosu üzerinde yapılmış değişiklikleri onaylar. Yani bu yordam çağrıldıktan sonra tablodaki değişiklik bilgisi yok olur. Clear(): DataTable içerisindeki tüm kayıtları siler. Clone(): Veri tablosunu şema ve kısıtlar dâhil olmak üzere yapısıyla birlikte kopyalar; veriler kopyalanmaz. Compute(): DataTable de T-SQL grupsal fonksiyonların çalıştırılmasını sağlar. Copy(): Veri tablosunu yapısı ve verisiyle birlikte kopyalar. CreateDataReader(): DataTable içerisindeki uygun verilerden (silinmiş olarak işaretlenmişler hariç) DataTableReader nesnesi oluşturur. GetChanges(): DataTable a son yükleme veya AcceptChanges() çağrılmasından sonra yapılmış değişiklikleri bildirir. DataTable türünde değer döndürür. ImportRow(): Kaynak tablodan aynı yapıda başka bir veri tabloya bir satırı bütün özelliklerini koruyarak kopyalar. Load(): IDataReader arabirimini destekleyen kayıt kümelerindeki verileri DataTable nesnesine aktarır. Eğer satır daha önce varsa bu ikisi birleştirilir. LoadDataRow(): Koleksiyona uyan satırı bulur ve günceller; eğer yoksa yeni satır olarak eklenir. Bu yordamı çalıştırmadan önce BeginLoadData(), çalıştırdıktan sonra da EndLoadData() yordamları çağrılmalıdır. Merge(): İki DataTable içerisindeki verileri birleştirir. Yeniden yüklenmiş olan bu yordam tablolardaki tüm verileri de birleştirebilir ya da sadece silinmek üzere olanların dışındakileri birleştirir. NewRow(): Var olan veri tablosuna yeni bir satır ekler. ReadXml(): Büyük bir kolaylık sağlayan bu yordam xml dosyasını var olan veri tablosuna aktarır. RejectChanges(): O ana kadar yapılmış değişiklikler iptal (Rollback) edilir. Reset(): Veri tablosunu sıfırlar; yani, tablo şemasını ve tüm satır ve kolon bilgileri yok eder. Bölüm 24

64 532 C# Programlama Dili Select(): Çok kullanılan bir yordamdır; tablodaki kayıtları belirli kriter ve sıralamaya göre listeler. System.Data.DataRow formatında değer döndürür. WriteXml(): Veri tablosundaki kayıtları xml formatında yazdırır. Bu yordam farklı parametre alacak şekilde yeniden yüklenmiştir (overloaded). Bu yordamların haricinde veri tablosundan herhangi bir satırı silmek için DataTable ın Rows üyesinin Remove() ve herhangi bir kolonu silmek için Columns üyesinin Remove() yordamları kullanılır. Ayrıca bu üyelerin koleksiyon sınıfları olmaları nedeniyle Add(), Clear(), CopyTo(), Find() yordamları ve Count özelliği bulunur. Veri tablosu satırlarının türü olan System.Data.DataRow sınıfı, özellikle ilişkisel işlemlerde kullanılacak önemli üyelere sahiptir: GetChildRows(): O satırla ilişkili child tablodaki satırları döndürür. Yeniden yüklenmiş (overload) bu yordam DataRow türünde dizi döndürür. GetParentRow(): Satırın ilişkili olduğu ana tablodaki satırı döndürür. GetParentRows(): Satırın ilişkili olduğu ana tablodaki satırları DataRow türünde dizi olarak döndürür. SetParentRow(): Satırı bir ana satır ile ilişkilendirir. Aynı şekilde veri tablosunun her sütununun türü olan System.Data.DataColumn sınıfının da birçok üyesi bulunur. Bu üyelerle çalışma anında veri tablosu bellekte oluşturulabilir veya yönetilebilir. Aşağıdaki örnekte basit bir DataTable oluşturulmuştur. Bu tablonun MusteriId, AdSoyad ve DogumTarih isminde üç tane sütunu bulunmaktadır. MusteriId kolonu, otomatik artan özelliğe sahiptir. Burada yapılan işlem üç tane DataColumn değişkeni tanımlamaktır. Sütunlarla ilgili ayarları düzenlemek için DataColumns sınıfının özellik ve yordamları kullanılır. // Musteri isminde tablo oluşturalım. DataTable odt = new DataTable("Musteri"); // MusteriId kolonu tanımlayalım. DataColumn odc = new DataColumn(); // Kolon adı MusteriId olacak odc.columnname = "MusteriId"; // Kolonun tipi integer olacak odc.datatype = System.Type.GetType("System.Int32"); // Kolon, otomatik artan özelliğine sahip. odc.autoincrement = true; // MusteriId kolonunu tablonun kolon koleksiyonuna ekleyelim. odt.columns.add(odc); // Aynı şekilde AdSoyad kolonunu oluşturalım. // Kolon tanımlamasını DataColumn'nın yapılandırıcıaı ile yapılabilir. odc = new DataColumn("AdSoyad",System.Type.GetType("System.String")); odt.columns.add(odc); // Veya odt.columns.add("adsoyad",typeof(string)); // 3. kolonu ekleyelim. odc = new DataColumn("DogumTarih"); odc.datatype = System.Type.GetType("System.String"); odt.columns.add(odc); Papatya Yayıncılık Eğitim

65 ADO.NET Bağlantısız Sınıflar 533 Musteri tablosundaki DogumTarih sütunun türünü string değil DateTime olarak değiştirmemiz daha anlamlı olacaktır. //Tablodaki DogumTarih kolonunu alalım. DataColumn odc = odt.columns["dogumtarih"]; odc.datatype = System.Type.GetType("System.DateTime"); Musteri tablosunun kolon ve ilgili bilgilerine ulaşmak için DataTable nesnesinin Columns özelliği kullanılır. DataColumnCollection türünde değer döndüren bu özelliğin sonucunu döngüye alarak koleksiyondaki bilgilere erişebiliriz. foreach (DataColumn odc1 in odt.columns) { Console.WriteLine("Column: {0", odc1.columnname); Console.WriteLine("\tType: {0", odc1.datatype.tostring()); Console.WriteLine("\tUnique: {0", odc1.unique); Console.WriteLine("\tAutoincrement: {0", odc1.autoincrement); Console.WriteLine("\tMaxLength: {0", odc1.maxlength); Console.WriteLine("\tExpression: {0", odc1.expression); Column: MusteriId Type: System.Int32 Unique: False Autoincrement: True MaxLength: -1 Expression: Column: AdSoyad Type: System.String Unique: False Autoincrement: False MaxLength: -1 Expression: Column: DogumTarih Type: System.DateTime Unique: False Autoincrement: False MaxLength: -1 Expression: Oluşturduğumuz veri tablosuna birkaç satır kayıt ekleyip listeleme, güncelleme ve silme işlemi yapalım. // Tablonun yapısına uygun bir satır oluşturulur. DataRow odr = odt.newrow(); // Kolonun adı verilerek değer girilir. odr["adsoyad"] = "Ali Korkmaz"; // Veya doğrudan kolonun indexi girilir. odr[2] = "10/10/1978"; // Oluşturulan satır nesnesini tabloya ekleyelim. odt.rows.add(odr); odr = odt.newrow(); //2. kaydı girelim. odr["adsoyad"] = "Ayşe Korkmaz"; odr[2] = "10/10/1980"; odt.rows.add(odr); Bölüm 24

66 534 C# Programlama Dili // İlk kaydın doğum tarihini değiştirelim. odt.rows[0]["dogumtarih"] = "10/10/1979"; Console.WriteLine("Kayıt Sayısı : {0",oDt.Rows.Count); // Kayıtları listeleyelim. for (int r = 0; r < odt.rows.count; r++){ Console.WriteLine("------{0. Satır------",(r+1)); for (int c = 0; c < odt.columns.count; c++) Console.WriteLine("{0 : {1", odt.columns[c].columnname,odt.rows[r][c]); odr = odt.rows[1]; //Tablodaki ikinci satırı silelim. odt.rows.remove(odr); Kayıt Sayısı : Satır MusteriId : 0 AdSoyad : Ali Korkmaz DogumTarih : 10/10/ Satır MusteriId : 1 AdSoyad : Ayşe Korkmaz DogumTarih : 10/10/1980 İlk kaydın DogumTarih kolonunu güncellediğimiz satırı aşağıdaki gibi düzenleyelim. // Şimdiye kadar yapılmış değişiklikleri onayla. odt.acceptchanges(); odt.rows[0]["dogumtarih"] = "10/10/1979"; //AcceptChanges() yordamından sonra yapılmış değişiklikleri iptal et. odt.rejectchanges(); Bu durumda ilk kaydın DogumTarih kolonu 10/10/1979 değil 10/10/1978 değerini içerecektir. Tablondaki MusteriId kolonunu tablonun birincil anahtarı olarak atayalım. // Tablo için birincil anahtar tanımlayalım. // Birincil anahtar, birden fazla kolondan oluşabileceği için dizi tanımlanır. DataColumn[] pkeys = new DataColumn[1]; pkeys[0] = odt.columns[0]; // Tablonun PrimaryKeys özelliğini diziyeatayalım. odt.primarykey = pkeys; // Veya // odt.primarykey = new DataColumn[] {odt.columns["musteriid"]; // Varsa tablonun birincil anahtar bilgisini alalım pkeys = odt.primarykey; Console.WriteLine("Kolon Sayısı: " + pkeys.length); for(int i = 0; i < pkeys.length; i++) Console.WriteLine(pKeys[i].ColumnName +" : "+ pkeys[i].datatype); Kolon Sayısı: 1 MusteriId : System.Int32 Papatya Yayıncılık Eğitim

67 ADO.NET Bağlantısız Sınıflar 535 Kayıtları incelendiğinde birincil anahtar olan MusteriId sütununun ilk değerinin sıfırdan başladığı görülür. Otomatik artan özelliğine sahip bu sütun davranışı aşağıdaki gibi değiştirilebilir. Aşağıdaki satırlar bu kolonun 1 den başlamasını ve her artışın birer birer olmasını sağlar. odc = odt.columns["musteriid"]; odc.autoincrement = true; //İçerdiği değer otomatik artacak. odc.autoincrementseed = 1; //ilk değeri 1 olacak. odc.autoincrementstep = 1; //1'er 1'er artacak. odc.allowdbnull = false; //NULL değer içermeyecek. Tabi bu ayarların veri tablosuna kayıt girilmeden önce yapılması gerekir. ADO.NET 2.0 ile birlikte gelmiş bir yordam da DataColumn.SetOrdinal() yordamıdır. Bu yordam tablodaki bir kolonun sırasını değiştirir. odt.columns[2].setordinal(0); ifadesi odt tablosundaki üçüncü kolonu ilk sıraya taşır DataTable Nesnesinde Kayıt Arama ve Filtreleme DataTable içerisinde bir kayıdın aranması ve ona erişilmesi için DataTable ın DataRowCollection türünde değer döndüren Rows özelliğinin Find() ve Contains() yordamları kullanılır. Her iki yordam da object türünde değer alır. Aldıkları parametre tablonun birincil anahtar sütununda aranır. Find() yordamı geriye DataRow türünde, Contains() yordamı ise geriye bool türünde değer döndürür. Aşağıdaki örnekte birincil anahtar değeri 1 ve 3 olan kayıtların olup olmadığı sınanmıştır: // PK değeri 3 olan kayıt var mı? bool Sonuc = odt.rows.contains(3); Console.WriteLine("PK değeri 3 olan kayıt var mı? "+ Sonuc); // PK değeri 1 olan kayıt var mı? Varsa AdSoyad kolon değeri nedir? odr = odt.rows.find(1); if (odr == null) Console.WriteLine("(PK=1) kaydı bulunamadı."); else { Console.WriteLine("(PK=1) kaydı bulundu."); // Bulduğumuz kaydın AdSoyad kolonunu okuyalım. Console.WriteLine("(PK=1) AdSoyad : {0",oDr["AdSoyad"]); // Veya bulduğumuz kaydı güncelleyebiliriz. // Doğru yöntem olarak o satırı önce edit moduna getirelim. odr.beginedit(); // AdSoyad kolonunu değiştirelim. odr["adsoyad"] = "Zeynep Korkmaz"; // Değişikliği kalıcı yapalım. odr.acceptchanges(); Bölüm 24

68 536 C# Programlama Dili PK değeri 3 olan kayıt var mı? False (PK=1) kaydı bulundu. (PK=1) AdSoyad : Ayşe Korkmaz Bu iki yordamın hatasız çalışabilmesi için tablo üzerinde birincil anahtar tanımlanmış olmalıdır. PÜF! DataTable da birincil anahtar birden fazla sütundan oluşuyorsa Contains() ve Find() yordamlarının object türünde dizi alan uyarlaması kullanılır. //2 kolonda oluşan bir birincil anahtar tanımlayalım. DataColumn[] pkeys = new DataColumn[2]; pkeys[0] = odt.columns["musteriid"]; pkeys[1] = odt.columns["adsoyad"]; odt.primarykey = pkeys; // MusteriId=1, AdSoyad=Ali Korkmaz olan satırı arayalım. object[] Filtre ={1,"Ali Korkmaz" ; bool Sonuc = odt.rows.contains(filtre); odr = odt.rows.find(filtre); DataTable nesnesinde filtreleme yapmak için Select() yordamı kullanılır. Bu yordam yeniden yüklenmiş olarak farklı şekillerde kullanılabilir ve DataRow türünde dizi döndürür. public DataRow[] Select(); // Tablodaki tüm kayıtları döndürür. public DataRow[] Select(string filterexpression); // Tablodan sadece filtreye uygun satırları getirir. public DataRow[] Select(string filterexpression, string sort); //Tablodan sadece filtreye uygun kayıtları, belirtilen sıralamada getirir. public DataRow[] Select(string filterexpression, string sort, DataViewRowState recordstates); // Tablodan DataRowViewState parametresine uygun kayıtlar içerisinden filtreye uygun kayıtları, belirtilen sıralamada getirir. Koşul ve sıralama ifadeleri Select() yordamında SQL deki formatta verilir ("Tarih >= ' ' AND Tarih <= ' '" gibi). Bu yordamın aldığı recordstates parametresi çalışma anında aramanın hangi kayıtlar arasından yapılacağını bildirir. Data- ViewRowState türünde olan bu parametrenin alabileceği değerler şunlardır: Papatya Yayıncılık Eğitim

69 ADO.NET Bağlantısız Sınıflar 537 Added: Tabloya sonradan eklenmiş kayıtlarda. CurrentRows: Tabloda o anda bulunan geçerli kayıtlarda. Deleted: Tablodan silinmiş kayıtlarda. ModifiedCurrent: Değiştirilen kayıtların o andaki değerlerinde. ModifiedOriginal: Değiştirilen kayıtların asıl değerlerinde. None: Herhangi bir satır döndürmez. OriginalRows: Tüm satırları başlangıç asıl değerlerinde. Unchanged: Değişmemiş satırlarda arama yapar. Kayıtlar içerisinde MusteriId kolonu 1 den büyük olan ve adı A ile başlayan kayıtları listeleyelim ardından başka bir tabloya aktaralım. DataRow[] orows; //Koşul ve sıralama ifadelerini yazalım. string Filtre = "MusteriId>0 AND AdSoyad LIKE 'A%'"; string Siralama = "MusteriId DESC"; orows = odt.select(filtre,siralama); //Bulunan satırları yazdıralım. Console.WriteLine("Bulunan kayıt sayısı : {0",oRows.Length); foreach (DataRow or in orows) { Console.Write("MusteriId : {0 ", or["musteriid"]); Console.Write("Ad Soyad : {0", or["adsoyad"]); Console.WriteLine(); Bulunan kayıt sayısı : 1 MusteriId : 1 Ad Soyad : Ayşe Korkmaz Bulduğumuz kayıtları yeni bir tabloya aktaralım. // Kaynak tablonun yapısından bir tablo oluşturalım. DataTable ohedef = odt.clone(); //Döngü yöntemiyle kayıtları hedef tabloya aktaralım. foreach (DataRow or in orows) { // ImportRow() yordamı bir satırı içeriye alır. ohedef.importrow(or); Console.WriteLine("Tablodaki kayıt sayısı : {0", ohedef.rows.count); foreach (DataRow or in ohedef.rows) { Console.Write("MusteriId : {0 ", or["musteriid"]); Console.Write("Ad Soyad : {0", or["adsoyad"]); Console.WriteLine(); Tablodaki kayıt sayısı : 1 MusteriId : 1 Ad Soyad : Ayşe Korkmaz Bölüm 24

70 538 C# Programlama Dili DataTable içerisinde bir satırı silmek için DataRowCollection nesnesinin Remove() veya DataRow nesnesinin Delete() yordamları kullanılır. Remove() kullanıldığı zaman sözkonusu satır, ilgili koleksiyondan doğrudan silinir. Delete() yordamında ise satır sadece silinecek olarak işaretlenir. Gerçekte silinmesi için Delete() den sonra AcceptChanges() yordamı çağrılmalıdır. PÜF!.NET te veri tablosundaki bir kolonu NULL yapmak için System.DBNull.Value değeri kullanılır. Kolonun NULL değeri içerip içermediğini sınamak için de DBNull.Value.Equals() yordamı kullanılabildiği gibi DataRow nesnesinin IsNull() yordamı tercih edilebilir. public bool IsNull(DataColumn column, DataRowVersion version); Değişen Kayıtlar Hakkında Bilgi Almak DataTable, DataSet ve DataRow gibi nesnelerdeki satırların durumu hakkında bilgi almak veya güncelleme olmuşsa hangi aşamada olduğunu öğrenmek için System.Data.DataRowState ve System.Data.DataRowVersion sınıfları kullanılır. DataRowState tablodaki her satırın durumunu belirtir. Özellikle satır güncelleme işlemlerini yönetmek amacıyla kullanılır. Her satırın bir RowState değeri vardır ve bu aşağıdakilerden birisi olabilir: Added: Sözkonusu satır DataRowCollection koleksiyonuna eklendi, fakat henüz AcceptChanges() yordamı çağrılmamış. Delete: DataRow nesnesinin Delete() yordamı kullanılarak veri tablosundan silinmiş. Detached: Bu satıra ait DataRows nesnesi oluşturulmuş, fakat henüz DataRowCollection koleksiyonuna eklenmemiş yani veri tablosuna yansıtılmamış. Bu durum satırın oluşturulma ve Add() yordamıyla DataTable e eklenme aşamaları arasında olduğunu gösterir. Modified: Satırda güncelleme yapılmış, ama güncelleme onaylanmamış. Unchanged: Bu satır başından beri veya AcceptChanges() yordamının çağrılmasından bu yana değişmemiş. Bu seçenekler kullanılarak kayıt satırının hangi aşamada olduğu, yani güncelleniyor, güncellenmiş veya güncellenmesi iptal edilmiş olup olmadığı öğrenilebilir. Papatya Yayıncılık Eğitim

71 ADO.NET Bağlantısız Sınıflar 539 // Yeni bir DataRow oluştur. odr = odt.newrow(); Console.WriteLine("NewRow() : " + odr.rowstate); // Satırı tabloya ekleyelim. odt.rows.add(odr); // Eklenmiş satır. Console.WriteLine("Rows.Add() : " + odr.rowstate); // Değişiklikleri onaylayalım. odt.acceptchanges(); // Değişmemiş satır. Console.WriteLine("AcceptChanges() : " + odr.rowstate); // Satırın AdSoyad kolonunu güncelleyelim. odr["adsoyad"] = "Ahmet Kaymaz"; // Değiştirilmiş satır. Console.WriteLine("Modified : " + odr.rowstate); //Bu satırı silelim. odr.delete(); // Silinmiş satır. Console.WriteLine("Deleted : " + odr.rowstate); NewRow() : Detached Rows.Add() : Added AcceptChanges() : Unchanged Modified : Modified Deleted : Deleted Bilindiği gibi veri tablosundan kayıt silmek için Remove() veya Delete() yordamları kullanılır; ilki söz konusu satırı RowState özelliğini bozmadan satır koleksiyonundan yok eder. Delete() ise satırı sadece silinecek olarak işaretler ve RowState özelliğini Deleted olarak ayarlar. odt.acceptchanges(); DataSet ods= new DataSet(); ods.tables.add(odt); //İlk satırı Delete() ile silelim. odt.rows[0].delete(); Console.WriteLine(oDt.Rows[0].RowState); Console.WriteLine(oDs.HasChanges()); //İkinci satırı Remove() ile silelim. odt.rows.remove(odt.rows[1]); Console.WriteLine(oDt.Rows[1].RowState); Console.WriteLine(oDs.HasChanges()); //Silindi. //Silindi. //True //Unchanged //False DataRow türündeki bir satırın RowState özelliği el-ile değiştirilebilir. Bunun için SetAdded() ve SetModified() yordamları kullanılır. Bu yordamların uygulanacağı satırların RowState durumlarının Unchanged olarak yerleştirilmiş olması lazım. Yani bu yordamlardan önce AcceptChanges() yordamı çağrılarak tablodaki değişikliklerin onaylanması gerekir. DataTable üzerinde çalışma anında ekleme, güncelleme ve silme yapılabilindiği için veriler asıl veritabanına gönderilmeye çalışıldığında veri tutarlılığı olması gere- Bölüm 24

72 540 C# Programlama Dili kir. Yani fiziksel veritabanındaki veriler ile en son aldığımız veriler aynı olmalı; başkası tarafından güncelleme yapılmamış olmalıdır. Çünkü daha sonra açıklayacağımız üzere DataAdapter, güncellemeleri veritabanı sunucusuna yansıtmaya çalışırken güncellenmiş verilerin orijinal değerlerini koşul olarak kullanır. Dolayısıyla bu süreçte her verinin öndeğer (default), asıl veya değişmiş uyarlaması bulunur. Bu değişiklikler hakkında bilgiyi DataRowVersion verir. Bir satırın değişik uyarlamalarının olup olmadığı bool türünde değer döndüren HasVersion() yordamıyla sınanır. Current: Satırın en son güncel değeri. Eğer satırın RowState özelliği Deleted değerindeyse DataRowVersion için Current değeri oluşmaz. Original: Satırın asıl değeri içerdiği uyarlaması. Eğer satırın RowState özelliği Added modundaysa DataRowVersion için Original değeri oluşmaz. Proposed: Yapılması düşünülen değişikliği belirtir. Bunlar DataRow nesnesinin BeginEdit() ve EndEdit() veya CancelEdit() yordamları arasında yapılan değişikliklerdir. Yani BeginEdit() çağrılmış, fakat EndEdit() ve CancelEdit() yordamları çağrılmamıştır. Default: Bu değer o satır için varsayılan satır uyarlamasının ne olacağını belirtir. Eğer satırın RowState özelliği Added, Modified veya UnChanged değerinde ise varsayılan satır uyarlaması Current olandır. Eğer RowState özelliği Deleted değerindeyse satırın varsayılan uyarlaması Original olur. Eğer RowState özelliği Detached modunda ise satırın varsayılan uyarlaması Proposed olur. Herhangi bir satırın bu değerlerdeki satır uyarlamalarına erişmek için DataRow nesnesinin indeksleyici üyesi kullanılır. public object this[datacolumn column, DataRowVersion version]{get; public object this[int columnindex, DataRowVersion version] {get; public object this[string columnname, DataRowVersion version] {get; // ColumnChanged olayı için bir yönetici tanımlayalım. odt.columnchanged += new DataColumnChangeEventHandler(KayitDegisti); //İlk değeri Ayşe olan bu kaydı Zeynep olarak güncelleyelim. odt.rows[1]["adsoyad"] = "Zeynep Korkmaz"; static void KayitDegisti(object sender, DataColumnChangeEventArgs e) { Console.WriteLine(e.Row["AdSoyad"]); Console.WriteLine(e.Row["AdSoyad", DataRowVersion.Current]); Console.WriteLine(e.Row["AdSoyad", DataRowVersion.Original]); Console.WriteLine(e.Row["AdSoyad", DataRowVersion.Default]); Console.WriteLine(e.Row["AdSoyad", DataRowVersion.Proposed]); Zeynep Korkmaz Ayşe Korkmaz - Current Ayşe Korkmaz - Original Zeynep Korkmaz - Default Zeynep Korkmaz - Proposed Papatya Yayıncılık Eğitim

73 ADO.NET Bağlantısız Sınıflar Tablodaki Değişiklikleri İzlemek DataTable ile ilgili diğer konu da tabloda yapılan değişikliklerin izlenmesidir. Veri tablosundaki eklenmiş, güncellenmiş veya silinmiş satırları öğrenmek için DataTable in GetChanges() yordamı kullanılır. Bu yordam veri tablosundaki AcceptChanges() ın çağrılmasından sonraki tüm değişikliklerin bulunduğu bir DataTable nesnesi döndürür. //Önceki tüm değişiklikleri onaylayalım. odt.acceptchanges(); //İlk değeri Ayşe olan 2.satırı Zeynep olarak güncelleyelim. odt.rows[1]["adsoyad"] = "Zeynep Korkmaz"; //Yeni bir satır ekleyelim. odr = odt.newrow(); odr["adsoyad"] = "Metin Güneş"; odr[2] = "10/10/1950"; odt.rows.add(odr); //Tablonun 2.satırını silelim. odt.rows[1].delete(); //İlk satırını Remove() ile silelim. odt.rows.remove(odt.rows[0]) ; //Kaç satırın değiştiğini öğrenelim //Değişiklik yapılmadığı zaman sistemin hata vermemesi için böyle bir kontrol gerekli if (odt.getchanges() == null) Console.WriteLine("Değişmiş satır bulunamadı."); else Console.WriteLine("Değişmiş satır sayısı: {0", odt.getchanges().rows.count); //Değişmiş kayıtları otb nesnesine alalım. DataTable otb = odt.getchanges(); foreach (DataRow or in otb.rows) { //Silinmiş kayıtların şu anki değerleri olmadığı için. if (or.rowstate == DataRowState.Deleted) { else { Console.Write("MusteriId: {0 ", or["musteriid"]); Console.Write("» Ad Soyad: {0", or["adsoyad"]); Console.Write("» Durum: {0", or.rowstate); Console.WriteLine(); Console.Write("MusteriId: {0 ", or["musteriid", DataRowVersion.Original]); Console.Write("» Ad Soyad: {0", or["adsoyad", DataRowVersion.Original]); Değişmiş satır sayısı: 2 MusteriId: 1» Ad Soyad: Ayşe Korkmaz» Durum: Deleted MusteriId: 2» Ad Soyad: Metin Güneş» Durum: Added Bölüm 24

74 542 C# Programlama Dili GetChanges() yordamı yeniden yüklenmiş olup DataRowState türünde parametre alan uyarlaması da kullanılabilir. Bu durumda sadece belirli durumlardaki satırları döndürür. if (odt.getchanges(datarowstate.added) == null) Console.WriteLine("Eklenmiş satır bulunamadı."); else Console.WriteLine("Eklenmiş satır sayısı: {0", odt.getchanges().rows.count); //Sadece yeni eklenmiş kayıtları otb nesnesine alalım. DataTable otb = odt.getchanges(datarowstate.added); foreach (DataRow or in otb.rows) { Console.Write("MusteriId: {0 ", or["musteriid"]); Console.Write("» Ad Soyad: {0", or["adsoyad"]); Console.Write("» Durum: {0", or.rowstate); Console.WriteLine(); Eklenmiş satır sayısı: 2 MusteriId: 2» Ad Soyad: Metin Güneş» Durum: Added Görüldüğü gibi GetChanges() aracılığıyla kirli veri olarak tanımlanan ve değişikliği henüz onaylanmamış kayıtları yakalama şansı vardır. Bu kayıtları AcceptChanges() veya RejectChanges() yordamlarını çağırdıktan sonra artık takip edemeyiz. Çünkü bu yordamlar veri tablosunda tutulan tüm değişiklik bilgilerini siler DataTable ile XML Okuma ve Yazma ADO.NET in önemli özelliklerinden birisi de birçok veri nesnesinin XML ile ilişkili olmasıdır. DataTable, DataSet gibi nesnelerin içerisindeki verileri XML olarak yazmak veya dışarıdan bir XML dosyasını bu nesnelerin içerisine aktarmak için WriteXml(), WriteXmlSchema(), ReadXmlSchema() ve ReadXml() yordamları bulunur. Yeniden yüklenmiş olan bu yordamlar ihtiyaca göre string, Stream, TextWriter, XmlWriter, TextReader veya XmlReader nesneleri türünde parametre alabilir. Aşağıdaki tek satırlık kısa kod ile bellek üzerinde yaşayan veri tablosunu diske XML dosyası olarak saklıyoruz. odt.writexml(@"c:\musteri.xml"); Papatya Yayıncılık Eğitim

75 ADO.NET Bağlantısız Sınıflar DataTable Olayları (DataTable Events) DataTable nesnesinin şu ana kadar ele alınan üyeler dışında da önemli olayları bulunur. Bunlar veri tablosunda sütun/kolon veya satır bazında bir değişiklik yapıldığı zaman tetiklenir: ColumnChanged: Bir sütundaki değer değiştiği zaman çalışır. ColumnChanging: Sütundaki değerin değişmesi esnasında tetiklenir. RowChanged : Tabloda bir kayıt değişince çalışır. RowChanging : Satırdaki değerin değişmesi sırasında çalışır. RowDeleted : Herhangi bir kayıt silinince tetiklenir. RowDeleting : Kaydın silinmesi esnasında tetiklenir. TableCleared : Tablodaki tüm kayıtlar silinince devreye girer. TableClearing: Tablonun silinmesi esnasında çalışır. TableNewRow : Tabloya yeni kayıt eklendiği zaman oluşur. //Önceki tüm değişiklikleri onaylayalım. odt.acceptchanges(); //ColumnChanged olayı için bir handler tanımlayalım. odt.columnchanged += new DataColumnChangeEventHandler(KayitDegisti); //Tabloda ikinci kaydı değiştirelim. odt.rows[1]["adsoyad"] = "Zeynep Korkmaz"; private static void KayitDegisti(object sender, DataColumnChangeEventArgs e) { Console.WriteLine("---Tabloda bir kayıt değişti---"); Console.WriteLine("Değişken Kolonun Adı: {0", e.column.columnname); Console.WriteLine("Kolonun Yeni Değeri: {0", e.row["adsoyad"]); Console.WriteLine("Kolonun Orijinal Değeri: {0", e.row["adsoyad", DataRowVersion.Original]); ---Tabloda bir kayıt değişti--- Değişken Kolonun Adı: AdSoyad Kolonun Yeni Değeri: Zeynep Korkmaz Kolonun Orijinal Değeri: Ayşe Korkmaz DataSet Nesnesinin Üyeleri DataSet ve DataTable nesneleri birçok ortak üye içerse de DataTable den daha fazlasını sunan yine çevrim-dışı çalışan bir veri depolayıcısıdır. DataSet in özellikle XML desteği ve içerisinde ilişkili birden fazla DataTable içerebiliyor olması tercih nedeni olmuştur. Bu yönüyle sunucu üzerindeki yükü alıp istemci üzerinde bir veritabanının sunduğu birçok özelliği uygulayabilmektedir. DataSet nesnesi DataAdapter üzerinden fiziksel veri kaynağıyla doldurulabileceği gibi daha önceden bellek üzerinde oluşturulmuş DataTable kaynaklarıyla da doldurulabilir. Bölüm 24

76 544 C# Programlama Dili DataSet nesnesinin yapıcı yordamı yeniden yüklenmiştir. Parametre olarak string türünde Dataset adını alabilir. Bu parametre verilmezse kendisi varsayılan bir isim atar. DataTable ile ortak olarak CaseSensitive, HasErrors, Locale, RemotingFormat özelliklerine AcceptChanges(), Clear(), Clone(), CreateDataReader(), GetChanges(), Load(), Merge(), ReadXml(), ReadXmlSchema(), RejectChanges(), Reset(), WriteXml() ve WriteXmlSchema() yordamlarına sahiptir. Bunların haricinde sahip olduğu önemli üyeler şunlardır: DataSetName: DataSet nesnesinin adını günceller veya döndürür. DefaultViewManager: DataSet içerisindeki tüm tablolar için ortak bir sorgulama hazırlayıp tablodaki verileri filtrelemek ve aramak için kullanılır. DataViewManager türünde değer döndürür. EnforceConstraints: bool türünde değer alan/döndüren bu özellik, tablolarda yapılacak bir güncellemede kısıt/kısıtlama (constraint) kurallarının uygulanıp uygulanmayacağını bildirir. Doğru (True) değerindeyken tablo üzerinde kısıt sınaması işlemi yapılır. Relations: DataSet içerisindeki tablolar arası ilişkiyi yönetmek için kullanılır. DataRelationCollection türünde değer döndürür. Tables: DataSet içerisindeki tablolara erişmemizi sağlar. DataTableCollection türünde değer döndürür. GetXml(): string türünde değer döndüren bu yordam DataSet içerisindeki verileri XML formatında döndürür. Veri tablosuna ait XML şemasını da GetXmlSchema() yordamı aracılığıyla alabiliriz. HasChanges(): Dataset içindeki tablolarda değişiklik yapılmadığını bildirir. Aşağıdaki kodlarda iki tablolu (Musteri ve Siparis) basit bir DataSet oluşturulmuş ve DataTable üyelerinden bildiğimiz yöntemlerle DataSet içerisindeki veriler listelenmiştir. Tablo şeması ve verilerinin daha açık görülmesi için SQL Sunucu üzerinde olduklarını düşünelim. Şekil Örnek Musteri ve Siparis tablosu Papatya Yayıncılık Eğitim

77 ADO.NET Bağlantısız Sınıflar 545 Bu tabloları çalışma zamanında bellek üzerinde oluşturalım. //DataSet nesnesi oluşturalım. DataSet ods = new DataSet(); //Musteri isminde tablo oluşturalım. DataTable odt = new DataTable("Musteri"); //Musteri tablosunda MusteriId, AdSoyad kolonlarını oluşturalım. DataColumn odc = new DataColumn("MusteriId", System.Type.GetType("System.Int32")); odc.autoincrement = true; odc.autoincrementseed = 1; odt.columns.add(odc); //2.Kolon (AdSoyad kolonu) odt.columns.add("adsoyad", System.Type.GetType("System.String")); //Tabloda iki kayıt girelim. DataRow odr = odt.newrow(); odr["adsoyad"] = "Ali Korkmaz"; odt.rows.add(odr); //2.kayıt odr = odt.newrow(); odr["adsoyad"] = "Ayşe Korkmaz"; odt.rows.add(odr); //Musteri tablosunu DataSet içerisine ekleyelim. ods.tables.add(odt); //Siparis tablosunu oluşturalım. odt = new DataTable("Siparis"); //Siparis tablosunda SiparisId,MusteriId,UrunId,Adet,Tarih kolonlarını oluşturalım. odc = new DataColumn("SiparisId", System.Type.GetType("System.Int32")); odc.autoincrement = true; odc.autoincrementseed = 1; odt.columns.add(odc); odt.columns.add("musteriid", System.Type.GetType("System.Int32")); odt.columns.add("urunid", System.Type.GetType("System.Int32")); odt.columns.add("adet", System.Type.GetType("System.Int32")); odt.columns.add("tarih", System.Type.GetType("System.DateTime")); //Tarih kolonu için default değer tanımlayalım. odt.columns["tarih"].defaultvalue = DateTime.Now; //Siparis tablosuna kayıtlar girelim. odr = odt.newrow(); odr["musteriid"] = 1; //Ali Korkmaz odr["urunid"] = 10; //10 numaralı ürün odr["adet"] = 2; odr["tarih"] = "01/01/2007"; odt.rows.add(odr); odr = odt.newrow(); odr["musteriid"] = 1; //Ali Korkmaz odr["urunid"] = 12; //12 numaralı ürün odr["adet"] = 3; odr["tarih"] = "01/01/2007"; odt.rows.add(odr); odr = odt.newrow(); odr["musteriid"] = 2; //Ayşe Korkmaz odr["urunid"] = 10; //10 numaralı ürün odr["adet"] = 1; odt.rows.add(odr); Bölüm 24

78 546 C# Programlama Dili //Siparis tablosunu DataSet içerisine ekleyelim. ods.tables.add(odt); //DataSet içindeki tablo sayısı. Console.WriteLine("DataSet tablo sayısı: {0",oDs.Tables.Count); //odt'nin dahil olduğu DataSet adını okuyalım. //DataSet tanımlarken bir isim vermediğimiz için default değer olan "NewDataSet" ifadesini yazdırır. Console.WriteLine("DataSet Adı: {0",oDt.DataSet.DataSetName); Console.WriteLine(); //DataSet içindeki her tablonun kayıtlarını listeyelim. foreach (DataTable otb in ods.tables) { Console.WriteLine("----{0 tablosu----",otb.tablename); foreach (DataRow orw in otb.rows) { foreach (DataColumn ocm in otb.columns) { Console.Write(oRw[oCm] +"\t"); Console.WriteLine(); Console.WriteLine(); DataSet tablo sayısı: 2 DataSet Adı: NewDataSet ----Musteri tablosu Ali Korkmaz 2 Ayşe Korkmaz ----Siparis tablosu :00: :00: :12:00 DataSet nesnesi altyapıları verileri XML formatında tutmaktadır. En basit haliyle GetXml() yordamı kullanılarak veriler XML yazım biçiminde alınabilir. Console.WriteLine(oDs.GetXml()); <NewDataSet> <Musteri> <MusteriId>1</MusteriId> <AdSoyad>Ali Korkmaz</AdSoyad> </Musteri> <Musteri> <MusteriId>2</MusteriId> <AdSoyad>Ayşe Korkmaz</AdSoyad> </Musteri> <Siparis> <SiparisId>1</SiparisId> <MusteriId>1</MusteriId> <UrunId>10</UrunId> <Adet>2</Adet> <Tarih> T00:00:00+02:00</Tarih> </Siparis> <Siparis> <SiparisId>2</SiparisId> <MusteriId>1</MusteriId> <UrunId>12</UrunId> <Adet>3</Adet> Papatya Yayıncılık Eğitim

79 ADO.NET Bağlantısız Sınıflar 547 <Tarih> T00:00:00+02:00</Tarih> </Siparis> <Siparis> <SiparisId>3</SiparisId> <MusteriId>2</MusteriId> <UrunId>10</UrunId> <Adet>1</Adet> <Tarih> T16:18: :00</Tarih> </Siparis> </NewDataSet> LoadOption Numaralandırma ADO.NET 2.0 yeniliklerinden birisi de System.Data.LoadOption numaralandırmasıdır; veri kaynağından alınmış kayıtların Load() veya Fill() ile var olan ve üzerinde birincil anahtar tanımlı veri tablosuna yüklenirken, aynı kayıtlara rastlanıldığında nasıl davranılacağını belirtir. Aşağıdaki değerleri alabilir: OverwriteChanges: O satır için veritabanından gelen değerler, kolonların hem orijinal (Original) hem de o anki (Current) uyarlamalarına yazılır. PreserveChanges: LoadOption numaralandırmanın varsayılan değeri olan bu seçenekte o satır için veritabanından gelen değerler kolonların asıl (original) u- yarlamalarına yazılır. O anki uyarlaması değiştirilmez. Upsert: O satır için veritabanından gelen değerler kolonların o an ki (Current) uyarlamalarına yazılır. Asıl uyarlaması değiştirilmez. Yüklenme işleminden sonra tablodaki satırların RowState durumlarının nasıl olacağı satırın önceki durumuna bağlıdır. Satırın durumu Added Modified Deleted Unchanged Tablodaki kayıtla eşleşmezse PreserveChanges Upsert OverwriteChanges Current=Existing Original=Incoming RowState=Modified Current=Existing Original=Incoming RowState=Modified Current=Existing Original=Incoming RowState=Deleted Current=Incoming Original=Incoming RowState=Unchanged Current=Incoming Original=Existing RowState=Added Current=Incoming Original=Existing RowState=Modified * Undo Delete Current=[n/a] Original=Existing RowState=Deleted Current=Incoming Original=Existing if new value=existing RowState=Unchanged else RowState=Modified Current=Incoming Current=Incoming Original=Incoming Original=[n/a] RowState=Unchanged RowState=Added Current = Incoming Original = Incoming RowState = Unchanged Current = Incoming Original = Incoming RowState = Unchanged Undo Delete Current = Incoming Original = Incoming RowState = Unchanged Current = Incoming Original = Incoming RowState = Unchanged Current = Incoming Original = Incoming RowState = Unchanged * Tablodan silinmiş satır için Upsert seçeneğinde gelen satır var olan satırla birincil anahtar üzerinden eşleşemeyeceği için gelen satır, tabloya yeni satır (Added) olarak eklenir. Bölüm 24

80 548 C# Programlama Dili Tablodaki Existing ifadesi kolonun tablodaki değerini, Incoming ifadesi de veritabanından gelen değerini bildirmektedir. SQL Sunucudaki Musteri tablosunda aşağıdaki kayıtlar bulunmaktadır: Bu tabloyu istemci tarafındaki Musteri tablosuna yükleyeceğiz. İstemci tarafındaki tabloda üç kayıt bulunmaktadır. Sonuç bölümünde tablodaki kayıtların yükleme öncesi ve sonrasında birincil anahtar kolonu, orijinal ve şu anki değerleri gösterilmiştir. static void Main(string[] args) { DataTable odt = new DataTable(); odt.columns.add("musteriid", typeof(int16)); odt.columns.add("adsoyad"); //Eşleşecek kayıtların güncellenebilmesi için tablo üzerinde birincil anahtar tanımlanmalıdır. odt.primarykey = new DataColumn[] { odt.columns["musteriid"] ; odt.rows.add("1", "Cem Aktar"); odt.rows.add("2", "Ahmet Demir"); odt.rows.add("3", "Ali Demir"); Console.WriteLine("--- Load() Öncesi ---"); //odt.rows[1].delete(); foreach (DataRow odr in odt.rows) Console.WriteLine("{0\t{1\t{2\t{3", odr["musteriid"], GetRowValue(oDr, "AdSoyad", DataRowVersion.Original), GetRowValue(oDr, "AdSoyad", DataRowVersion.Current), odr.rowstate); SqlCommand ocmd=new SqlCommand("SELECT MusteriId,AdSoyad FROM Musteri",oCnn); ocnn.open(); SqlDataReader osdr = ocmd.executereader(commandbehavior.closeconnection); odt.load(osdr, LoadOption.PreserveChanges); Console.WriteLine("\n--- Load() Sonrası ---"); foreach (DataRow odr in odt.rows) Console.WriteLine("{0\t{1\t{2\t{3", odr["musteriid"], GetRowValue(oDr, "AdSoyad", DataRowVersion.Original), GetRowValue(oDr, "AdSoyad", DataRowVersion.Current), odr.rowstate); Console.ReadLine(); // Main static string GetRowValue(DataRow odr,string ColumnName, DataRowVersion version){ //Silinmiş satırların Current değeri, //Eklenmiş satırların Original değeri olmadığından hata vermemesi için. try{ return odr[columnname, version].tostring(); catch{ return "-NULL-\t"; //GetRowValue Papatya Yayıncılık Eğitim

81 ADO.NET Bağlantısız Sınıflar Load() Öncesi NULL- Cem Aktar Added 2 -NULL- Ahmet Yavuz Added 3 -NULL- Ali Demir Added --- Load() Sonrası Cem Aktar Cem Aktar Modified 2 Ahmet Kaymaz Ahmet Demir Modified 3 -NULL- Ali Demir Added 5 Metin Meriç Metin Meriç Unchanged Diğer seçenekleri deneyelim. odt.load(osdr, LoadOption.OverwriteChanges) --- Load() Öncesi NULL- Cem Aktar Added 2 -NULL- Ahmet Demir Added 3 -NULL- Ali Demir Added --- Load() Sonrası Cem Aktar Cem Aktar Unchanged 2 Ahmet Kaymaz Ahmet Kaymaz Unchanged 3 -NULL- Ali Demir Added 5 Metin Meriç Metin Meriç Unchanged odt.load(osdr, LoadOption.Upsert) --- Load() Öncesi NULL- Cem Aktar Added 2 -NULL- Ahmet Demir Added 3 -NULL- Ali Demir Added --- Load() Sonrası NULL- Cem Aktar Added 2 -NULL- Ahmet Kaymaz Added 3 -NULL- Ali Demir Added 5 -NULL- Metin Meriç Added DataTable Merge() Yordamı İki veri tablosunu birleştirmek ve birindeki satırları diğer tabloya taşımak için Merge() yordamı kullanılır. Aşağıda gösterildiği biçimleri kullanılarak farklı şemalı tabloları birleştirme esnasında nasıl davranacağı da yönlendirilebilir. public void Merge(DataTable table); public void Merge(DataTable table, bool preservechanges); public void Merge(DataTable table, bool preservechanges, MissingSchemaAction missingschemaaction); İki tabloyu birleştirmeye çalıştığımızda ilk tabloda birincil anahtar varsa güncellemeyi bunun üzerinde yapacaktır; yoksa ikinci tablodaki satırlar ilk tabloya yeni satır olarak eklenir. İkinci tablodaki kayıtların, birinci tabloda uyuştukları kayıtların güncellenip güncellemeyecekleri preservechanges parametresiyle belirlenir. Aynı şekilde birleştirmeye çalıştığımız bu iki tablonun şema yapıları da farklı olabilir. Bu Bölüm 24

82 550 C# Programlama Dili durumda nasıl bir davranış sergileneceği missingschemaaction parametresiyle belirlenir. Bu parametreler aşağıdaki değerleri alabilir: Add: Kopyalanacak tablonun kolonları fazlaysa bu kolonlar hedef tablo üzerinde oluşturulur. Ignore: Hedef tablonun şemasına uygun olmayan kolonlar görmezden gelinir. Error: Şema farklılığı olduğu zaman birleştirme işlemi hata verir. AddWithKey: Add seçeneğinin benzeri olup tek farkı kopyalanan tabloda birincil anahtar tanımlıysa bu bilgi de hedef tabloya kopyalanır. Böylece hedef tablonun birincil anahtar bilgisi oluşmuş olur. static void Main() { DataTable odt1 = new DataTable("Sehir"); odt1.columns.add("sehirid", typeof(int32)); odt1.columns.add("sehirad"); odt1.primarykey = new DataColumn[] { odt1.columns["sehirid"] ; //Tablodaki değişiklikten haberdar olalım. odt1.rowchanged += new DataRowChangeEventHandler(SatirDegisti); // Birinci tabloya kayıt ekleyelim. odt1.rows.add(new object[] { 1, "Adana" ); odt1.rows.add(new object[] { 2, "Ankara" ); odt1.rows.add(new object[] { 3, "İzmir" ); odt1.acceptchanges(); Console.WriteLine("\n---Sehir tablosunun ilk durumu---"); TabloYazdir(oDt1); //İlk tabloyla aynı yapıda bir tablo oluşturalım. DataTable odt2 = odt1.clone(); //Tabloların şemaları farklı olsun diye ikinci tabloya yeni kolon ekleyelim. odt2.columns.add("sehirkod", typeof(system.string)); //2.Tabloya kayıt ekleyelim. odt2.rows.add(new object[] { 1, "Adana", "01" ); odt2.rows.add(new object[] { 2, "İstanbul", "34" ); odt2.rows.add(new object[] { 4, "Gaziantep", "27" ); //odt2 tablosunu, odt1 tablosuna Merge edelim. Console.WriteLine("\noDt2->oDt1 Merge Edildi."); odt1.merge(odt2, false, MissingSchemaAction.Add); Console.WriteLine("\n---Sehir tablosunun son durumu---"); TabloYazdir(oDt1); Console.ReadLine(); //Main static void SatirDegisti(object sender, DataRowChangeEventArgs e) { Console.WriteLine("SehirId={0 --> {1", e.row.itemarray[0], e.action); static void TabloYazdir(DataTable otb) { foreach (DataRow odr in otb.rows) { foreach (DataColumn odc in otb.columns) { Console.Write("\t" + odr[odc].tostring()); Console.WriteLine(); Papatya Yayıncılık Eğitim

83 ADO.NET Bağlantısız Sınıflar 551 SehirId=1 --> Add SehirId=2 --> Add SehirId=3 --> Add SehirId=1 --> Commit SehirId=2 --> Commit SehirId=3 --> Commit ---Sehir tablosunun ilk durumu--- 1 Adana 2 Ankara 3 İzmir odt2->odt1 Merge Edildi. SehirId=1 --> Change SehirId=2 --> Change SehirId=4 --> Add ---Sehir tablosunun son durumu--- 1 Adana 01 2 İstanbul 34 3 İzmir 4 Gaziantep 27 Görüldüğü gibi ikinci tablonun fazla kolonu (SehirKod), birinci tablo üzerinde oluşturulmuş oldu ve birincil anahtar bilgileri çakışan satırlar güncellenmiş oldu. Eğer Merge() yordamında preservechanges parametresini true olarak atamış olsaydık, çakışmadan dolayı yapılacak güncellemeler iptal edilirdi. Bu durumda Sehir tablosunun içeriği şu şekilde olurdu: odt1.merge(odt2, true, MissingSchemaAction.Add) ---Sehir1 tablosunun son durumu--- 1 Adana 2 Ankara 3 İzmir 4 Gaziantep 27 Merge() yordamı aynı şekilde DataSet nesnesi için de kullanılabilir. Ayrıca DataSet nesnesi üzerinde birleştirme işlemi esnasında olası hataları yakalamak için MergeFailed olayı kullanılabilir. static void Main() { SqlDataAdapter oda = new SqlDataAdapter("SELECT * FROM Marka",oCnn); DataSet ods1 = new DataSet("DataSet1"); oda.fill(ods1,"marka"); oda = new SqlDataAdapter("SELECT * FROM Kategori", ocnn); DataSet ods2 = new DataSet("DataSet2"); oda.fill(ods2,"kategori"); ods1.mergefailed += new MergeFailedEventHandler(OnMergeFailed); //ods2'yi ods1'ye yükleyelim. ods1.merge(ods2); Bölüm 24

84 552 C# Programlama Dili // Her iki datasette tablo isimleri farklı olduğu için ods1'de iki tablo oluşturulmuş olur. DataSetYazdir(oDs1); Console.ReadLine(); //Main static void OnMergeFailed(object sender, MergeFailedEventArgs e) { Console.WriteLine(e.Conflict); static void DataSetYazdir(DataSet ods) { foreach (DataTable odt in ods.tables) { Console.WriteLine("Table Adı: " + odt.tablename); foreach (DataRow odr in odt.rows) { foreach (DataColumn odc in odt.columns) { Console.Write("\t{0: {1", odc.columnname, odr[odc]); Console.WriteLine(); Table Adı: Marka MarkaId: 1 MarkaId: 2 Table Adı: Kategori KategoriId: 1 KategoriId: 2 MarkaAd: Marka1 MarkaAd: Marka2 KategoriAd: Kategori1 KategoriAd: Kategori DataView Nesnesi DataView nesnesi DataTable daki verileri istenilen şekilde filtreleme, sıralama ve gruplama yapılmasına izin vererek aynı tablodan farklı görünümler elde edilmesini sağlar. Daha çok Web veya Windows tarafındaki Data Binding işlemlerinde kullanılır. Veritabanı literatüründeki View yapısına benzetilebilir. DataView nesnesinin birçok üyesi bulunmaktadır. Bu üyelerden en çok tercih edilenleri şunlardır: AllowDelete: View den kayıt silmeye izin olup olmadığını bildirir. AllowEdit: View içerisinden kayıt düzenleyeme ve güncellemeye izin verilip verilmeyeceğini bool türünde bildirir. Kayıt üzerinde değişikliğin BeginEdit() ve EndEdit() yordamları arasında yapılması gerekir. AllowNew: View içerisinde AddNew() yordamı kullanılarak yeni sayı eklenmeye i- zin verilip verilmeyeceğini bool türünde bildirir. ApplyDefaultSort: Verilerin geçerli sıralama modunda (birincil anahtara göre) olup olmayacağını. Öndeğer olarak false değerindedir. Count: RowFilter veya RowStateFilter kriterleri uygulandıktan sonra View in içerisinde kaç kayıdın olduğunu bildirir. DataViewManager: Sözkonusu DataView nesnesiyle ilişkili DataViewManager nesnesini döndürür. Papatya Yayıncılık Eğitim

85 ADO.NET Bağlantısız Sınıflar 553 Item: DataView nesnesinin indeksleyicisi olup tablodaki parametre olarak aldığı indisteki satırı döndürür. DataRowView türünde değer döndürür. RowFilter: DataView in çok kullanılan bu özelliği string değer alarak tablo üzerinde uygulanacak filtreleri içerir. Bu ifade kriter olarak kullanılacak kolon ve değerini ve ilgili koşul operatörlerini içerir ("AdSoyad='Ahmet Kaymaz'" gibi). RowStateFilter: RowFilter kadar yoğun kullanılan bu özellik ne tür RowState bazlı bir filtrelemenin yapılacağını bildirir. Sort: DataView deki verileri listelerken kullanılacak sıralama ifadesini içerir. Table: DataView nesnesinin ilişki olduğu DataTable nesnesini içerir. AddNew(): DataView nesnesine yeni kayıt ekler. DataRowView türünde değer döndürür. DataView in beslendiği veri tablosunu etkilemez. CopyTo(): İçerdiği kayıtları parametre olarak aldığı diziye aktarır. Web uygulamalarında kullanılır. Delete(): Parametre olarak aldığı indisi uygun satırı DataTable nesnesinden siler. Silinen satırın durumu DataViewRowState.Deleted olarak işaretlenir. Find(): Object türünde değer alarak View içerisinde bu değer veya değerlere uygun satırın indisini döndürür. FindRows(): Object türünde değer alarak View içerisinde bu değer veya değerlere uygun satırları DataRowView türünde döndürür. ToTable(): View içerisinde bulunan kayıtları DataTable nesnesine aktarır. Aşağıdaki dört kayıtlı tablo üzerinde farklı iki görünüm nesnesi uygulanmıştır: static void Main(string[] args) { //Tek kolonlu bir tablo. DataTable odt = new DataTable("Tablo"); DataColumn odc = new DataColumn("Kolon", Type.GetType("System.String")); odt.columns.add(odc); //4 kayıt ekleyelim. DataRow odr; for (int i = 0; i < 4; i++) { odr = odt.newrow(); odr["kolon"] = "Kayit " + (i + 1); odt.rows.add(odr); //Aynı tabloyla ilişkili 2 DataView nesnesi oluşturalım. DataView odv1 = new DataView(oDt); DataView odv2 = new DataView(oDt); //Tablodaki ilk iki kaydı değiştirelim. odt.rows[0]["kolon"] = "Yeni Kayit1"; odt.rows[1]["kolon"] = "Yeni Kayit2"; odt.acceptchanges(); //İlk View'e yeni satır ekleyelim. DataRowView orv; orv = odv1.addnew(); orv["kolon"] = "Yeni Kayıt5"; Bölüm 24

86 554 C# Programlama Dili //Tablodaki satırları listeyelim. Console.WriteLine("----Tablodaki Satırlar----"); for (int i = 0; i < odt.rows.count; i++) { Console.WriteLine((i+1) +".Kayıt: "+ odt.rows[i]["kolon"]); Console.ReadLine(); //Main static void ViewYazdir(DataView odv) { for (int i = 0; i < odv.count; i++) { Console.WriteLine((i+1) +".Satır: " + odv[i]["kolon"]); //ViewYazdir ----Tablodaki Satırlar Kayıt: Yeni Kayit1 2.Kayıt: Yeni Kayit2 3.Kayıt: Kayit 3 4.Kayıt: Kayit 4 Şimdi bu View ler üzerinde işlemler yapalım. //İlk View'i yazdıralım. Console.WriteLine("----İlk View(oDv1) Satırlar----"); ViewYazdir(oDv1); Console.WriteLine(); //İkinci View'i yazdıralım. Console.WriteLine("----İkinci View(oDv2) Satırlar----"); ViewYazdir(oDv2); ----İlk View(oDv1) Satırlar Satır: Yeni Kayit1 2.Satır: Yeni Kayit2 3.Satır: Kayit 3 4.Satır: Kayit 4 5.Satır: Yeni Kayıt5 ----İkinci View(oDv2) Satırlar Satır: Yeni Kayit1 2.Satır: Yeni Kayit2 3.Satır: Kayit 3 4.Satır: Kayit 4 Birinci View üzerinde güncelleme ve filtreleme işlemlerini uygulayalım. //İlk View'in ilk satırını değiştirelim. odv1[0]["kolon"] = "Yeni Kayıt11"; //İlk view için RowStateFilter belirleyelim. odv1.rowstatefilter = DataViewRowState.ModifiedCurrent; //Bu durumda odv1 sadece değeri değişmiş veya eklenmiş satırları gösterir. //ModifiedCurrent yapıldığı için güncellenmiş satırların güncel değerleri okunur.(yeni Kayıt11 gibi) ModifiedOriginal yapmış olsaydık güncellenmiş satırların orijinal değerleri okunurdu (Yeni Kayıt1 gibi). //İlk View'i yazdıralım. Console.WriteLine("--İlk View(oDv1)ModifiedCurrent Satırlar--"); ViewYazdir(oDv1); Papatya Yayıncılık Eğitim

87 ADO.NET Bağlantısız Sınıflar İlk View(oDv1)ModifiedCurrent Satırlar-- 1.Satır: Yeni Kayıt11 2.Satır: Yeni Kayıt5 İkinci View üzerinde veri bazlı filtreleme yapalım. //İkinci view için kriter belirleyelim. odv2.rowfilter = "Kolon >'Kayıt5' "; //İkinci view için sıralama ifadesi. odv2.sort = "Kolon ASC"; //İkinci View'i yazdıralım. Console.WriteLine("----İkinci View(oDv2) Satırlar----"); ViewYazdir(oDv2); ----İkinci View(oDv2) Satırlar Satır: Kayit 3 2.Satır: Kayit 4 3.Satır: Yeni Kayit1 4.Satır: Yeni Kayit2 DataView nesnesinin yapıcı yordamı yeniden yüklenmiştir. Bunlardan birisi herhangi parametre almaz; diğeri önceki örnekte verdiğimiz gibi DataTable türünde parametre alır. Üçüncünün yapısı şu şekildedir: public DataView(DataTable table, string RowFilter, string Sort, DataViewRowState RowState); Bu parametre yapısını temel alarak önceki örnekteki tablo yapısına göre bir DataView nesnesini aşağıdaki gibi tek satırda tanımlayabiliriz. DataView odv = new DataView(oDt, "Kolon > 'Kayit 3'", "Kolon ASC", DataViewRowState.CurrentRows); Console.WriteLine("----View'deki Satırlar----"); ViewYazdir(oDv); ----View'deki Satırlar Satır: Kayit 4 2.Satır: Yeni Kayit1 3.Satır: Yeni Kayit2 Veri tablosunda kayıt arama işlemi için DataView nesnesinin Find() veya FindRows() yordamının da kullanılabileceğini söylenmişti. Bunları da örneklendirelim. DataTable odt = new DataTable("Table"); odt.columns.add("no"); odt.columns.add("adsoyad"); odt.rows.add("10", "Ahmet"); odt.rows.add("11", "Mehmet"); DataView odv = odt.defaultview; Bölüm 24

88 556 C# Programlama Dili // No=11, AdSoyad=Mehmet olan kaydı arayalım. object[] Kriter= new object[2]; Kriter[0] = "11"; Kriter[1] = "Mehmet"; // Kayıtları aşağıdaki gibi sıralasın. odv.sort = "No DESC, AdSoyad ASC"; int x = odv.find(kriter); if (x == -1) Console.WriteLine("Uygun kayıt bulunamadı."); else // Bulduğu kaydın bilgilerini yazdıralım. Console.WriteLine(oDv[x]["No"] +" "+ odv[x]["adsoyad"]); 11 Mehmet Find() yordamı kriterlere uygun bulduğu satırın indisi, bulamazsa geriye -1 döndürür. FindRows() yordamı, Find() gibi object türünde parametre alır. Find() yordamından farklı olarak geriye DataRowView türünde bir dizi döndürür. DataTable odt = new DataTable("Table"); odt.columns.add("no"); odt.columns.add("adsoyad"); odt.rows.add("10", "Ahmet"); odt.rows.add("11", "Mehmet"); DataView odv = odt.defaultview; //No=11, AdSoyad=Mehmet olan kaydı arayalım. object[] Kriter= new object[2]; Kriter[0] = "11"; Kriter[1] = "Mehmet"; //Kayıtları aşağıdaki gibi sıralasın. odv.sort = "No DESC, AdSoyad ASC"; DataRowView[] orv = odv.findrows(kriter); if (orv.length==0) Console.WriteLine("Uygun kayıt bulunamadı."); else foreach (DataRowView odr in orv) Console.WriteLine(oDr["No"] +" "+ odr["adsoyad"]); 11 Mehmet DataView nesnesinin Find() veya FindRows() yordamlarını kullanmak için bir sıralama ifadesi de oluşturmak gerekir. Bunun için ya ApplyDefaultSort özelliği etkin (true) yapılır ya da Sort() yordamı kullanılır. Birden fazla kolon seçilmişse kriter dizisindeki sıra Sort() yordamında verilmiş olan kolon sırasıyla eşleştirilir. Örnekteki Kriter dizisinin ilk elemanı Sort() yordamındaki ilk kolon olan No ya, ikinci eleman ise Sort() yordamında ikinci kolon AdSoyad a referanstır. Bu da kriterdeki kolon sayısıyla sıralamadaki kolon sayının eşit olması gerektiği anlamına gelir. Eğer DataView nesnesinin ApplyDefaultSort özelliği etkin yapılmış ise kriter olarak geçilecek kolon sayısı, birincil anahtar olarak atanmış kolonların sayısına eşit olmalıdır. Papatya Yayıncılık Eğitim

89 ADO.NET Bağlantısız Sınıflar 557 ToTable() yordamı DataView içerisindeki kayıtları yeni bir DataTable nesnesine aktarmanın en kısa yoludur; bu yordam yeniden yüklenmiş olup verileri olduğu gibi tabloya aktarabilir; veya verilecek parametreler doğrultusunda belirli kolon(lar) üzerinde DISTINCT (tekilleştirme) işlemini uygulayarak verileri tabloya aktarabilir. public DataTable ToTable(); public DataTable ToTable(string tablename); public DataTable ToTable(bool distinct, params string[] columnnames); public DataTable ToTable(string tablename, bool distinct, params string[] columnnames); Tablodan TOP N Kayıt Almak Bir tabloda üstteki veya alttaki n tane kaydı okumak isteyebiliriz. DataTable veya DataView nesneleri üzerinde doğrudan TOP N işlemini yaptıracak bir yordam bulunmamaktadır. Bunu yapmanın en kolay yolu kayıtları döngüye sokarak istenilen kayıt kadar okuma yapılıp okunan kayıtların yeni bir nesneye aktarılmasıdır. Eğer tabloda arada eksik kayıt içermeyen kolon varsa bu kolon referans alınarak kolayca TOP N işlemi yapılabilir (DataTable.Select ( IdentiyKolon<N ) gibi). Fakat tablodaki tüm kayıtlar arasındaki değil de belirli koşula uyan kayıtlar arasından seçim yapılacak ise o zaman en mantıklısı döngü yöntemini kullanmaktır. static void Main(string[] args) { //Tek kolonlu bir tablo DataTable odt = new DataTable("Tablo"); odt.columns.add( new DataColumn("No",typeof(Int32))); odt.columns.add(new DataColumn("Aciklama")); //10 kayıt ekleyelim. DataRow odr; for (int i = 0; i < 10; i++) { odr = odt.newrow(); odr["no"] = (i+1); odr["aciklama"] = "Kayit " + (i + 1); odt.rows.add(odr); //DataView nesnesi oluşturalım. DataView odv = new DataView(oDt); //Numarası, 4'ten büyük olanları alalım. odv.rowfilter = "No>4"; odv.sort = "Aciklama DESC"; //Tabloda ilk 5 satırı seçelim. DataView odvtop5 = GetTopRows(oDv,5); Console.WriteLine("----Tablodaki İlk 5 Satır----"); Console.WriteLine("[No]\t[Aciklama]"); Console.WriteLine("----\t "); for (int i = 0; i < odvtop5.count; i++) { Console.WriteLine(oDv[i]["No"] + "\t" + odv[i]["aciklama"]); Console.ReadLine(); //Main Bölüm 24

90 558 C# Programlama Dili // odv parametresi üzerinde işlem yapacağı DataView'i belirtir. // N parametresi de odv den kaç kayıt alması gerektiğini bildirir. static DataView GetTopRows(DataView odv, int N) { // Ara bir DataTable oluşturalım. // Bu tablo, kaynak tablonun aynısı olmalıdır. DataTable otbhedef = odv.table.clone(); //Kaç kayıt isteniyorsa kaynak view'den o kadar okuyalım. //Eğer istenilen kayıt sayısı, kaynak tablodaki satır sayısını aşıyorsa sistem hata vermesin. if (N >= odv.count) N = odv.count; for (int i = 0; i < N; i++) otbhedef.importrow(odv[i].row); return new DataView(oTbHedef); //GetTopRows ----Tablodaki İlk 5 Satır---- [No] [Aciklama] Kayit 9 8 Kayit 8 7 Kayit 7 6 Kayit 6 5 Kayit DataViewManager Nesnesi DataSet içerisinde birden fazla tablo olduğu zaman her tablo için ayrı ayrı DataView nesneleri oluşturup tablo verilerini sorgulamak yerine tüm tablolar için ortak bir sorgulama hazırlamak daha mantıklı olacaktır. Bu işlem için DataSet nesnesinin DataViewManager türünde değer döndüren DefaultViewManager özelliği kullanılır. DataViewManager nesnesi DataView nesnelerinin bulunduğu koleksiyon için tek bir sorgulama nesnesi oluşturur. DataViewManager nesnesi DataSet içerisindeki DataTableCollection koleksiyonundaki tabloları DataViewSetting denilen nesneler üzerinde sorgular. DataView- Setting nesnesini her tablo için çalışan DataView nesnelerinin konfigürasyonu olarak düşünebiliriz. Bu nesne üzerinden her tablo için Sort, RowFilter ve Row- StateFilter ayarları yapılır. Şekil DataSet ve DefaultViewManager ilişkisi Papatya Yayıncılık Eğitim

91 ADO.NET Bağlantısız Sınıflar 559 DataViewManager nesnesi daha çok ilişkili tablolar içeren DataSet yapılarının kullanıldığı Windows uygulamalarında büyük kolaylık sağlar. Aşağıdaki şekil DataSet ve DataViewManager ilişkisini göstermektedir. Şekil DataSet ve DefaultViewManager ilişkisi DataViewManager nesnesinin önemli üyeleri şunlardır: DataSet: DataViewManager nesnesinin ilişkili olduğu DataSet nesnesini belirtir. DataViewSettings: DataSet içinde her tablo için oluşturulmuş DataViewSetting- Collection koleksiyonunu döndürür. CreateDataView(): Belirlenmiş bir tablo için DataView nesnesi oluşturur. Önceki örneklerde kullandığımız Musteri ve Siparis tabloları üzerinde çalışalım. Öncelikle bu iki tablonun olduğu bir DataSet oluşturalım. Bölüm 24

92 560 C# Programlama Dili //DataSet nesnesi oluşturalım. DataSet ods = new DataSet(); //Musteri isminde tablo oluşturalım. DataTable odt = new DataTable("Musteri"); //Musteri tablosunda MusteriId, AdSoyad kolonlarını oluşturalım. DataColumn odc = new DataColumn("MusteriId", System.Type.GetType("System.Int32")); odc.autoincrement = true; odc.autoincrementseed = 1; odt.columns.add(odc); //2.Kolon (AdSoyad kolonu) odt.columns.add("adsoyad", System.Type.GetType("System.String")); //Tabloda iki kayıt girelim DataRow odr = odt.newrow(); odr["adsoyad"] = "Ali Korkmaz"; odt.rows.add(odr); //2.kayıt odr = odt.newrow(); odr["adsoyad"] = "Ayşe Korkmaz"; odt.rows.add(odr); //Musteri tablosunu DataSet içerisine ekleyelim. ods.tables.add(odt); //Siparis tablosunu oluşturalım odt = new DataTable("Siparis"); //Siparis tablosunda SiparisId,MusteriId,UrunId,Adet,Tarih kolonlarını oluşturalım. odc = new DataColumn("SiparisId", System.Type.GetType("System.Int32")); odc.autoincrement = true; odc.autoincrementseed = 1; odt.columns.add(odc); odt.columns.add("musteriid", System.Type.GetType("System.Int32")); odt.columns.add("urunid", System.Type.GetType("System.Int32")); odt.columns.add("adet", System.Type.GetType("System.Int32")); odt.columns.add("tarih", System.Type.GetType("System.DateTime")); //Tarih kolonu için default değer tanımlayalım odt.columns["tarih"].defaultvalue = DateTime.Now; //Siparis tablosuna kayıtlar girelim odr = odt.newrow(); odr["musteriid"] = 1; //Ali Korkmaz odr["urunid"] = 10; //10 numaralı ürün odr["adet"] = 2; odr["tarih"] = "01/01/2007"; odt.rows.add(odr); odr = odt.newrow(); odr["musteriid"] = 1; //Ali Korkmaz odr["urunid"] = 12; //12 numaralı ürün odr["adet"] = 3; odr["tarih"] = "01/01/2007"; odt.rows.add(odr); odr = odt.newrow(); odr["musteriid"] = 2; //Ayşe Korkmaz odr["urunid"] = 10; //10 numaralı ürün odr["adet"] = 1; odt.rows.add(odr); //Siparis tablosunu DataSet içerisine ekleyelim. ods.tables.add(odt); Papatya Yayıncılık Eğitim

93 ADO.NET Bağlantısız Sınıflar 561 DataViewManager nesnesini kullanarak her tablo için ayrı veya ortak ayarlar belirleyelim. //Her tablo için DataView özelliklerini belirleyelim. //Bunun için DataViewManager nesnesi oluşturulur. DataViewManager odvm = new DataViewManager(oDs); //DataViewSettings koleksiyonunu döngüye alıp her tablo için Sort ve RowFilter kriterlerini belirleyelim string otbname=""; foreach (DataViewSetting odvs in odvm.dataviewsettings) { //Default sıralama özelliğini belirleyelim //Bütün tablolar için sıralama Birincil anahtar üzerinden yapılacak. odvs.applydefaultsort = true; //Her tabloya özgü Sort ve RowFilter belirleyelim otbname = odvs.table.tostring(); if (otbname == "Musteri") { odvs.sort = "AdSoyad DESC"; //Veya tek satırda tanımlama yapabiliriz. //odvm.dataviewsettings["musteri"].sort = "AdSoyad DESC"; if (otbname == "Siparis") { odvs.rowfilter = "Tarih=' '"; //foreach //DataViewManager nesnesinin, tablolara uyguladığı seçenekler Console.WriteLine(oDvm.DataViewSettingCollectionString); <DataViewSettingCollectionString> <Musteri Sort="AdSoyad DESC" RowFilter="" RowStateFilter="CurrentRows"/> <Siparis Sort="" RowFilter="Tarih=' '" RowStateFilter="CurrentRows"/> </DataViewSettingCollectionString> DataViewManager nesnesi üzerinden tanımlanmış ayarlar doğrultusunda Siparis tablosunun kayıtlarını listeleyelim. SettingCollectionString değerinden de görüleceği gibi Siparis tablosunda sadece Tarih alanı, olan kayıtlar listelenecektir. //Siparis tablosu için DataView oluşturalım. //Bu işlemi odvm üzerinden yaptığımız için yukarıdaki RowFilter, Sort işlemlerini tabloya uyguladıktan sonra tablodan view oluşturur. DataTable tbsiparis = ods.tables["siparis"]; DataView osdv = odvm.createdataview(tbsiparis); //View'deki satırları listeyelim. foreach (DataRowView odrv in osdv) { for (int i = 0; i < osdv.table.columns.count; i++) Console.WriteLine(oSDv.Table.Columns[i].ColumnName +": "+ odrv[i]); Console.WriteLine(); Bölüm 24

94 562 C# Programlama Dili SiparisId: 1 MusteriId: 1 UrunId: 10 Adet: 2 Tarih: :00:00 SiparisId: 2 MusteriId: 1 UrunId: 12 Adet: 3 Tarih: :00:00 Görüldüğü gibi ADO.NET ortamında DataSet içindeki verileri filtrelemek, aramak ve sıralamak için üç yöntem kullanılmaktadır: DataTable nesnesinin Select() yordamı DataView nesnesi (Find() veya FindRows() yordamları) DataViewManager nesnesi. Bu yöntemlerin haricinde DataSet içinde arama yapmak için XmlDataDocument nesnesi de kullanılabilir; XML türündeki verileri sorgulamak için kullanılan bir nesnedir. En önemli özelliği DataSet nesneleriyle ilişkilendirebiliyor olmasıdır. XML verilerini sorgulamak için Xpath (XML Path Language) denilen sorgu yöntemi kullanılır. Sayfamıza System.Xml kütüphanesini ekleyerek aşağıdaki kodu çalıştıralım. //ods ile ilişkili XmlDataDocument nesnesini oluşturalım. XmlDataDocument oxdd = new XmlDataDocument(oDs); //Arama ifadesini yazalım. XmlNode oxn =oxdd.selectsinglenode("//musteri[adsoyad ='Ali Korkmaz']"); if (oxn!= null) { Console.WriteLine("Bulunan kayıt:"); Console.WriteLine(oXn.OuterXml); else Console.WriteLine("Kayıt bulunamadı."); Bulunan kayıt: <Musteri> <MusteriId>1</MusteriId> <AdSoyad>Ali Korkmaz</AdSoyad> </Musteri> DataTableReader Nesnesi System.Data.DataTableReader nesnesi bir veya daha fazla DataTable veya DataSet nesnelerinden oluşturulup DataReader gibi veriler arasında read-only ve forward-only ilerlememizi sağlar. SQL veritabanı için nasıl ki SqlDataReader kullanılıyorsa DataTable veya DataSet için de DataTableReader nesnesi kullanılır. Papatya Yayıncılık Eğitim

95 ADO.NET Bağlantısız Sınıflar 563 Bu nesnenin avantajı doğrudan bağlantısız nesnelerden beslendiği için veritabanı bağlantısının açık kalma sorunu bulunmamasıdır. Bir tablodan DataTableReader nesnesi oluşturmak için DataSet sınıfının GetDataReader() yordamı kullanılır. DataTableReader sınıfı DbDataReader sınıfından türediği için DataReader sınıfıyla aynı kullanıma sahiptir. //odt tablosundan table reader nesnesi oluşturalım. DataTableReader odr = odt.createdatareader(); if (odr.hasrows) { while (odr.read()) { Console.WriteLine("{0\t{1", odr["musteriid"], odr["adsoyad"]); else Console.WriteLine("Kayıt bulunamadı."); 1 Ali Korkmaz 2 Ayşe Korkmaz 3 Mert Atıl 4 Cüneyt Şen DataTableReader nesnesinin diğer özelliği de birden fazla tablodan beslenebiliyor olmasıdır. Bu durumda ikinci veri tablosuna geçmek için NextResult() yordamı kullanılır. DataTableReader odr = new DataTableReader( new DataTable[]{dtMusteri,dtSehir); if (odr.hasrows) { byte i = 1; do { Console.WriteLine("----{0. Tablo ----",i); while (odr.read()) { Console.WriteLine("{0\t{1", odr[0].tostring(), odr[1].tostring()); i++; while (odr.nextresult()); else Console.WriteLine("Kayıt bulunamadı."); Tablo Ali Korkmaz 2 Ayşe Korkmaz 3 Mert Atıl 4 Cüneyt Şen Tablo Ankara 2 İstanbul 3 Gaziantep Bölüm 24

96 564 C# Programlama Dili DataRelation Nesnesi ADO.NET in en önemli özelliklerinden birisi de DataSet içerisindeki iki tablo arasında ilişki kurabiliyor olmasıdır. Tablolar arasındaki tutarlılığı ifade eden ilişkisel veritabanı modelinin amacı, bir tabloda meydana gelen güncellemeden bu tabloya bağlı diğer tabloların etkilenmesi, güncellemenin onlara da yansıtılmasıdır. Tablolar arasındaki ilişki ana tablo üzerinde bulunan birincil anahtar ve alt tablolar üzerinde bulunan ikincil anahtar aracılığıyla kurulur. İlişkisel veritabanında bir tablo birden fazla tabloyla ilişkili olabilir. İlişkisel veritabanı örneği olarak ürünler tablosunu düşünebiliriz. Her ürünün bir markası ve kategorisi olduğunu düşünelim. Marka ve kategori bilgileri ayrı tablolarda durmaktadır. Ürünlerin hangi marka ve kategoriye ait olduğu bilgisi ürün tablosundaki MarkaId ve KategoriId kolonlarında tutulmaktadır. Bu kolonlar Marka ve Kategori tablolarına işaret etmektedir. Tablolardaki veriler aşağıdaki gibidir: Bu tablolar arasındaki ilişkiyi SQL Sunucudaki diyagram üzerinde gösterelim. Burada Marka ve Kategori tabloları ana (parent) tablo; Urun tablosu çocuk (child) tablo; Marka tablosundaki MarkaId kolonu ana kolon ve Urun tablosundaki MarkaId kolunu ise çocuk kolon olarak tanımlanır. Papatya Yayıncılık Eğitim

97 ADO.NET Bağlantısız Sınıflar 565 ADO.NET te tablolar arası ilişki System.Data.DataRelation sınıfıyla sağlanır. DataRelation işlemi için içinde birden fazla tablonun bulunduğu bir DataSet nesnesi gereklidir. DataRelation sınıfıyla ilgili örneğe geçmeden bu sınıfın önemli üyelerini işleyelim: ChildColumns: Bu ilişkide kullanılacak child kolonları döndürür. DataColumn türünde dizi döndürür. ChildKeyConstraint: Bu ilişkide kullanılan ForeignKeyConstraint nesnesini döndürür. ChildTable: Bu ilişkideki alt tabloyu DataTable türünde döndürür. DataSet: DataRelation nesnesinin ilişkili olduğu DataSet nesnesini döndürür. ParentColumns: Bu ilişkide kullanılan ana tabloya ait kolonları DataColumn türünde bir dizi olarak döndürür. ParentKeyConstraint: Ana tablodaki birincil anahtar seçilen kolondaki verilerin tekil olmasını garanti eden UniqueConstraint nesnesinin döndürür. ParentTable: İlişkinin kurulduğu ana tabloyu döndürür. DataRelation sıfınının yapıcı yordamı yeniden yüklenmiştir. İlişkilendireceği iki tablonun kolon bilgileriyle birlikte ilişki üzerinde kısıtların uygulanıp uygulanmayacağına dair bilgi de parametre olarak girilebilir. Eğer constraint seçeneği etkin yapılırsa ana tabloda yapılan değişiklik alt tablolara da yansır: public DataRelation(string relationname, DataColumn anasutun, DataColumn childcolumn); public DataRelation(string relationname, DataColumn[] anasutun, DataColumn[] childcolumns); public DataRelation(string relationname, DataColumn anasutun, DataColumn childcolumn, bool createconstraints); public DataRelation(string relationname, DataColumn[] anasutun, DataColumn[] childcolumns, bool createconstraints); Şimdi DataRelation ile ilgili bir örnek yapalım. Öncelikle örnek olarak verdiğimiz üç tabloyu oluşturalım: DataSet ods = new DataSet("Iliski"); //Tabloları oluşturmak için DataTable nesnesi. DataTable odt = new DataTable("Marka"); //MarkaId kolonu. DataColumn odc = new DataColumn("MarkaId", typeof(int32)); odc.autoincrement = true; odc.autoincrementseed = 1; odc.readonly = true; odc.unique = true; odt.columns.add(odc); //MarkaAd kolunu. odt.columns.add("markaad"); //Tabloyu DataSet'e ekleyelim. ods.tables.add(odt); odt = new DataTable("Kategori"); Bölüm 24

98 566 C# Programlama Dili //KategoriId kolonu odc = new DataColumn("KategoriId", typeof(int32)); odc.autoincrement = true; odc.autoincrementseed = 1; odc.readonly = true; odc.unique = true; odt.columns.add(odc); //KategoriAd kolunu odt.columns.add("kategoriad"); //Tabloyu DataSet'e ekleyelim ods.tables.add(odt); odt = new DataTable("Urun"); //UrunId kolonu odc = new DataColumn("UrunId", typeof(int32)); odc.autoincrement = true; odc.autoincrementseed = 1; odc.readonly = true; odc.unique = true; odt.columns.add(odc); //UrunAd kolunu odt.columns.add("urunad"); odt.columns.add("kategoriid", typeof(int32)); odt.columns.add("markaid", typeof(int32)); //Tabloyu DataSet'e ekleyelim ods.tables.add(odt); //Tablolara örnek kayıtlar girelim. //-----Marka Tablosu----- Object[] orecord = new object[] { 1, "Marka1" ; ods.tables["marka"].rows.add(orecord); orecord = new object[] { 2, "Marka2" ; ods.tables["marka"].rows.add(orecord); //-----Kategori Tablosu----- orecord = new object[] { 1, "Kategori1" ; ods.tables["kategori"].rows.add(orecord); orecord = new object[] { 2, "Kategori2" ; ods.tables["kategori"].rows.add(orecord); //-----Urun Tablosu----- orecord = new object[] { 1, "Urun1", 1, 1 ; ods.tables["urun"].rows.add(orecord); orecord = new object[] { 2, "Urun2", 1, 2 ; ods.tables["urun"].rows.add(orecord); orecord = new object[] { 3, "Urun3", 2, 1 ; ods.tables["urun"].rows.add(orecord); orecord = new object[] { 4, "Urun4", 1, 1 ; ods.tables["urun"].rows.add(orecord); Tablolara ait birincil anahtarları oluşturalım. //Marka tablosu için birincil anahtaratayalım. odc = ods.tables["marka"].columns["markaid"]; ods.tables["marka"].primarykey = new DataColumn[] { odc ; //Kategori tablosu için birincil anahtaratayalım. odc = ods.tables["kategori"].columns["kategoriid"]; ods.tables["kategori"].primarykey = new DataColumn[] { odc ; //Urun tablosu için birincil anahtaratayalım. odc = ods.tables["urun"].columns["urunid"]; ods.tables["urun"].primarykey = new DataColumn[] { odc ; Papatya Yayıncılık Eğitim

99 ADO.NET Bağlantısız Sınıflar 567 Urun tablosunu Marka ve Kategori tablolarıyla ilişkilendirelim. Urun tablosu Marka tablosuna MarkaId kolonu üzerinden Kategori tablosuna KategoriId kolonu üzerinden bağlanacak. Bu işlem için DataRelation sınıfının yapıcı yordamı kullanılır. İlk parametre ilişkinin adını, ikinci parametre ana tabloya ait kolonu, üçüncü parametre, alt tabloya (child table) ait kolonu ve dördüncü parametre bu ilişki için bir kısıtlama (constraint) yaratılıp yaratılmayacağını belirtir. // Urun tablosunu, diğer tablolarla ilişkilendirelim. DataRelation odrl = new DataRelation("MarkaUrunIliski", ods.tables["marka"].columns["markaid"], ods.tables["urun"].columns["markaid"],true); ods.relations.add(odrl); odrl = new DataRelation("KategoriUrunIliski", ods.tables["kategori"].columns["kategoriid"], ods.tables["urun"].columns["kategoriid"], true); ods.relations.add(odrl); Tablolar üzerinde oluşturulmuş anahtar ve kurdukları ilişki bilgilerini yazdıralım. foreach (DataTable otb in ods.tables) { Console.WriteLine("\nTable: {0", otb.tablename); Console.WriteLine("\tPrimaryKey: {0", otb.primarykey); foreach (DataRelation orl in otb.parentrelations) { Console.WriteLine("\tRelation: {0", orl.relationname); Console.WriteLine("\t\tParent Columns: {0", orl.anasutun); Console.WriteLine("\t\tParentKeyConstraint: {0", orl.parentkeyconstraint); Console.WriteLine("\t\tChild Columns: {0", orl.childcolumns); Console.WriteLine("\t\tChildKeyConstraint: {0", orl.childkeyconstraint); Table: Marka PrimaryKey: MarkaId Table: Kategori PrimaryKey: KategoriId Table: Urun PrimaryKey: UrunId Relation: MarkaUrunIliski Parent Columns: MarkaId ParentKeyConstraint: Constraint1 Child Columns: MarkaId ChildKeyConstraint: MarkaUrunIliski Relation: KategoriUrunIliski Parent Columns: KategoriId ParentKeyConstraint: Constraint1 Child Columns: KategoriId ChildKeyConstraint: KategoriUrunIliski Bölüm 24

100 568 C# Programlama Dili Burada constraint parametresi için true girilmiş olması ilişkide kısıtların aktif olmasını sağlar. Bu durumda ana tablo olan Marka ve Kategori tablolarında silme, güncelleme yapılırsa bu değişiklik Urun tablosuna da yansır. Bu yüzden Marka veya Kategori tablosundan bir kayıt sildiğimizde Urun tablosunda o kayıda denk gelen satırlar da silinir. //MarkaId=1 olan satırı silelim. ods.tables["marka"].rows[0].delete(); //Bu satırdan sonra Urun tablosunda MarkaId=1 olan satırlar da silinmiş olur. //DataSet içindeki her tablonun kayıtlarını listeyelim. foreach (DataTable otb in ods.tables) { Console.WriteLine("----{0 tablosu----", otb.tablename); foreach (DataRow orw in otb.rows) { foreach (DataColumn ocm in otb.columns) { Console.Write(oRw[oCm] + "\t"); Console.WriteLine(); Console.WriteLine(); ----Marka tablosu Marka2 ----Kategori tablosu Kategori1 2 Kategori2 ----Urun tablosu Urun2 1 2 Başka tablo ile ilişkisi olan bir tablodaki satırların, diğer tablodaki ilişkili olduğu satırlara ulaşmak için DataRow sınıfının GetChildRows(), GetParentRow() ve GetParentRows() yordamları kullanılır. public DataRow[] GetChildRows(DataRelation relation); public DataRow[] GetChildRows(string relationname); public DataRow[] GetChildRows(DataRelation relation, DataRowVersion version); public DataRow[] GetChildRows(string relationname, DataRowVersion version); GetChildRows() yordamına Marka ile Urun tablosu arasındaki ilişki adı paremetre olarak gönderilecektir. string MarkaAd, UrunAd; foreach (DataRow omrkrow in ods.tables["marka"].rows) { MarkaAd = omrkrow["markaad"].tostring(); Console.WriteLine(MarkaAd); foreach (DataRow ourrow in omrkrow.getchildrows("markauruniliski")) { UrunAd = ourrow["urunad"].tostring(); Console.WriteLine("\t" + UrunAd); Papatya Yayıncılık Eğitim

101 ADO.NET Bağlantısız Sınıflar 569 Marka1 Marka2 Urun1 Urun3 Urun4 Urun Kısıtlama (Constraint) Kullanımı Veritabanı literatüründe kısıtlama (constraint) kavramı, veri bütünlüğünü sağlamak amacıyla sistem üzerinde tanımlanmış kurallardır. Veriler, veritabanı üzerinde tanımlı kısıtlara göre girilir. Constraint, üzerinde tanımlandığı tabloda verilerin tekil olup olmayacağını, NULL değer kabul edip etmeyeceğini, özellikle birbirleriyle ilişkili tablo durumlarında ana tabloda bir kaydın silinmesi durumunda alt tablolarda bu kayıtla ilişkili satırların bundan nasıl etkileneceğini bildirir. ADO.NET in önceki veri yönetim bileşeni olan ADO dan en önemli farklardan birisi de DataSet nesnesi üzerinde kısıtlara destek veriyor olmasıdır. Kısıtlar, veri tablolarının bir parçası olarak tanımlanır. DataSet içerisindeki tablolara kısıtlamaların uygulanabilmesi için DataSet sınıfının EnforceConstraints özelliği true yapılmalıdır. ADO.NET te ForeignKeyConstraint ve UniqueConstraint olmak üzere iki temel kısıtlama türü vardır. Varsayılan olarak iki veya daha fazla tablo arasında DataRelation sınıfı kullanılarak ilişki kurulduğu zaman alt (child) tabloda ikincil anahtar (foreign key), ana tablodaki birincil anahtar alanında da unique constraint oluşturulur. ADO.NET in bu davranışını engellemek için DataRelation nesnesi createconstraints=false parametresiyle oluşturulur. Tekilleştirme Kısıtlaması (Unique Constraint) Tablodaki sütun/kolon veya kolonlardaki verilerin tekil olmasını, yani aynı verilerin ikinci kez girilmemesini sağlar. Bu kısıtlamanın olduğu kolon NULL değer içerebilir. Bu kısıtlama System.Data.UniqueConstraint sınıfı kullanılarak tanımlanır; yapılandırıcı yordamı yeniden yüklenmiştir. Parametre olarak kısıtlamanın uygulanacağı kolon bilgisini alır; ayrıca diğer kullanım şekillerinde bu kolonların birincil anahtar olup olmayacağı bilgisini de alır. public UniqueConstraint(DataColumn column); public UniqueConstraint(DataColumn[] columns); public UniqueConstraint(DataColumn column, bool isprimarykey); public UniqueConstraint(DataColumn[] columns, bool isprimarykey); public UniqueConstraint(string name, DataColumn column); public UniqueConstraint(string name, DataColumn[] columns); public UniqueConstraint(string name, DataColumn column, bool isprimarykey); public UniqueConstraint(string name, DataColumn[] columns, bool isprimarykey); Bölüm 24

102 570 C# Programlama Dili odt = ods.tables["urun"]; UniqueConstraint ouc = new UniqueConstraint("UrunUnique", odt.columns["urunid"]); odt.constraints.add(ouc); Tek kolon üzerinde tekilleştirme kısıtlaması oluşturmak istendiği zaman bu yönteme alternatif olarak o kolonun Unique özelliğine true atanması yeterlidir; eğer false yerleştirilirse kısıtlama tanımı silinir. Bunlara ek olarak bir veya daha fazla kolon tablonun birincil anahtarı olarak tanımlandığında o kolonlar için kendiliğinden tekil-kısıtlaması oluşturulur. // Unique özelliğini ayarlayarak. ods.tables["urun"].columns["urunid"].unique=true; ods.tables["urun"].columns["urunid"].allowdbnull = false; // Veya kolonu birincil anahtar olarak tanımlayarak unique constraint oluşturulabilir. odc = ods.tables["urun"].columns["urunid"]; ods.tables["urun"].primarykey = new DataColumn[] { odc ; Bu kısıtlamanın sonucunda Urun tablosunun UrunId kolonundaki kayıtların tekilleştirilmiş olması garanti edilmiş oldu. Bu kolona daha önce var olan bir kayıt artık girilemez. İkinci Anahtar Kısıtlaması (ForeignKey Constraint) Bu kısıtlama, birbirleriyle ilişkili tabloların olduğu durumlarda ana tablo üzerinde gerçekleştirilecek silme veya güncellemelerin alt tablolara nasıl yansıtılacağının kurallarını koyar. ADO.NET te tablolar arasında bu tür kuralları oluşturmak için System.Data.ForeignKeyConstraint sınıfı kullanılır. Bu sınıfın yapıcı yordamı yeniden yüklenmiştir. Aşağıdaki örneklerde kullanılan parametreler daha sonraki paragraflarda ele alınmaktadır: public ForeignKeyConstraint(DataColumn anasutun, DataColumn childcolumn); public ForeignKeyConstraint(DataColumn[] anasutun, DataColumn[] childcolumns); public ForeignKeyConstraint(string kısıtadı, DataColumn anasutun, DataColumn childcolumn); public ForeignKeyConstraint(string kısıtadı, DataColumn[] anasutun, DataColumn[] childcolumns); public ForeignKeyConstraint(string kısıtadı, string parenttablename, string[] anasutunnames, string[] childcolumnnames, AcceptRejectRule acceptrejectrule, Rule deleterule, Rule updaterule); public ForeignKeyConstraint(string kısıtadı, string parenttablename, string parenttablenamespace, string[] anasutunnames, string[] childcolumnnames, AcceptRejectRule acceptrejectrule, Rule deleterule, Rule updaterule); Ana tablo üzerinde silme veya güncelleme yapıldığı zaman bu değişikliğin alt tablolara nasıl yansıtılacağı ForeignKeyConstraint sınıfının DeleteRule ve Papatya Yayıncılık Eğitim

103 ADO.NET Bağlantısız Sınıflar 571 UpdateRule özellikleriyle belirtilir. Bu özellikler System.Data.Rule türünde değer alır ve döndürür. Rule numaralandırma aşağıdaki değerleri alır: Cascade: Ana tablosundaki değişiklik alt tablolara olduğu gibi yansır. SetNull: İlişkili satırların değerleri DBNull yapılır. SetDefault: İlişkili satırların değerlerini varsayılan değer olarak yerleştirir. None: Varsayılan değer olarak bu seçenekte ilişkili satırlarda herhangi bir değişiklik yapılmaz. Bu durumda alt-tablo satırları olan ana satırlar silinemez. Aşağıdaki tabloda Marka ve Urun tabloları arasında yabancı kısıt tanımlanmıştır. DataColumn parentcol= ods.tables["marka"].columns["markaid"]; DataColumn childcol= ods.tables["urun"].columns["markaid"]; ForeignKeyConstraint ofkc = new ForeignKeyConstraint("MarkaUrunFKC", parentcol, childcol); //Altında ürün bulunan markalar silinmesin. ofkc.deleterule = Rule.None; ods.tables["urun"].constraints.add(ofkc); Burada görüldüğü gibi ikinci anahtar kısıtlaması için DeleteRule özelliğine Rule.None değerini atadık. Bu ayardan sonra Marka tablosundan bir kayıt silmek istediğimizde sistem hata verecektir. ods.tables["marka"].rows[0].delete(); Cannot delete this row because constraints are enforced on relation MarkaUrunFKC, and deleting this row will strand child rows. Aynı şekilde Kategori ve Urun tabloları arasındaki ilişki için de foreign constraint oluşturalım. parentcol = ods.tables["kategori"].columns["kategoriid"]; childcol = ods.tables["urun"].columns["kategoriid"]; ofkc = new ForeignKeyConstraint("KategoriUrunFKC", parentcol, childcol); //Altında ürün bulunan kategoriler silinebilir //ofkc.deleterule = Rule.None; ods.tables["urun"].constraints.add(ofkc); Eğer Kategori tablosunda herhangi bir tekilleştirme kısıtlayıcısı tanımlanmamış ise, yani KategoriId kolonu Unique=true olarak atanmamış veya tablonun birincil anahtarı değil ise, foreign kısıtlaması oluşturduğumuz bu satırlardan sonra kendiliğinden KategoriId kolonu üzerinde kısıtlama tanımlanmış olacaktır. Üzerinde çalıştığımız DataSet içindeki tablolar (Urun, Marka ve Kategori) birbirleriyle ilişkili durumda ve üzerlerinde kısıtlamalar bulunmaktadır. Bir tablo üzerinde geçerli olan kısıtlamaları yönetmek için DataTable nesnesinin Constraints özelliği kullanılır. Bölüm 24

104 572 C# Programlama Dili Aşağıdaki kod DataSet üzerindeki tüm tablolara ait kısıtlamaların özelliklerini listelemektedir: foreach (DataTable otb in ods.tables) { Console.WriteLine("\nTable: {0", otb.tablename); foreach (Constraint ocns in otb.constraints) { Console.Write("\tConstraint: {0 ", ocns.kısıtadı); if (ocns is UniqueConstraint) { Console.WriteLine("UniqueConstraint"); UniqueConstraint ouct = (UniqueConstraint)oCns; Console.WriteLine("\t\t" + "PrimaryKey:{0", ouct.isprimarykey); else if (ocns is ForeignKeyConstraint) { Console.WriteLine("ForeignKeyConstraint"); ForeignKeyConstraint ofkct = (ForeignKeyConstraint)oCns; Console.WriteLine("\t\t Related" + "Table:{0", ofkct.relatedtable); Console.WriteLine("\t\t Related" + "Column:{0", ofkct.relatedcolumns); Console.WriteLine("\t\t UpdateRule:{0", ofkct.updaterule); Console.WriteLine("\t\t DeleteRule:{0", ofkct.deleterule); Console.WriteLine("\t\t AcceptRejectRule:{0", ofkct.acceptrejectrule); Table: Marka Constraint: Constraint1 UniqueConstraint PrimaryKey:False Table: Kategori Constraint: Constraint1 UniqueConstraint PrimaryKey:False Table: Urun Constraint: UrunUnique UniqueConstraint PrimaryKey:False Constraint: MarkaUrunFKC ForeignKeyConstraint RelatedTable:Marka RelatedColumn:MarkaId UpdateRule:Cascade DeleteRule:None AcceptRejectRule:None Constraint: KategoriUrunFKC ForeignKeyConstraint RelatedTable:Kategori RelatedColumn:KategoriId UpdateRule:Cascade DeleteRule:Cascade AcceptRejectRule:None Papatya Yayıncılık Eğitim

105 ADO.NET Bağlantısız Sınıflar 573 ForeignKeyConstraint sınıfının diğer önemli üyesi AcceptRejectRule özelliğidir. System.Data.AcceptRejectRule türünde değer alan bu özellik None veya Cascade değerini alabilir. Bilindiği gibi DataSet, DataTable ve DataRow nesneleri için ortak bir özellik olarak bu kaynaklar üzerinde yapılan değişiklikler AcceptChanges() ile kabul edilir veya RejectChanges() yordamıyla ret edilir. ForeignKey kısıtlaması içeren bir DataTable veya DataRow nesnesi AcceptChanges() veya Reject- Changes() ile çağrıldığı zaman bunlara bağlı tablolar ForeignKeyConstraint nesnesinin AcceptRejectRule özelliğindeki değere göre hareket eder. Accept- RejectRule ana tablo için AcceptChanges() veya RejectChanges() yordamları çağrıldığı zaman alt tablo satırların aynı yordamları kendileri için çalıştırıp çalıştırmayacağını bildirir. AcceptRejectRule, varsayılan olarak None değerine sahiptir. Bu durumda ana satırlarda yapılan değişiklikten sonra o tablo için AcceptChanges() yordamı çağrıldığı zaman değişiklikler, alt tablo tarafından onaylanmaz; yani alt tablonun AcceptChanges() yordamı çağrılmaz. Accept- RejectRule özelliği Cascade olarak düzeltilirse yapılan değişiklikten sonra ana tablo için AcceptChanges() yordamı çağrılırsa alt tablolar için de Accept- Changes() yordamı çağrılmış olur. Örneğin Tablo1 den bir kayıt silindiğinde (DeleteRule özelliği Rule.Cascade değerindeyse) aynı anda bu tabloyla ilişkili Tablo2 deki kayıtlar da silinir. Sonuçta hem Tablo1 hem de Tablo2 deki ilgili satırlar Deleted olarak düzenlenir. Ardından Table1.AcceptChanges() yordamını çağırdığımızda: AcceptRejectRule.None durumunda Table2 de herhangi bir hareketlilik olmaz. AcceptRejectRule.Cascade durumunda Table2 deki ilişkili satırların durumu da verilir. //Önceki değişiklikleri onaylayalım. ods.acceptchanges(); parentcol = ods.tables["kategori"].columns["kategoriid"]; childcol = ods.tables["urun"].columns["kategoriid"]; ofkc = new ForeignKeyConstraint("KategoriUrunFKC", parentcol, childcol); //Kategori tablosu AcceptChanges() çalıştırsa bile değişiklikler Urun tablosu tarafından onaylanmaz (COMMIT edilmez). ofkc.acceptrejectrule = AcceptRejectRule.None; //ods.enforceconstraints = true; ods.tables["urun"].constraints.add(ofkc); //Kategori tablosundan ilk kaydı silelim. ods.tables["kategori"].rows[0].delete(); //Kategori tablosunda Deleted modunda olan satırların sayısı Console.WriteLine("Kategori-DataViewRowState.Deleted sayısı: " + ods.tables["kategori"].select("", "", DataViewRowState.Deleted).Length); //0 Bölüm 24

106 574 C# Programlama Dili //Kategori tablosuna bağlı ürün tablosu için bakalım. //KategoriId=1 olan 3 ürün vardı. Console.WriteLine("Urun-DataViewRowState.Deleted sayısı: " + ods.tables["urun"].select("", "", DataViewRowState.Deleted).Length);//3 //Değişiklikleri onaylayalım. ods.tables["kategori"].acceptchanges(); Console.WriteLine("\n---Kategori-AcceptChanges() çağrıldı---"); //Tekrar Kategori ve Urun tablosunu sorgulayalım. Console.WriteLine("Kategori-DataViewRowState.Deleted sayısı: " + ods.tables["kategori"].select("", "", DataViewRowState.Deleted).Length);//0 //Urun tablosunda hala 3 adet satır Deleted modunda. Console.WriteLine("Urun-DataViewRowState.Deleted sayısı: " + ods.tables["urun"].select("", "", DataViewRowState.Deleted).Length);//3 Kategori-DataViewRowState.Deleted sayısı: 1 Urun-DataViewRowState.Deleted sayısı: 3 ---Kategori-AcceptChanges() çağrıldı--- Kategori-DataViewRowState.Deleted sayısı: 0 Urun-DataViewRowState.Deleted sayısı: 3 Eğer foreign kısıtı için AcceptRejectRule özelliği AcceptRejectRule.Cascade olarak değiştirilirse aşağıdaki sonuç elde edilir. Aynı durum RejectChanges() yordamı için de geçerlidir. Kategori-DataViewRowState.Deleted sayısı: 1 Urun-DataViewRowState.Deleted sayısı: 3 ---Kategori-AcceptChanges() çağrıldı--- Kategori-DataViewRowState.Deleted sayısı: 0 Urun-DataViewRowState.Deleted sayısı: Hesaplanmış Kolon Oluşturulması Bir veri tablosunda bir kolon başka bir kolona bağlanabilir. Hesaplanmış kolon (computed column) olarak tanımlanan bu durumda, kolon değeri dışarıdan alınmak yerin diğer kolonların değerinden hesaplanır. Bunun için kolon oluşturulma esnasında DataColumn sınıfının yapıcı yordamına üçüncü parametre olarak hesaplama ifadesi girilir. Örneğin üzerinde Fiyat ve Kdv kolonlarının bulunduğu Urun tablosuna KdvliFiyat isminde yeni bir kolon ekleyip bunun değerini Fiyat ve Kdv kolonlarına bağlayabiliriz. odc = new DataColumn("KdvliFiyat", typeof(decimal), "Fiyat*(1+(Kdv/100))"); ods.tables["urun"].columns.add(odc); Papatya Yayıncılık Eğitim

107 ADO.NET Bağlantısız Sınıflar 575 ADO.NET in sağladığı diğer kolaylık hesaplama ifadelerine tablonun ilişkili olduğu diğer tablodaki kolonları da eklememize izin vermesidir. Bu işlem için Parent ve Child sözcüklerini vardır. Parent ifadesi tablonun ilişkili olduğu ana tabloya, Child ifadesi de tablonun ilişkili olduğu alt tablolara erişmek için kullanılır. Birer yordam gibi çalışan bu ifadelere parametre olarak ilişkinin adı girilir. Parent(DataRelationName).anaSutun Child(DataRelationName).anaSutun Örneklerde kullandığımız Urun tablosunu hatırlayalım. Bu tabloya Adet kolonu da eklediğimizi düşünelim. Burada her markanın altında kaç adet ürün olduğunu bulalım. T-SQL komutlarıyla bu işlemi aşağıdaki gibi yapabiliriz: SELECT M.MarkaId,MAX(M.MarkaAd) MarkaAd,SUM(Adet) Adet FROM Marka M INNER JOIN Urun U ON M.MarkaId=U.MarkaId GROUP BY M.MarkaId ADO.NET te bu şekilde o anda dinamik olarak alt tablodan okumak yerine alt tablodaki adetlerin toplamı Urun tablosuna yazdırılabilir. DataRelation odrl = new DataRelation("MarkaUrunIliski", ods.tables["marka"].columns["markaid"], ods.tables["urun"].columns["markaid"], true); ods.relations.add(odrl); odt = ods.tables["marka"]; odc = new DataColumn("UrunToplam", typeof(decimal), "Sum(Child(MarkaUrunIliski).Adet)"); odt.columns.add(odc); Console.WriteLine("MarkaId\tMarkaAd\tUrunToplam"); Console.WriteLine(" "); foreach (DataRow orw in odt.rows) { foreach (DataColumn ocm in odt.columns) { Console.Write(oRw[oCm] + "\t"); Console.WriteLine(); MarkaId MarkaAd UrunToplam Marka Marka2 5 Bölüm 24

108 576 C# Programlama Dili Burada Sum() yordamı kullanıldığı gibi diğer grupsal ifadeleri de (aggregate expression) kullanabiliriz (Min, Max, Avg gibi). Aynı şekilde Urun tablosunda her ürünün karşısında o ürünün marka adını yazdırabiliriz. odt = ods.tables["urun"]; odc = new DataColumn("MarkaAd", typeof(string), "Parent(MarkaUrunIliski).MarkaAd"); odt.columns.add(odc); Konuyla ilgili olarak ADO.NET in sunduğu diğer özellik te grupsal fonksiyonların (aggregate function) kullanılabiliyor olunmasıdır. İstemcinin yerel belleğinde olan veri tablosu üzerinde SQL dilinden bildiğimiz grupsal fonksiyonları (COUNT, SUM, MIN, MAX, AVG gibi) çalıştırabiliriz. Bunun için Compute() yordamı kullanılır. Bu yordam iki parametre alır: public object Compute(string expression, string filter); İlk parametre uygulanacak grupsal fonksiyonun tanımını, ikinci parametre ise hangi kayıtların seçileceğini belirleyerek filtre tanımını içerir. Aşağıdaki tabloda ürünlerin kaç adet satıldığı bilgisi tutulmaktadır. Bu veriler üzerinde toplam, ortalama kaç adet ürün satıldığı bulunmuştur. DataTable odt = new DataTable("Table"); odt.columns.add("urun"); odt.columns.add("adet", Type.GetType("System.Int32")); odt.rows.add("u1", 4); odt.rows.add("u2", 3); odt.rows.add("u1", 2); odt.rows.add("u1", 3); object Toplam = odt.compute("sum(adet)", ""); Console.WriteLine("Toplam kaç adet ürün satılmış: {0", Toplam); Toplam = odt.compute("sum(adet)", "Urun='U1'"); Console.WriteLine("Toplam kaç adet U1 satılmış: {0", Toplam); Toplam kaç adet ürün satılmış: 12 Toplam kaç adet U1 satılmış: 9 Compute() yordamı içerisinde kullanabileceğimiz grupsal fonksiyonlar şunlardır: Sum (Sum - Toplama) Avg (Average - Ortalama) Min (Minimum - En küçük değer) Max (Maximum - En büyük değer) Count (Count - Satır sayısı) StDev (Statistical standard deviation - Standart sapma değeri) Var (Statistical variance-varyans değeri) Papatya Yayıncılık Eğitim

109 ADO.NET Bağlantısız Sınıflar Özet ADO.NET te bağlantılı ve bağlantısız olmak üzere iki tür nesne yapısı kullanılır. Bağlantılı nesneler, yürütülme esnasında veri kaynağıyla bağlantının açık kalmasına ihtiyaç duyarlar. Bağlantısız nesneler ise verileri tümüyle istemcinin belleğinde taşıdıkları için veri okuma ve işleme süreçlerinde açık bağlantıya ihtiyaç duymazlar. Bağlantılı ve bağlantısız katman arasındaki geçişi bağdaştırıcı (DataAdapter) nesneleri (OleDbDataAdapter, SqlDataAdapter, OracleDataAdapter) sağlar. ADO.NET in iki temel bağlantısız sınıfı mevcuttur: DataSet ve DataTable. DataSet, içinde veri tablolarını barındıran, ana veri kaynağından bağımsız olarak istemcinin yerel belleğinde yaşayan bir nesnedir. DataSet nesnesi DataTable kümelerinden oluşur ve veriyi XML formatında saklar. ADO.NET, DataSet içerisindeki iki tabloyu birbiriyle ilişkilendirmemize olanak sağlar. Tablolar arasındaki tutarlılığı ifade eden ilişkisel veritabanı modelinin amacı, bir tabloda meydana gelen güncellemeden bu tabloya bağlı diğer tabloların etkilenmesi, güncellemenin onlara yansıtılmasıdır. ADO.NET te tablolar arası ilişki System.Data.DataRelation sınıfıyla sağlanır. Birbirleriyle ilişkili iki tablo arasındaki veri bütünlüğünü sağlamak için constraint (kısıtlama) denilen yapılar kullanılır. Veriler, veritabanı üzerinde tanımlı kısıtların şart koştuğu düzene göre girilir Sorular 24.1) DataSet ve DataTable nesnelerinin yapısını ve görevlerini açıklayınız? 24.2) DataTable nesnesinde kayıt arama ve filtreleme işlemini örneklendiriniz? 24.3) DataTable içerisindeki değişiklikler nasıl yönetilir? 24.4) LoadOption numaralandırma hangi amaçla kullanılır? 24.5) DataView nesnesinin kayıt arama, filtreleme ve sıralama işlemlerinde kullanınız? 24.6) DataTableReader ile DataReader nesneleri arasındaki farkı açıklayınız? 24.7) Tablolar arasında neden ve nasıl ilişki kurulur? Örnek vererek açıklayınız 24.8) Kısıtlama (constraint) kavramı nedir? Kaç çeşit kısıtlama bulunmaktadır? 24.9) Tekilleştirme kısıtlaması ne amaçla kullanılır? Örnek vererek açıklayınız. Bölüm 24

110 578 C# Programlama Dili Papatya Yayıncılık Eğitim

111 25. DataSet Nesnesi Türleri Typed & Untyped DataSet Veri-kümesi (DataSet) yapıları kullanım biçimlerine göre Untyped ve Typed olmak üzere iki şekilde kullanılır. Typed Dataset tasarım aşamasında şeması yapılandırılmış ve bu şemadaki tablo, kolon/sütun, kısıtlama ve ilişki bilgilerinin birer üye haline getirilerek oluşturulmuş özel bir sınıf şeklidir. Bu bilgiler tasarım aşamasında xml şema dosyası (xsd-xml schema definition) formatında saklanır. Şemadaki bilgiler kodlarımızın bir parçası haline getirilerek kodlar tarafından veri-kümesi üyelerine statik olarak erişilmesi sağlanır. Typed Dataset doğrudan System.Data.DataSet sınıfından türemez; bu sınıftan türemiş özel bir dataset sınıfından türetilir. Untyped Dataset doğrudan System.Data.DataSet sınıfından türemiş ve şeması çalışma zamanında oluşturulmuş tipik veri-kümesidir. Bu tür kümede elemanlara erişebilmek için nesne yönelimli programlamanın sonucu olarak kullanılan namespace.class.member yazım biçimi baştan sonuna kadar yazılır. Örneğin ods olarak isimlendirdiğimiz DataSet nesnesinin içindeki Musteri tablosunun ikinci satırına ait AdSoyad kolonunu okumak için aşağıdaki söz dizimi kullanılır: string AdSoyad = ods.tables["musteri"].rows[1]["adsoyad"].tostring(); Bu şekilde uzun uzun yazmak bazen programcılarda memnuniyetsizliğe neden olmaktadır. Önceki satırları aşağıdaki gibi yazdığımızı düşünelim. string AdSoyad = ods.musteri[1].adsoyad; Böylesi kolay yazım biçimini kullanmak tercih edilen bir durumdur. Burada görüldüğü gibi veri-kümesindeki tablo ve sütunlara onun birer özelliği gibi erişilmiştir. ods nesnesi Typed Dataset niteliğinde olup önceden oluşturulmuş DatasetMusteri sınıfının bir nesnedir.

112 580 C# Programlama Dili Typed Dataset Oluşturulması Typed dataset türünde bir veri-kümesi oluşturmak için iki yöntem kullanılır: Birinci yöntem olarak VS.NET ile birlikte gelen DataSet Designer aracı kullanılır. Bunun için VS.NET içinde projemizin başlığını sağ tıklayıp Add» New Item menüsünden Add New Item diyalog penceresi açılır. Bu pencereden DataSet elemanı seçilir. İsim olarak DatasetMusteri yazıp projeye veri-kümesi nesnesini ekleyelim. Bu işlem sonucunda görsel tabanlı DataSet Designer aracı açılır. Bu arada Toolbox bölümünde bu araçta kullanabileceğimiz kontroller görünür. Bu ekranda TableAdapter kontrolünü kullanarak herhangi bir veritabanına bağlanarak fiziksel bir tabloyu buraya taşıyabiliriz. Fakat ilk örnek olarak burada kendimiz bir tablo oluşturacağız; onun için Toolbox bölümünden DataTable kontrolünü ekrana taşıyıp MusteriId ve AdSoyad kolonlarını oluşturalım. Tabloya yeni kolon eklemek için kontrolü sağ tıklayıp Add» Column menüsünü kullanalım. Kolonların türü ve uzunluğu gibi bilgiler, kontrole ait Properties penceresinden düzenlenir. Papatya Yayıncılık Eğitim

113 DataSet Nesnesi Türleri 581 Bu işlemden sonra projemizde DatasetMusteri.xsd dosyası oluşturulur; dosyada Musteri kümesinin şema bilgisi bulunur. Daha sonra DatasetMusteri.Designer isimli dosya üretilir. Bu dosyada da veri-kümesi bir sınıf haline getirilerek içindeki tablo ve kolon gibi üyeler özellik olarak tanımlanmıştır. Uygulamamızda bu dosyadaki sınıfı (DatasetMusteri) kullanacağız. Bu yöntemin (DataSet Desinger aracının) olumsuz yanı bu işlemi yapabilmek için VS.NET IDE sine ihtiyaç duyuyor olmamızdır. Typed dataset oluşturmanın ikinci yöntemi Framework ile birlikte gelen "XML Schema Definition Tool" (XSD.EXE) aracını kullanmaktadır. Bunu kullanmak için öncelikle çalışma zamanında şu ana kadar işlediğimiz yordamlar aracılığıyla verikümesi nesnesi oluşturulur. Ardından bunun şeması diskte.xsd formatında saklanır. Hatırlanacağı gibi bir veri kümesinin şemasını yazdırmak için WriteXmlSchema() yordamı kullanılır. Musteri tablosunu oluşturup veri-kümesine yükleyelim. // DatasetMusteri isimli dataset nesnesi. DataSet ods = new DataSet("DatasetMusteri"); // Musteri Tablosu. DataTable odt = new DataTable("Musteri"); // Musteri tablosunda MusteriId, AdSoyad kolonlarını oluşturalım. DataColumn odc = new DataColumn("MusteriId", typeof(int32)); odc.autoincrement = true; odc.autoincrementseed = 1; odt.columns.add(odc); // 2.Kolon (AdSoyad kolonu). odt.columns.add("adsoyad", typeof(string)); // Musteri tablosunu DataSet içerisine ekleyelim. ods.tables.add(odt); // Datasetin şemasını oluşturalım. ods.writexmlschema("datasetmusteri.xsd"); Bölüm 25

114 582 C# Programlama Dili Bu basit uygulama çalıştırıldığında projenin altında DatasetMusteri.xsd dosyası oluşur. Aşağıdaki bu şema dosyasının içeriği gösterilmiştir: <?xml version="1.0" standalone="yes"?> <xs:schema id="datasetmusteriasd" xmlns="" xmlns:xs=" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xs:element name="datasetmusteri" msdata:isdataset="true" msdata:usecurrentlocale="true"> <xs:complextype> <xs:choice minoccurs="0" maxoccurs="unbounded"> <xs:element name="musteri"> <xs:complextype> <xs:sequence> <xs:element name="musteriid" msdata:autoincrement="true" msdata:autoincrementseed="1" type="xs:int" minoccurs="0" /> <xs:element name="adsoyad" type="xs:string" minoccurs="0" /> </xs:sequence> </xs:complextype> </xs:element> </xs:choice> </xs:complextype> </xs:element> </xs:schema> Daha sonra VS.NET komut satırını açıp xsd.exe aracını aşağıdaki gibi çalıştıralım. C:\Kitap\Ornek> xsd /d /l:cs DatasetMusteri.xsd Buradaki d parametresi sonucun DataSet nesnesine dönüştürüleceği, l parametresi de dosyanın hangi dilden oluşacağını bildirir. Bu komutu yürüttükten sonra çalışma klasöründe bu şemadan elde edilmiş DatasetMusteri.cs dosyası oluşur. Bu dosyayı açtığımızda oluşan veri-kümesinin yine ADO.NET in genel DataSet sınıfından türediğini ve tablo kolonlarının özellikler olarak tanımlandığını görürüz. public class DatasetMusteri : System.Data.DataSet { private MusteriDataTable tablemusteri;... private System.Data.DataColumn columnmusteriid; private System.Data.DataColumn columnadsoyad;... private void InitClass() { this.columnmusteriid = new System.Data.DataColumn("MusteriId", typeof(int), null, System.Data.MappingType.Element); base.columns.add(this.columnmusteriid); this.columnadsoyad = new System.Data.DataColumn("AdSoyad", typeof(string), null, System.Data.MappingType.Element); base.columns.add(this.columnadsoyad); this.columnmusteriid.autoincrement = true; this.columnmusteriid.autoincrementseed = 1;... Papatya Yayıncılık Eğitim

115 DataSet Nesnesi Türleri 583 Birinci veya ikinci yöntemle ortaya çıkmış bu Typed Dataset nesnesini kullanmak çok kolay. Uygulama içerisinde kullanacağımız veri-kümesini System.Data.DataSet sınıfından değil oluşan bu yeni veri-kümesinden türeteceğiz. Aşağıda bu verikümesin genel diyagramı gösterilmiştir: Şekil DatasetMusteri sınıfının üyeleri Bu yeni sınıfın içinde DataTable nesnesinden bildiğimiz birçok üye oluşturulan veri-kümesindeki tablolara göre oluşturulmuştur. Buradaki isimlendirmelerde tablo ismi ve ön-ek olarak kullanılmıştır. Örneğin veri-kümesindeki Musteri tablosuna erişmek için MusteriDataTable sınıfı, tablonun satır şablonunu almak için NewMusteriRow(), tabloya yeni satır eklemek için AddMusteriRow() ve tablodan satır silmek için RemoveMusteriRow() yordamları kullanılır. // Typed Dataset'ten bir dataset nesnesi oluşturalım. DatasetMusteri ods = new DatasetMusteri(); // Dataset içindeki musteri tablosundan bir örnek alalım. DatasetMusteri.MusteriDataTable odt = new DatasetMusteri.MusteriDataTable(); Bölüm 25

116 584 C# Programlama Dili // Tabloya kayıt ekleyelim. DatasetMusteri.MusteriRow odr = odt.newmusterirow(); odr.musteriid = 1; odr.adsoyad = "Ali Korkmaz"; odt.addmusterirow(odr); odr = odt.newmusterirow(); odr.musteriid = 2; odr.adsoyad = "Ahmet Kaymaz"; odt.addmusterirow(odr); Console.WriteLine("Tablodaki kayıt sayısı: "+ odt.count); Console.WriteLine(); foreach (DataRow odr1 in odt.rows) Console.WriteLine(oDr1["MusteriId"] + "\t" + odr1["adsoyad"]); Tablodaki kayıt sayısı: 2 1 Ali Korkmaz 2 Ahmet Kaymaz Buradaki kolaylık programcının daha az ifadeyle istediği satıra ulaşabiliyor olmasıdır. Aşağıdaki tabloda Typed Dataset ve Untyped Dataset yazım biçiminin farkı gösterilmiştir. // Untyped Dataset yazım biçimi. String Ad =(String)oDs.Tables["Musteri"].Rows[0]["AdSoyad"]; // Typed Dataset yazım biçimi. String Ad = ods.musteri[0].adsoyad; Typed Dataset türündeki veri-kümesi birincil anahtar içeriyorsa doğrudan onun üzerinden arama yapmak için FindByMusteriId() yordamı oluşturulur. Ayrıca ilk sütun birincil anahtar olduğu için kullanıcının o kolonu girmesine gerek kalmadığından AddMusteriRow() yordamının sadece AdSoyad parametresini alan uyarlaması oluşturulur. Typed Dataset ile Untyped Dataset arasındaki başka bir fark ise veri tablosundaki bir kolonun null olup olmadığının sınanma yöntemidir. Bilindiği gibi Untyped Dataset formasyonunda bu işlem için IsNull() kullanılmaktadır. Aynı şekilde bir kolona null değer vermek için DBNull değeri kullanılır. Typed Dataset formasyonundaysa bir kolonun null olup olmadığının sınanması için Is<KolonAdı>Null(), null değerini vermek için de Set<KolonAdı>Null() yordamları kullanılır. Aşağıdaki kodlarda Marka tablosunun ilk satırına ait MarkaAd kolonunun null olup olmadığı kontrol edilmiş, null değilse yapılması sağlanmıştır. Bu amaç için hem Typed Dataset hem de Untyped Dataset formatı verilmiştir. Papatya Yayıncılık Eğitim

117 DataSet Nesnesi Türleri 585 // Typed Dataset formatı. if (!ods.marka[0].ismarkaadnull()) { ods.marka[0].setmarkaadnull(); // UnTyped Dataset formatı. if (!ods.tables["marka"].rows[0].isnull("markaad")) { ods.tables["marka"].rows[0]["markaad"] = Convert.DBNull; Benzer şekilde içerisinde ilişkili tabloların bulunduğu bir typed dataset oluşturulduğunda, ana ait sınıf dosyasında tablolar arasındaki ilişkiyi daha kolay yönetmemizi sağlayacak özel yordamlar oluşturulur. Bu durumu gerçek bir veritabanını kullanarak gösterelim. Veri-kümesi içerisindeki tabloyu doğrudan bir veritabanından beslemek için iki yöntem kullanılır: Dataset designer a ait araç kutusundaki TableAdapter kontrolü veya VS.NET içindeki Server Explorer penceresi kullanılır. Bilindiği gibi bu pencere, VS.NET içinden, sunucu servislerine ve veri tabanlarına erişilmesini sağlar. Buradan oluşturulan bağlantı nesneleri aracılığıyla bir veritabanının altındaki table veya view nesnesini tasarım ekranına taşıyarak bir DataTable nesnesi oluşturulabilir. Daha önce SQL Sunucu üzerinde oluşturduğumuz Urun, Kategori ve Marka tablolarını içerecek bir typed dataset kümesi oluşturalım. Öncelikle veritabanı sunucuyla iletişimizi sağlayacak bir bağlantı nesnesine ihtiyacımız var. Bunu TableAdapter üzerinden yapacağız. TableAdapter kontrolünü kullanmaya çalıştığımızda, bizden kaynak olarak hangi veritabanını kullanacağını sorar. Eğer daha önce oluşturulmuş bir veri bağlantı tanımını varsa onları listeler. Veya New Connection bölümünde ilgili sağlayıcıyı seçip bir yeni bağlantı nesnesi tanımlayabiliriz. Buradaki ekran aracılığıyla üzerinde çalışacağımız veritabanı için bağlantı cümlesi (connection string) oluşturulmuş olur. Bölüm 25

118 586 C# Programlama Dili Burada oluşturulan bağlantı cümlesi, istenilirse projenin.config dosyasında saklı tutulabilir. TableAdapter ün sonraki ekranlarında veritabanı üzerinde çalıştırılacak sorgu hazırlanır. Buradaki Query Builder aracı kullanılarak sorgular görsel olarak hazırlanabilir. Şimdilik herhangi bir sorgu hazırlamayacağız. O yüzden bağlantı ayarlarını girdikten sonra Finish düğmesiyle bağlantı nesnesini Server Explorer penceresine ekleyelim. <connectionstrings> <add name="sqlcnnstr" connectionstring="akaymaz\sql2005;initial Catalog=Kitap;Integrated Security=True" providername="system.data.sqlclient" /> </connectionstrings> Data Connection nesnesinin altındaki Urun, Kategori ve Marka tablolarını verikümesi ekranına taşıyalım. Papatya Yayıncılık Eğitim

119 DataSet Nesnesi Türleri 587 SQL Sunucuda bu tablolar arasında ilişki kurulduğu için veri-kümesi üzerinde bu ilişki otomatik olarak görülür ve ona göre xml şeması oluşturulur. Bu şemaya göre oluşturulmuş sınıf incelendiğinde her tablo için <Tablo Adı> Row() şeklinde yordamların (KategoriRow(), MarkaRow(), UrunRow()) oluşturulduğunu görürüz. Bu yordamlar Untyped Dataset yapısından bildiğimiz GetParentRow() ve GetChildRows() yordamlarına benzer. Örneğin Table- AdiRow() yordamı, alt tablo için tanımlanmış olup mevcut satırın ilişkili olduğu ana satırı döndürür. Aynı şekilde TebleAdiRows() yordamı ana tablo için tanımlanmış olup mevcut satırın ilişkili olduğu alt tablodaki satırları döndürür. Örnek kullanacağımız tabloların, veritabanındaki içeriklerini hatırlayalım. Bölüm 25

120 588 C# Programlama Dili Bu tabloları temsil eden sınıflar içinde tabloları dolduracak adapter nesneleri tanımlanmıştır. Örneğin Urun tablosunu doldurmak için UrunTableAdapter kullanılır. Bu sınıfın isim-uzayı adı projenin adı olarak atanır. Bu bilgiler doğrultusunda oluşturduğumuz veri-kümesi aracılığıyla marka ve o markanın altındaki ürünleri listeleyelim. MusteriDataset ods = new MusteriDataset(); // Tabloları, ilişkili adapterlerini kullanarak dolduralım. UrunTableAdapter odaur= new UrunTableAdapter(); // Aşağıdaki satırda GetData() yordamı, tabloyu doldurur. // Ancak aşağıdaki döngüde GetUrunRows() yordamını kullanırken casting hatası almamak için sonraki satırda gösterilmiş olan Fill() yordamını kullanmalıyız. // MusteriDataset.UrunDataTable odtur = odaur.getdata(); odaur.fill(ods.urun); MarkaTableAdapter odamrk = new MarkaTableAdapter(); // MusteriDataset.MarkaDataTable odtmrk = odamrk.getdata(); odamrk.fill(ods.marka); KategoriTableAdapter odaktgr = new KategoriTableAdapter(); // MusteriDataset.KategoriDataTable odtktgr = odaktgr.getdata(); odaktgr.fill(ods.kategori); string MarkaAd, UrunAd; // Marka tablosunu döngüye alalım. foreach (MusteriDataset.MarkaRow omrkrow in ods.marka) { MarkaAd = omrkrow.markaad; Console.WriteLine(MarkaAd); // Urun tablosundan o anda okunan markayla ilişkili ürünleri listeleyelim. foreach (MusteriDataset.UrunRow ourrow in omrkrow.geturunrows()) { UrunAd = ourrow.urunad; Console.WriteLine("\t"+ UrunAd); Marka1 Marka2 Urun1 Urun3 Urun4 Urun2 Papatya Yayıncılık Eğitim

121 DataSet Nesnesi Türleri 589 Aynı işlemi Untyped Dataset yapısını kullanarak yapalım. foreach (DataRow omrkrow in ods.tables["marka"].rows) { MarkaAd = omrkrow["markaad"].tostring(); Console.WriteLine(MarkaAd); foreach (DataRow ourrow in omrkrow.getchildrows("fk_urun_marka")) { UrunAd = ourrow["urunad"].tostring(); Console.WriteLine("\t" + UrunAd); Bir başka nokta ise alt tablo elemanlarının sıralanmasıdır. İlişki barındıran bir typed dataset örneğinde alt tablo satırlarını sıralamak için özel bir yordam yoktur. ods.urun tablosu için Select() kullanılabilir; fakat bu yordam geriye DataRow formatında değer döndürüyor; oysa UrunRow formatında değer döndürecek bir yordama ihtiyacımız var. Bu işlemi yapabilmek için Array.Sort()'dan yararlanılabilir. GetUrunRows() yordamının sıralama yapıp yapmayacağını parametre alan uyarlamasını hazırlayalım. Array.Sort() yordamının anonim yordam türünde parametre alan uyarlamasını ve iki değeri kıyaslamak için System.Collections.Generic.Comparer sınıfını kullanacağız. Burada ürünlerin adına göre büyükten küçüğe doğru sıralama yapalım. Veri-kümesi için üretilmiş sınıfın içerisine aşağıdaki yordamı yazalım. // Sıralamayı destekleyen GetUrunRows() yordamı. public UrunRow[] GetUrunRows(bool issirali) { UrunRow[] arrurun = GetUrunRows(); if (issirali) { Array.Sort(arrUrun, delegate(urunrow x, UrunRow y) { // Azalan yönde sıralama yapılacağı için parametre sırası y, x olarak verildi. return Comparer<string>.Default.Compare(y.UrunAd, x.urunad); ); return arrurun; Bu yordamı kullanarak markalarla ilişkili ürünleri adına göre azalan sırada sıralayalım. // Marka tablosunu döngüye alalım. foreach (MusteriDataset.MarkaRow omrkrow in ods.marka) { MarkaAd = omrkrow.markaad; Console.WriteLine(MarkaAd); // Urun tablosundan o anda okunan markayla ilişkili ürünleri listeleyelim. foreach (MusteriDataset.UrunRow ourrow in omrkrow.geturunrows(true)) { UrunAd = ourrow.urunad; Console.WriteLine("\t" + UrunAd); Bölüm 25

122 590 C# Programlama Dili Marka1 Marka2 Urun4 Urun3 Urun1 Urun2 Sonuç olarak görüldüğü gibi Untyped Dataset in olumsuz tarafı tasarım aşamasında DataSet in içerdiği tablo, şema ve verilerine ulaşmak için Intellisense özelliği desteklememesi ve tür dönüşümü işlemlerini gerektirmesidir. Ayrıca derleyicinin tasarım aşamasında veri-kümesi şemasının nasıl olacağını bilmiyor olması, az da olsa bir performans sorunu olarak karşımıza çıkmaktadır. Bu yüzden mümkün olduğunca Typed Dataset kullanılması önerilir Özet DataSet yapıları kullanım biçimlerine göre Untyped ve Typed olmak üzere iki şekilde kullanılır. Typed Dataset tasarım aşamasında şeması yapılandırılmış ve bu şemadaki tablo, kolon/sütun, kısıtlama, ilişki bilgilerinin birer üye haline getirilerek oluşturulmuş özel bir sınıftır. Typed Dataset, doğrudan System.Data.DataSet sınıfından türemez; bu sınıftan türemiş özel bir veri-kümesi sınıfından türetilir. Untyped Dataset doğrudan System.Data.DataSet sınıfından türemiş ve şeması çalışma zamanında oluşturulmuş tipik veri-kümesi yapısıdır. Bu tür veri-kümeleri oluşturulduğu anda herhangi bir xml şema içermediği için tasarım anında verikümesinin şemasına erişilemez: Dolayısıyla bu tür veri kümesinin hangi tablo, kolon, kısıtlama ve ilişkileri içereceği çalışma anında şekillenir Sorular 25.1) Veri-kümesi ne amaçla kullanılmaktadır? Örnek vererek açıklayınız. 25.2) Typed Dataset nesnesi nasıl oluşturulur? Örnek vererek açıklayınız. 25.3) Untyped Dataset nesnesi nasıl oluşturulur? Örnek vererek açıklayınız. 25.4) Untyped ve Typed Dataset nesneleri arasındaki temel fark nedir? Örnek vererek açıklayınız. Papatya Yayıncılık Eğitim

123 26. DataAdapter Kullanımı Bağlantısız katmanın aktörleri olan DataAdapter ve DataSet nesneleri fiziksel veri kaynağından istemciye verileri alıp (DataAdapter) verilerin istemci tarafında yönetilmesini (DataSet, DataTable), istemci tarafından yapılan değişiklikleri gözlemleyip aynı yoldan fiziksel veri kaynağa yansıtmakla görevlidirler. Bağlantısız katman kullanmanın olumlu yanı her veritabanı işlemi için bağlantı kurulma ihtiyacının olmaması ve yapılan değişikliklerin bir kerede toplu olarak veritabanına gönderiliyor olmasıdır. Bu bölümde DataAdapter nesnesinin önemli üyeleri olan TableMappings, AcceptChangesDuringFill ve AcceptChangesDuringUpdate özellikleri ve Fill(), FillSchema() ve Update() yordamları ele alınmaktadır Tablo Eşleştirmesi (TableMappings) Veri-kümesi (DataSet) ile veri kaynağı arasındaki iletişimde, öncelikle her iki taraf için tablo eşleştirmesi (mapping) yapılır. Veritabanındaki tablo ve kolonların verikümesindeki hangi tablo ve kolonlarla eşleştirileceği sırasıyla DataTableMapping ve DataColumnMapping sınıfları tarafından yönetilir. DataAdapter nesnesinin bir özelliği olan TableMappings veri kaynağı ile DataSet arasındaki bu eşleştirme işlemini yönetmekle sorumludur. Fiziksel veri kaynağından aldığı kayıt listesini oda.fill(ods) şeklinde DataSet nesnesine aktardığımızda bu kayıtlar ods içerisinde Table isminde bir tabloda saklanır. Aynı DataSet nesnesine başka kayıt kümeleri yüklendiği zaman ondaki tablolar ardışık olarak isimlendirilir (Table, Table1, Table2...). DataSet tarafından kullanılacak isim Fill() yordamına ikinci parametre olarak girilebilir. Öncelikle DataTableMapping ve DataColumnMapping sınıflarının tanımlanma biçimlerini (yapıcı yordamlarını) inceleyelim. // DataTableMapping yapılandırıcı. public DataTableMapping(); public DataTableMapping(string sourcetable, string datasettable); public DataTableMapping(string sourcetable, string datasettable, DataColumnMapping[] columnmappings);

124 592 C# Programlama Dili // DataColumnMapping yapılandırıcı. public DataColumnMapping(); public DataColumnMapping(string sourcecolumn, string datasetcolumn); DataSet'deki tablo isimlerini daha anlaşılır yapmak için DataAdapter in TableMappings özelliğine kaynak ve hedef tablo isimlerini yazmak yeterli olacaktır. Aynı durum, tablolardaki kolon isimleri için de geçerlidir. Kolon isimlerini eşleştirmek için DataTableMapping sınıfının ColumnMappings özelliği kullanılır. Bu özellikleri kullanarak veri-kümesindeki tablo ve kolonları birbirinden bağımsız i- simlendirebiliriz. Her iki özellik koleksiyon türünde değer döndürdüğü için Add(), AddRange(), Remove() ve Insert() yordamları kullanılabilir. DataAdapter nesnesi Fill() yordamını çalıştırdıktan sonra tabloları eşleştirme bilgilerine göre isimlendirir. Eşleştirme bilgisi olmayan bir tabloya denk gelirse otomatik isimlendirme yapar (Table, Table1... gibi). SqlCommand ocmd = new SqlCommand("SELECT * FROM Musteri;SELECT * FROM Urun", ocnn); SqlDataAdapter oda = new SqlDataAdapter(); oda.selectcommand = ocmd; // Tablolar, Table, Table1 olarak isimlendirilir. // İlk tabloyu Musteri olarak map et. oda.tablemappings.add("table", "Musteri"); // İkinci tabloyu Urun olarak map et. oda.tablemappings.add("table1", "Urun"); DataSet ods = new DataSet(); oda.fill(ods); for (int i = 0; i < oda.tablemappings.count; i++) { Console.WriteLine("{0 : {1", oda.tablemappings[i].sourcetable, oda.tablemappings[i].datasettable); Table : Musteri Table1 : Urun Birden fazla tabloyu DataTableMapping sıfınının AddRange() yordamını kullanarak tek satırda ekleyebiliriz. oda.tablemappings.addrange(new DataTableMapping[]{ new DataTableMapping("Table","Musteri"), new DataTableMapping("Table1","Urun") ); Aynı şekilde veri-kümesindeki bir kolonu eşleştirebiliriz. Aşağıdaki satırda Musteri olarak eşleştirilmiş tablodaki MusteriId kolonu Müşteri Kod olarak eşleştirilmiştir. DataTableMapping odtm = oda.tablemappings.add("table", "Musteri"); odtm.columnmappings.add("musteriid", "Müşteri Kod"); Papatya Yayıncılık Eğitim

125 DataAdapter Kullanımı 593 Veya DataTableMapping sınıfının üç parametre alan yapıcı yordamını kullanarak tabloyla birlikte kolonlarını da eşleştirebiliriz. oda.tablemappings.add( new DataTableMapping("Table", "Musteri", new DataColumnMapping[] { new DataColumnMapping("MusteriId", "Müşteri Kod"), new DataColumnMapping("AdSoyad", "Müşteri Adı Soyadı"), )); DataTableMapping ve DataColumnMapping sınıflarını kullanmak için projemize System.Data.Common isim-uzayını eklemeyi unutmamalıyız. Eşleştirme zorunlu bir işlem olmayıp daha çok Windows veya Web uygulamalarında kullanılan Databound (veri bağlanan) kontrollerinde (DataGrid, GridView, DataList...) kullanılır. Bir DataAdapter üzerinde eşleştirme yapıldıktan sonra veri aktarımı esnasında veritabanıyla veri-kümesi arasında eşleştirme sorunu yaşanabilir. Yani veritabanından gelen tablo veya kolonların o adaptör için tanımlanmış eşleştirme koleksiyonunda karşılığı olmayabilir. Böyle bir durumda DataAdapter ün nasıl bir davranış sergileyeceği MissingMappingAction özelliğiyle belirlenir. Bu özellik Error, Ignore ve Passthrough değerleri alır. Error: DataSet'e yüklenen tablo veya kolonların eşleştirme koleksiyonunda karşılığı bulunmazsa hata verir. Aşağıdaki örnekte böyle bir durum gösterilmiştir. SqlDataAdapter oda = new SqlDataAdapter("SELECT * FROM Musteri",oCnn); oda.missingmappingaction = MissingMappingAction.Error; DataSet ods = new DataSet(); oda.fill(ods); Burada oda.tablemappings.add("table", "Musteri") tanımlaması yapılmış olunsaydı bu sefer kolonlar için eşleştirme yapılmadığı için Musteri tablosunun ilk kolonu MusteriId için hata verecekti. Ignore: Veri-kümesine aktarılacak nesnelerden sadece eşleştirme koleksiyonunda karşılığı olanları aktarır; karşılığı bulunmayanlar görmezden gelinir ve bunlar için herhangi bir aykırı durum oluşturulmaz. Dolayısıyla herhangi bir eşleştirme tanımlaması yapılmamışsa veri-kümesine herhangi bir nesne aktarılmaz. Bölüm 26

126 594 C# Programlama Dili SqlDataAdapter oda = new SqlDataAdapter("SELECT * FROM Musteri;SELECT * FROM Urun",oCnn); DataTableMapping odtm= oda.tablemappings.add("table", "Musteri"); odtm.columnmappings.add("musteriid", "MusteriNo"); oda.missingmappingaction = MissingMappingAction.Ignore; Bu durumda veritabanından gelen kayıtlar içerisinden sadece Musteri tablosunu ve ona ait MusteriId kolonunu veri-kümesine aktarır. Passthrough: MissingMappingAction özelliğinin varsayılan değeri olan bu seçenekte veri-kümesine aktarılacak tablo ve kolonlardan eşleştirme (map) koleksiyonunda karşılığı bulunanlar koleksiyondaki isimlerine göre diğerleri ise veritabanındaki isimleri baz alınarak veri-kümesine aktarılır. DataAdapter içerisinde eşleştirme hem veriler hem de şema bazında yapılabilir. Veriler bazında meydana gelebilecek çakışma ve uyuşmazlıklar şema için de geçerlidir. Veritabanından alınan verilerin DataSet e ait şemayla uyuşmaması durumunda DataAdapter ün nasıl davranacağı MissingSchemaAction özelliğiyle belirlenir. Bu özelliğin alacağı değerler şunlardır: Add: Bu seçenek MissingMappingAction özelliğinin varsayılan değeri olup veritabanından gelen kayıtların şeması, DataSet in sahip olduğu şemadan farklı olup olmadığına bakılmaksızın DataSet e eklenir. Aşağıdaki örnekte veritabanından veriler alınmadan önce DataSet içerisinde Table isminde bir tablo oluşturulmuştur. Bellekteki bu tabloda string türünde Kolon1 ve AdSoyad isimli kolonlar eklenmiştir. DataSet ods = new DataSet(); DataTable odt = new DataTable("Table"); odt.columns.add("kolon1", typeof(string)); odt.columns.add("adsoyad", typeof(string)); ods.tables.add(odt); SqlDataAdapter oda = new SqlDataAdapter("SELECT * FROM Musteri",oCnn); oda.missingschemaaction = MissingSchemaAction.Add; oda.fill(ods); foreach (DataColumn odc in ods.tables[0].columns) Console.WriteLine(oDc.ColumnName); Kolon1 AdSoyad MusteriId Telefon DogumTarih AktifPasif Görüldüğü gibi veritabanındaki Musteri tablosunu veri-kümesindeki tabloya aktardığımızda iki tablonun şemasının birleşimi alındı. Papatya Yayıncılık Eğitim

127 DataAdapter Kullanımı 595 Ignore: Veritabanından veri-kümesine aktarılacak tablo ve kolonlardan şemaları uymayanlar görmezden gelinir; aktarılmaz ve bunun için herhangi bir aykırı durum oluşturulmaz. Örneğimizde Ignore seçeneğini kullanırsak DataSet içerisine sadece AdSoyad kolonu yüklenir. Error: Veritabanıyla veri-kümesi kaynaklarının şemaları uyuşmadığı zaman veri aktarımı gerçekleşmez; DataAdapter tarafından hata üretilir. AddWithKey: Bu seçenek Add seçeneği gibi şemanın birleşimini alır ve en önemli ö- zelliği şemaya veritabanındaki birincil anahtar bilgisinin de ekleniyor olmasıdır. Ayrıca kolonların MaxLength ve AllowDBNull gibi değerleri de şemaya dahil edilir. Add seçeneği örnekteki sorgu için SQL Sunucusunda sadece select cümlesini çalıştırır. SELECT * FROM Musteri AddWithKey seçeneği ise tablonun hem şemasını hem de verisini almak üzere aşağıdaki sorguyu çalıştırır. SET FMTONLY OFF; SET NO_BROWSETABLE ON;SELECT * FROM Musteri SET NO_BROWSETABLE OFF; Bu seçeneklerin farkını daha net görmek için her defasında DataSet in WriteXmlSchema() ve WriteXml() yordamlarını kullanarak ondaki şema ve veriyi xml formatında yazdırarak görebiliriz Fill() ve FillSchema() Yordamları Fill() yordamı DataAdapter nesnesinin veri kaynağından çektiği verileri DataSet veya DataTable nesnelerine doldurmasını sağlar. Yeniden yüklenmiş (overload) olan Fill() yordamının birçok kullanım uyarlaması bulunur. Bunlardan en çok kullanılanı parametre olarak DataSet veya DataTable nesnesi alan uyarlamasıdır. Buna ek olarak örneğin başlangıç ve bitiş değerleri verilerek sadece belirli aralıktaki kayıtlar veri-kümesine aktarılabilir. public int Fill(DataTable datatable); public int Fill(DataSet dataset, string srctable); public int Fill(int startrecord, int maxrecords, params DataTable[] datatables); public int Fill(DataSet dataset, int startrecord, int maxrecords, string srctable); Aşağıdaki örnekte veritabanından çekilmiş Musteri tablosundan üçüncü kayıttan itibaren üç satır seçilerek veri-kümesindeki Musteri tablosuna konulmuştur. SqlDataAdapter oda = new SqlDataAdapter("SELECT * FROM Musteri",oCnn); DataSet ods = new DataSet(); oda.fill(ods, 2, 3, "Musteri"); foreach (DataRow odr in ods.tables[0].rows) Console.WriteLine("{0\t{1", odr["musteriid"],odr["adsoyad"]); Bölüm 26

128 596 C# Programlama Dili 3 Mert İkiz 4 Murat Kaya 5 Berk Yavuz FillSchema() yordamı ise DataAdapter ün veritabanından okuduğu tablonun şemasını (kolon türleri, null ve key bilgileri) DataSet veya DataTable nesnesine yükler. Fill() istemcinin belleğine yalnızca veriyi yükler. Bu durumda okunan tablonun veritabanı sunucusundaki yapısıyla DataSet'deki yapısı birbirinden farklı olur. Örneğin sadece Fill() çağrıldığı zaman tablonun birincil anahtar bilgisi istemci tarafına gönderilmez. FillSchema() aşağıdaki şekillerde yeniden yüklenmiştir. public DataTable FillSchema(DataTable datatable, SchemaType schematype); public DataTable[] FillSchema(DataSet dataset, SchemaType schematype, string srctable); Aşağıdaki veri-kümesi tablolarının kolonları listelenmiştir. Fikir vermesi açısından Musteri tablosunun sadece MusteriId, AdSoyad ve AktifPasif kolonları alındı. SqlCommand ocmd = new SqlCommand("SELECT * FROM Musteri;SELECT * FROM Urun", ocnn); SqlDataAdapter oda = new SqlDataAdapter(oCmd); DataSet ods = new DataSet(); oda.fill(ods); ods.tables["table"].tablename = "Musteri"; ods.tables["table1"].tablename = "Urun"; // DataSet içindeki tabloların bilgilerini okuyalım. foreach (DataTable odt in ods.tables) { Console.WriteLine("Tablo Adı: {0", odt); // Tablo üzerindeki birincil anahtar kolonlar. foreach (DataColumn odcpk in odt.primarykey) { Console.WriteLine("\tBirincil anahtar: " + odcpk); // Tablo üzerindeki kısıtlar. foreach (Constraint ocnst in odt.constraints) { Console.WriteLine("\tConstraint.IsPrimaryKey = " + ((UniqueConstraint)oCnst).IsPrimaryKey); foreach (DataColumn odc in ((UniqueConstraint)oCnst).Columns){ Console.WriteLine("\t\toDc.ColumnName = " + odc.columnname); foreach (DataColumn odc in odt.columns) { Console.WriteLine("\n\tColumnName = " + odc.columnname); Console.WriteLine("\t\tDataType = " + odc.datatype); Console.WriteLine("\t\tAllowDBNull = " + odc.allowdbnull); Console.WriteLine("\t\tAutoIncrement = " + odc.autoincrement); Console.WriteLine("\t\tAutoIncrementSeed = " + odc.autoincrementseed); Console.WriteLine("\t\tAutoIncrementStep = " + odc.autoincrementstep); Console.WriteLine("\t\tMaxLength = " + odc.maxlength); Console.WriteLine("\t\tReadOnly = " + odc.readonly); Console.WriteLine("\t\tUnique = " + odc.unique); Papatya Yayıncılık Eğitim

129 DataAdapter Kullanımı 597 Tablo Adı: Musteri ColumnName = MusteriId DataType = System.Int32 AllowDBNull = True AutoIncrement = False AutoIncrementSeed = 0 AutoIncrementStep = 1 MaxLength = -1 ReadOnly = False Unique = False ColumnName = AdSoyad DataType = System.String AllowDBNull = True AutoIncrement = False AutoIncrementSeed = 0 AutoIncrementStep = 1 MaxLength = -1 ReadOnly = False Unique = False ColumnName = AktifPasif DataType = System.Boolean AllowDBNull = True AutoIncrement = False AutoIncrementSeed = 0 AutoIncrementStep = 1 MaxLength = -1 ReadOnly = False Unique = False Elde edilen şema bilgilerinin doğruluğunu kontrol etmek için Musteri tablosunun SQL Sunucu üzerindeki tasarımına bakalım. O tasarımda MusteriId kolonunun birincil anahtar olarak yerleştirildiğini ve AktifPasif kolonun da NULL değerlere izin vermediğini görmekteyiz. Oysa ADO.NET in vermiş olduğu şema bilgileri bunlardan farklı şeyler içeriyor. Burada sadece Fill() yordamını kullandığımız için tabloya ait şemalar istemciye gönderilmedi. Bu yüzden FillSchema() yordamını da çağırmalıyız. Bu örnekte olduğu gibi herhangi bir veri okuma veya listeleme yapılmayacağı için yalnızca FillSchema() yordamını çağırmamız yeterli olacaktır. Bölüm 26

130 598 C# Programlama Dili DataSet ods = new DataSet(); //oda.fill(ods); oda.fillschema(ods, SchemaType.Mapped); ods.tables["table"].tablename = "Musteri"; ods.tables["table1"].tablename = "Urun"; Tablo Adı: Musteri Birincil anahtar: MusteriId Constraint.IsPrimaryKey = True odc.columnname = MusteriId ColumnName = MusteriId DataType = System.Int32 AllowDBNull = False AutoIncrement = True AutoIncrementSeed = 0 AutoIncrementStep = 1 MaxLength = -1 ReadOnly = True Unique = True ColumnName = AdSoyad DataType = System.String AllowDBNull = True AutoIncrement = False AutoIncrementSeed = 0 AutoIncrementStep = 1 MaxLength = 50 ReadOnly = False Unique = False ColumnName = AktifPasif DataType = System.Boolean AllowDBNull = False AutoIncrement = False AutoIncrementSeed = 0 AutoIncrementStep = 1 MaxLength = -1 ReadOnly = False Unique = False Böylece hem tablolara ait birincil anahtar ve kısıtlama bilgilerini hem de kolonlara ait doğru bilgileri elde etmiş olduk. FillSchema() yordamının çağrılması durumunda SQL Sunucu tarafında aşağıdaki satır çalıştırılır: SET FMTONLY OFF; SET NO_BROWSETABLE ON; SET FMTONLY ON;SELECT * FROM Musteri SET FMTONLY OFF; SET NO_BROWSETABLE OFF; FillSchema() yordamının System.Data.SchemaType türünde aldığı ikinci parametre Source veya Mapped değerlerinden birini alarak tabloların şema bilgileri o- luşturulurken tanımlı eşleştirme bilgilerinden nasıl etkileneceğini bildirir: Source değeri seçildiği zaman DataAdapter teki eşleştirme bilgileri yok sayılır. Mapped değerindeyse veritabanından alınan şemaya DataAdapter deki eşleştirme bilgileri uygulanır. Papatya Yayıncılık Eğitim

131 DataAdapter Kullanımı Update() Yordamı Daha önce DbDataAdapter nesnesini anlattığımız paragraflarda onun dört temel komut nesnesi içerdiğini yazmıştık: SelectCommand, InsertCommand, UpdateCommand ve DeleteCommand olarak. Bu özelliklerin veritabanı sunucusuyla veri-kümesi arasında nasıl bir rol oynadıkları msdn deki şu çizimde açıkça gösterilmiştir. Şekil DataAdapter komut nesnelerinin işlevi Bölüm 26

132 600 C# Programlama Dili İstemci tarafında yapılan değişiklikler DataAdapter nesnesinin Update() yordamı aracılığıyla fiziksel veri kaynağına yansıtılır. Yeniden yüklenmiş Update() yordamı duruma göre aşağıdaki parametreleri alır: public int Update(DataRow[] datarows); public override int Update(DataSet dataset); public int Update(DataTable datatable); protected virtual int Update(DataRow[] datarows, DataTableMapping tablemapping); public int Update(DataSet dataset, string srctable); Update() yordamının geri döndürdüğü tamsayı değer güncelleme işleminin başarıyla uygulandığı satır sayısını belirtir; her satır için yapılan işleme göre DataAdapter ün INSERT, UPDATE ve DELETE komut nesnelerini çalıştırır. Veri tablosundaki satırların hangi işlemlerden geçildiği satırların RowState özelliğinden anlaşılır. DataAdapter tablodaki tüm satırları veri kaynağına göndermek yerine sadece değişikliğe uğraşmış satırları gönderir. Bunu da GetChanges() yordamını sorgulayarak öğrenir. DataAdapter nesnesi Update() yordamını çağırdığı zaman aşağıdaki işlemler gerçekleşir: DataRow'da bulunan değerler güncelleme ifadesine parametre olarak gönderilir. RowUpdating olayı tetiklenir. Command nesnesi yürütülür. Varsa çıkış parametreler DataRow içerisine yüklenir. RowUpdated olayı tetiklenir. AcceptChanges() yordamı çağrılır. DataAdapter ün bu önemli komut nesnelerini örneklendirelim: SelectCommand: DataAdapter nesnesinin tanımlanması zorunlu olan özelliği olup veri kaynağından kayıtları çekecek sorguyu temsil eder. Aşağıdaki örnekte DataAdapter nesnesinin SelectCommand özelliği kullanılarak veritabanındaki Musteri tablosu listelenmiştir. string CnnStr SqlConnection ocnn = new SqlConnection(CnnStr); SqlCommand ocmd = new SqlCommand("SELECT * FROM Musteri", ocnn); // Sql Provider için bir DataAdapter tanımlayalım. SqlDataAdapter oda = new SqlDataAdapter(); // DataAdapter'in select komutunu belirleyelim. oda.selectcommand = ocmd; // Verikaynağından çekeceğimi verileri tutacak DataSet. DataSet ods = new DataSet(); // DataAdapter'in aldığı verileri DataSet'e Musteri tablosu olarak aktaralım. oda.fill(ods,"musteri"); DataTable odt = ods.tables["musteri"]; foreach (DataRow or in odt.rows) { Console.WriteLine(oR["AdSoyad"]); Papatya Yayıncılık Eğitim

133 DataAdapter Kullanımı 601 Ali Korkmaz Veli Geçmiş Ayşe Güler1 UpdateCommand: DataAdapter ün, DataSet veya DataTable üzerinde yapılmış güncellemeleri veritabanı sunucusuna gönderirken kullanacağı sorguyu bildirir. Sorgu ifadesi tablo üzerinde hangi kolonlara göre güncelleme yapılacaksa onları olarak alır. Aşağıdaki kodları inceleyelim. // SelectCommand özellik. SqlCommand ocmd = new SqlCommand("SELECT * FROM Musteri", ocnn); oda.selectcommand = ocmd; // UpdateCommand özellik. ocmd = new SqlCommand("UPDATE Musteri SET AdSoyad=@AdSoyad, Telefon=@Telefon WHERE MusteriId=@MusteriId", ocnn); ocmd.parameters.add("@adsoyad",sqldbtype.varchar,50,"adsoyad"); ocmd.parameters.add("@telefon", SqlDbType.VarChar,15,"Telefon"); ocmd.parameters.add("@musteriid", SqlDbType.Int,4,"MusteriId"); oda.updatecommand = ocmd; DataSet ods = new DataSet(); oda.fill(ods, "Musteri"); DataTable odt = ods.tables["musteri"]; // Tablodaki ilk kaydın AdSoyad kolonunu güncelleyelim. odt.rows[0]["adsoyad"] = "Mehmet Çelik"; // İkinci kaydın telefonunu değiştirelim. odt.rows[1]["telefon"] = "(0342) "; // Değişiklikleri veritabanına gönderelim. int RowCount =oda.update(ods, "Musteri"); Console.WriteLine("{0 kayıt güncellendi.", RowCount); 2 kayıt güncellendi. Görüleceği gibi parametreler tanımlanırken o parametrenin kümedeki hangi kolonla eşleneceği de girilmiştir. ocmd.parameters.add("@adsoyad",sqldbtype.varchar,50,"dataset tarafındaki AdSoyad kolonu") Update() yordamı çalıştığı zaman SQL Sunucuya aşağıdaki komutlar gönderilir. exec sp_executesql N'UPDATE Musteri SET AdSoyad=@AdSoyad,Telefon=@Telefon WHERE MusteriId=@MusteriId',N'@AdSoyad varchar(50),@telefon varchar(15),@musteriid int',@adsoyad='mehmet Çelik',@Telefon='(212) ',@MusteriId=1 exec sp_executesql N'UPDATE Musteri SET AdSoyad=@AdSoyad,Telefon=@Telefon WHERE MusteriId=@MusteriId',N'@AdSoyad varchar(50),@telefon varchar(15),@musteriid int',@adsoyad='veli Geçmiş',@Telefon='(0342) ',@MusteriId=2 Bölüm 26

134 602 C# Programlama Dili DeleteCommand: DataAdapter ün, DataSet veya DataTable kaynaklarından silinmiş kayıtları veritabanı sunucusuna gönderirken kullanacağı sorguyu bildirir. Burada parametre olarak sadece MusteriId kolonunu göndermemiz yeterli olacaktır. // DeleteCommand özellik. ocmd = new SqlCommand("DELETE FROM Musteri WHERE MusteriId=@MusteriId", ocnn); ocmd.parameters.add("@musteriid", SqlDbType.Int, 4, "MusteriId"); oda.deletecommand = ocmd; DataSet ods = new DataSet(); oda.fill(ods, "Musteri"); DataTable odt = ods.tables["musteri"]; // MusteriId=3 olan kolonu silelim. // MusteriId, birincil anahtar olduğu için Find() yordamını kullanabilir. odt.rows.find(3).delete(); int RowCount =oda.update(ods, "Musteri"); Console.WriteLine("{0 kayıt silindi.", RowCount); Kodları bu şekilde çalıştırdığımızda Delete()'in çağrıldığı satır Table doesn't have a primary key hata mesajını verecektir. Oysa SQL Sunucu tarafında bu tablo üzerinde MusteriId kolonu birincil anahtar olarak düzenlenmiştir. Önceki konudan hatırlayacağımız gibi FillSchema() yordamını çağırmadığımız için tablonun SQL Sunucu üzerindeki yapısını kaybediyoruz. Sonuçta bellekteki DataTable üzerinde birincil anahtar bulunmadığı için program hata vermektedir. Fill() yordamından sonraki satırları aşağıdaki gibi düzenleyelim. DataSet ods = new DataSet(); // Musteri tablosunun şemasını al. oda.fillschema(ods, SchemaType.Source,"Musteri"); oda.fill(ods,"musteri"); DataTable odt = ods.tables["musteri"]; // Tablonun Birincil anahtar kolonunu kontrol edelim. if (odt.primarykey.length!= 0) { Console.WriteLine("SqlClient'tan gelen PK : " + odt.primarykey[0].columnname); // MusteriId=3 olan kolonu silelim. // MusteriId, birincil anahtar olduğu için Find() yordamını kullanılabilir. odt.rows.find(3).delete(); int RowCount = oda.update(ods, "Musteri"); Console.WriteLine("{0 kayıt silindi.", RowCount); else Console.WriteLine("Bu tablo için Birincil anahtar tanımlı değil"); SqlClient'tan gelen PK : MusteriId 1 kayıt silindi. Papatya Yayıncılık Eğitim

135 DataAdapter Kullanımı 603 Update() yordamı SqlDataAdapter tarafından SQL Sunucu üzerindeki aşağıdaki kodu çalıştırır: exec sp_executesql N'DELETE FROM Musteri WHERE Burada silme işlemi Delete() yerine Remove() ile yapılmış olunsa idi silme işlemi veri kaynağına gönderilmeyecekti. Bunun farkı RowState konusunda ele alınmıştı. InsertCommand: DataAdapter ün, DataSet veya DataTable kaynaklarına sonradan eklenmiş kayıtları veritabanı sunucusuna gönderirken kullanacağı sorguyu bildirir. // InsertCommand özellik ocmd = new SqlCommand("INSERT Musteri(AdSoyad,Telefon) VALUES(@AdSoyad,@Telefon)", ocnn); ocmd.parameters.add("@adsoyad", SqlDbType.VarChar, 50, "AdSoyad"); ocmd.parameters.add("@telefon", SqlDbType.VarChar, 15, "Telefon"); oda.insertcommand = ocmd; DataSet ods = new DataSet(); oda.fill(ods, "Musteri"); DataTable odt = ods.tables["musteri"]; DataRow odr = odt.newrow(); odr["adsoyad"] = "Ali Mert"; odr["telefon"] = "(111) "; // Oluşturduğumuz satırı tabloya ekleyelim. odt.rows.add(odr); // Değişiklikleri veritabanına gönderelim. int RowCount = oda.update(ods,"musteri"); Console.WriteLine("{0 kayıt eklendi.", RowCount); 1 kayıt eklendi. Update() yordamı çalıştırıldığı zaman SQL Sunucu tarafında aşağıdaki ifade çalışır: exec sp_executesql N'INSERT Musteri(AdSoyad,Telefon) VALUES(@AdSoyad,@Telefon)',N'@AdSoyad varchar(50),@telefon varchar(15)',@adsoyad='ali Mert',@Telefon='(111) ' DataAdapter Olayları (Events) DataAdapter nesnesi veri kaynağından veriyi veri-kümesine yüklerken veya küme üzerinde yapılan değişiklikleri veri kaynağına yansıtırken üç temel olayı (event) gerçekleştirir: FillError, RowUpdating ve RowUpdated. FillError olayı Fill() yordamının çağrılmasında istenmeyen bir durum oluştuğu zaman tetiklenir. FillErrorEventArgs türünde parametre alan bu olay veri kaynağından istemci tablolarına veriler aktarılırken meydana gelebilecek hataları yakalamamızı sağlar. Bu amaçla, Continue, DataTable, Errors ve Values özelliklerini sunar. Bölüm 26

136 604 C# Programlama Dili Continue: bool türünde değer alıp Fill() yordamı esnasında istenmeyen bir durum oluştuğu zaman hata üretilip üretilmeyeceğini bildirir. True olarak düzenlendiği zaman o anda bir hata oluşursa hata görmezden gelinir ve işleme devam edilir. DataTable: Hatanın oluştuğu veri tablosunu döndürür. Errors: Exception türünde değer döndürerek oluşan hata hakkında bilgi verir. Values: Object türünde bir dizi döndüren bu özellik tabloya eklenirken hataya neden olan satırın değerlerini içerir. Aşağıdaki örnekte AdSoyad kolunu SQL Sunucu tarafında string türünde, Dataset tarafında integer türünde olduğu için yüklemede hata oluşacaktır. DataSet ods = new DataSet(); // Musteri isminde tablo oluşturalım. DataTable odt = new DataTable("Musteri"); odt.columns.add("musteriid",typeof(int32)); // AdSoyad kolonunu bilerek Integer türünde oluşturalım. odt.columns.add("adsoyad", typeof(int32)); // Tabloyu Dataset içerisine alalım. ods.tables.add(odt); // SQL Sunucu'dan verileri çekelim. SqlDataAdapter oda = new SqlDataAdapter("SELECT MusteriId, AdSoyad FROM Musteri",oCnn); // FillError olayı tetiklendiğinde çalıştırılacak yordam. oda.fillerror += new FillErrorEventHandler(HataOlustu); // Gelen verileri, dataset içindeki Musteri tablosuna yükleyelim. oda.fill(ods,"musteri");... static void HataOlustu(object sender, FillErrorEventArgs e) { Console.WriteLine("Hatanın türü: {0", e.errors.gettype().tostring()); Console.WriteLine("Hatalı kaydın ilk 2 kolonu: {0 - {1", e.values[0], e.values[1]); // Sonraki adımlara devam etsin. Böylece program kırılmamış olur. e.continue = true; Hatanın türü: System.ArgumentException Hatalı kaydın ilk 2 kolonu: 1 - Ali Korkmaz Hatanın türü: System.ArgumentException Hatalı kaydın ilk 2 kolonu: 2 - Metin Korkmaz OnRowUpdating olayı güncellemeden önce, OnRowUpdated olayı ise sonra tetiklenir. Bunlar sırasıyla RowUpdatingEventArgs (OleDbRowUpdatingEventArgs, SqlRow-UpdatingEventArgs gibi) ve RowUpdatedEventArgs (OleDbRowUpdatedEventArgs, SqlRowUpdatedEventArgs gibi) türünde parametre a- lırlar. Bu sınıflar kullanılarak bir satır veritabanına yazılmadan önce veya yazılırken yönetebiliriz. Bu sınıfların ortak özellikleri şunlardır: Command: Mevcut satır için Update() çalıştırıldığında oluşacak DbCommand türündeki komut nesnesini belirtir. Papatya Yayıncılık Eğitim

137 DataAdapter Kullanımı 605 Row: O anda Update() yordamının çalıştırdığı satırı döndürür (DataRow). Status: System.Data.UpdateStatus türünde değer alan bu özellik işlem esnasında oluşailecek bir hatada o satırın nasıl davranacağını bildirir. Continue, Errors- Occurred, SkipAllRemainingRows, SkipCurrentRow değerlerini alabilir. Continue: Güncelleme işlemine devam etmesini ve diğer satırlara geçmesini sağlar. ErrorsOccurred: Güncelleme işleminin bir hata üretmesini engeller. SkipCurrentRow: O anki satırı atlayıp diğer satırları güncellemeye devam eder. SkipAllRemainingRows: Güncelleme operasyonunu sonlandırır; ayrıca hata ü- retmesini engeller. StatementType: Update() yordamının çalıştırdığı SQL deyiminin türünü bildirir (select, insert, delete veya update). TableMapping: Dataadapter ün güncelleme anında veri-kümesi ile veri kaynağı a- rasındaki tablo veya kolon eşleştirmesinde kullandığı DataTableMapping nesnesini döndürür. Errors: Güncelleme esnasında meydana gelen hatayı bildirir. Bu iki olayı kullanmak için Musteri tablomuzun üzerinde iki adet güncelleme ve bir adet silme işlemi yapalım. Açıklamaları ilgili satırların yanında bulabilirsiniz. SqlDataAdapter oda = new SqlDataAdapter("SELECT * FROM Musteri", ocnn); // UpdateCommand özellik. SqlCommand ocmd = new SqlCommand("UPDATE Musteri SET AdSoyad=@AdSoyad WHERE MusteriId=@MusteriId", ocnn); ocmd.parameters.add("@adsoyad", SqlDbType.VarChar,50, "AdSoyad"); ocmd.parameters.add("@musteriid", SqlDbType.Int,4, "MusteriId"); oda.updatecommand = ocmd; // DeleteCommand özellik. ocmd = new SqlCommand("DELETE FROM Musteri WHERE MusteriId=@MusteriId", ocnn); ocmd.parameters.add("@musteriid", SqlDbType.Int, 4, "MusteriId"); oda.deletecommand = ocmd; // RowUpdating ve RowUpdated için handler tanımlayalım. oda.rowupdating += new SqlRowUpdatingEventHandler(Guncelliyor); oda.rowupdated += new SqlRowUpdatedEventHandler(Guncellendi); // Musteri tablonun şema ve verilerini alalım. DataSet ods = new DataSet(); oda.fillschema(ods,schematype.source, "Musteri"); oda.fill(ods,"musteri"); // İlk satırı(musteriid=1) güncelleyelim. ods.tables[0].rows[0]["adsoyad"] = "Zeynep Genç"; // İkinci satırı güncelleyelim. ods.tables[0].rows[1]["adsoyad"] = "Murat Genç"; // MusteriId=10 olan satırı silelim. ods.tables[0].rows.find(10).delete(); oda.update(ods.tables[0]); Bölüm 26

138 606 C# Programlama Dili Dataadapter ün kullanacağı yordamları tanımlayalım. static void Guncelliyor(object sender, SqlRowUpdatingEventArgs e) { Console.WriteLine("\nİşlem tarihi: {0", DateTime.Now); Console.WriteLine("Yapılan işlem: {0", e.statementtype); if (e.statementtype == StatementType.Delete) { Console.WriteLine("{0 ID'li satır silindi.", e.row["musteriid", DataRowVersion.Original]); if (e.statementtype == StatementType.Update) { Console.Write("{0 ID'li satır ",e.row["musteriid"]); Console.WriteLine("{0 -> {1 olarak güncellendi.", e.row["adsoyad",datarowversion.original], e.row["adsoyad", DataRowVersion.Current]); // Ayrıca hiçbirşekilde ID'si 1 olan satırı güncellemesin. if ((int)e.row["musteriid"] == 1) { Console.WriteLine("ID=1 olan satır güncellenemez."); // Bu satırı atlasın. e.status = UpdateStatus.SkipCurrentRow; İşlem tarihi: :22:51 Yapılan işlem: Update 1 ID'li satır Ali Korkmaz -> Zeynep Genç olarak güncellendi. ID=1 olan satır güncellenemez. İşlem tarihi: :22:51 Yapılan işlem: Update 2 ID'li satır Murat Korkmaz -> Murat Genç olarak güncellendi. İşlem tarihi: :22:51 Yapılan işlem: Delete 10 ID'li satır silindi. Satırlar güncellendikten sonraki durumlarını inceleyelim. static void Guncellendi(object sender, SqlRowUpdatedEventArgs e) { // O satırda herhangi bir hata oluşursa. if (e.status == UpdateStatus.ErrorsOccurred) { e.row.rowerror = e.errors.message; // Bu satırdaki operasyonu iptal diğer satırlara geç. e.status = UpdateStatus.SkipCurrentRow; Identity Bilgisinin Alınması SQL Sunucu tarafında bağlantısız katmanda oluşturulan yeni kayıtlar için, var ise, identity kolonunda ID oluşturulmuştur. Bazen bu identity değerine ihtiyacımız olabilir. SQL Sunucu tarafında en son oluşmuş bu genel değişkene aktarılır. Kayıtlar eklendikten hemen sonra sunucudan bu değişkenin değeri Papatya Yayıncılık Eğitim

139 DataAdapter Kullanımı 607 istenir. DataAdapter, Update()'i bitirdikten sonra RowUpdated() olayını tetikler. değişkeni bu olay içerisinde okunup ilgili satır güncellenir. static string CnnStr static SqlConnection ocnn = new SqlConnection(CnnStr); static void Main(string[] args){ // DataAdapter nesnesi. SqlDataAdapter oda = new SqlDataAdapter(); // SelectCommand özellik. SqlCommand ocmd = new SqlCommand("SELECT * FROM Musteri", ocnn); oda.selectcommand = ocmd; // InsertCommand özellik. ocmd = new SqlCommand("INSERT Musteri(AdSoyad,Telefon) VALUES(@AdSoyad,@Telefon)", ocnn); ocmd.parameters.add("@adsoyad", SqlDbType.VarChar, 50, "AdSoyad"); ocmd.parameters.add("@telefon", SqlDbType.VarChar, 15, "Telefon"); oda.insertcommand = ocmd; // DataAdapter için RowUpdated olayını oluşturalım. oda.rowupdated += new SqlRowUpdatedEventHandler(Da_Guncellendi); DataSet ods = new DataSet(); oda.fill(ods, "Musteri"); DataTable odt = ods.tables["musteri"]; DataRow odr = odt.newrow(); odr["adsoyad"] = "Ali Mert"; odr["telefon"] = "(111) "; // Oluşturduğumuz satırı tabloya ekleyelim. odt.rows.add(odr); // Değişiklikleri veritabanına gönderelim. int RowCount = oda.update(ods, "Musteri"); Console.WriteLine("{0 kayıt eklendi.", RowCount); // INSERT işleminden sonra tablodaki satırları listeyelim. foreach (DataRow odr1 in odt.rows) { Console.WriteLine("{0: {1", odr1[0], odr1[1]); // Main static void Da_Guncellendi(object sender, SqlRowUpdatedEventArgs e){ // Sadece güncelleme yapılırken ve yapılan işlem INSERT ise. if ((e.status == UpdateStatus.Continue) && (e.statementtype == StatementType.Insert)) { SqlCommand ocmd = new ocnn); // Eklenmiş satırın MusteriId kolonuna SQL'den dönen değeri yazdıralım. e.row["musteriid"] = ocmd.executescalar(); e.row.acceptchanges(); // Da_Guncellendi Bölüm 26

140 608 C# Programlama Dili Bu yönteme alternatif olarak iki yöntem daha kullanılabilir. Bunlardan ilki ekleme (insert) işlemi yapan ve geriye o bilgisini döndüren bir prosedür hazırlamak, diğeri de komut nesnesinin (DbCommand) yeni eklediği satırı seçen bir sorgu hazırlamaktır. Her iki yöntemin SQL tarafındaki deyimleri tabloda gösterilmiştir. CREATE PROCEDURE int OUT AS INSERT INTO Musteri(AdSoyad) VALUES(@AdSoyad) = SCOPE_IDENTITY() INSERT Musteri(AdSoyad) VALUES(@AdSoyad); SELECT * FROM Musteri WHERE MusteriId=SCOPE_IDENTITY() Her iki yöntemde de veritabanından gelen sonucun DataSet te o anki satırı nasıl etkileyeceği DbCommand nesnesinin UpdateRowSource özelliğiyle belirlenir. System.Data.UpdateRowSource türünde olan bu özellik aşağıdaki değerleri alabilir: Both: Hem çıkış parametreleri hem de dönen kayıt kümesinin ilk satırı DataTable'de güncellenecek satır eşleştirilir. Eğer komut nesnesi herhangi bir sonuç döndürmezse, sözkonusu satırın RowState özelliği Unchanged olarak düzenlenir. FirstReturnedRecord: Komut nesnesinin yürütme sonrası döndürdüğü ilk satırın bilgileri tablodaki güncellenecek satıra eşleştirilir. Bu durumda satırın RowState özelliği Modified olarak düzenlenir. Eğer veritabanından herhangi bir satır dönmezse RowState özelliği Added olarak düzenlenir. None: Komut nesnesinden dönen çıkış parametreleri görmezden gelinir; söz konusu satır güncellenmez ve satırın RowState özelliği Added olarak düzenlenir. OutputParameters: Komut nesnesinin yürütme sonrası döndürdüğü çıkış parametreleri doğrultusunda tablodaki satır güncellenir. Musteri tablosunda her satırın ne zaman kayıt edildiğini tutan KayitTarih isimli kolonun olduğunu ve bunun doğrudan SQL Sunucu tarafından varsayılan olarak Getdate() fonksiyonuyla doldurulduğunu düşünelim. Yeni bir müşteri kayıt ettikten sonra tüm müşterilerin AdSoyad bilgisini ve bu kayıt tarihlerini listeleyeceğiz. static void Main(string[] args) { SqlCommand ocmd = new SqlCommand("SELECT * FROM Musteri", ocnn); oda.selectcommand = ocmd; // InsertCommand özellik. ocmd = new SqlCommand("dbo.MusteriEkle", ocnn); ocmd.commandtype = CommandType.StoredProcedure; ocmd.parameters.add("@adsoyad", SqlDbType.VarChar, 50, "AdSoyad"); // MusteriId için output parametre tanımlayalım. // parametresi, datasetteki MusteriId kolonuna yerleştirecek. SqlParameter opr = ocmd.parameters.add("@musteriid", SqlDbType.Int, 4, "MusteriId"); Papatya Yayıncılık Eğitim

141 DataAdapter Kullanımı 609 opr.direction = ParameterDirection.Output; oda.insertcommand = ocmd; // Dönen sonuç o anki satırın identity kolonuna yerleştirilsin. oda.insertcommand.updatedrowsource = UpdateRowSource.OutputParameters; oda.rowupdated += new SqlRowUpdatedEventHandler(Guncellendi); DataTable odt = new DataTable(); oda.fill(odt); DataRow odr = odt.newrow(); odr["adsoyad"] = "Zeynep Kaya"; odt.rows.add(odr); // Değişiklikleri veritabanına gönderelim. oda.update(odt); Console.WriteLine("\n---Tablonun İçeriği---"); Console.WriteLine("MId\tAdSoyad\t\tKayitTarih"); foreach (DataRow odr1 in odt.rows) { Console.WriteLine("{0\t{1\t{2\t{3", odr1["musteriid"], odr1["adsoyad"], odr1["kayittarih"], odr1.rowstate); Console.ReadLine(); // Main static void Guncellendi(object adapter, SqlRowUpdatedEventArgs e) { Console.WriteLine("Eski Müşteri Id: {0", e.row["musteriid",datarowversion.original]); Console.WriteLine("Yeni Müşteri Id: {0", e.row["musteriid"]); Console.WriteLine("RowState: {0",e.Row.RowState); // Guncellendi. Eski Müşteri Id: Yeni Müşteri Id: 2 RowState: Modified ---Tablonun İçeriği--- MId AdSoyad KayitTarih 1 Ali Korkmaz :19:00 Unchanged 2 Zeynep Kaya Unchanged Örnekte InsertCommand komut nesnesi yürütüldüğünde MusteriEkle yordamı auto-incremented identity değerini döndürür ve bunu yeni eklemiş satıra yansıtır. Böylece o satırın durumu Added durumundan Modified durumuna geçmiş olur ve güncellemeden sonra Unchanged olarak düzenlenir. int exec Kaya',@MusteriId=@p2 output Bölüm 26

142 610 C# Programlama Dili Örnekte görüldüğü gibi son eklenen satırın tüm bilgileri değil, sadece identity bilgisi güncellenmiş oldu. Çünkü prosedür sadece bir tane çıkış parametresi döndürüyor. Kolonun tüm bilgilerini güncellemek için Insert işleminden sonra o satırın tüm bilgileri istemciye gönderilmeli ve DataSet içerisindeki satırın veritabanıyla aynı olması için UpdateRowSource.FirstReturnedRecord seçeneği kullanılmalıdır.... // InsertCommand özellik. ocmd = new SqlCommand("INSERT Musteri(AdSoyad) VALUES(@AdSoyad);SELECT * FROM Musteri WHERE MusteriId=SCOPE_IDENTITY()", ocnn); ocmd.parameters.add("@adsoyad", SqlDbType.VarChar, 50, "AdSoyad"); SqlParameter opr = ocmd.parameters.add("@musteriid", SqlDbType.Int, 4, "MusteriId"); opr.direction = ParameterDirection.Output; oda.insertcommand = ocmd; // Dönen ilk sonuç, o anki satırın tüm kolonlarını güncellesin. oda.insertcommand.updatedrowsource = UpdateRowSource.FirstReturnedRecord;... Eski Müşteri Id: Yeni Müşteri Id: 2 RowState: Modified ---Tablonun İçeriği--- MId AdSoyad KayitTarih 1 Ali Korkmaz :19:00 Unchanged 2 Zeynep Kaya :36:00 Unchanged AcceptChangesDuringFill ve AcceptChangesDuringUpdate Özellikleri Bilindiği gibi Fill() yordamı kullanılarak veritabanından tablolara aktarılmış satırlar için tablonun AcceptChanges() yordamı çağrılır. Böylece Fill() yordamından sonra her satırın RowState özelliği Unchanged olarak düzenlenir. Eğer AcceptChanges() yordamı çağrılmamış olsaydı, satırların RowState özelliği Added olarak düzenlenirdi. DataAdapter sınıfının AcceptChangesDuringFill ö- zelliği bu işlem için kullanılır. bool türünde değer alan bu özellik false olarak düzenlenirse yapılan yükleme için AcceptChanges() yordamı çağrılmaz; yani satırların RowState özelliği Added olarak ayarlanır. Benzer durum adaptörün günceleme süreci için de geçerlidir. DataAdapter ün Update() yordamı çağrıldıktan sonra tablo üzerinde değişikliklerin onaylanması için AcceptChanges() yordamı yürütülür. Update() yordamından sonra AcceptChanges()'ın çağrılıp çağrılmayacağı DataAdapter sınıfının bool türünde değer alan AcceptChangesDuringUpdate özelliğiyle belirlenir. Bu özellik false olarak düzenlenirse Update() işleminden sonra tablodaki veriler henüz onaylanmamış olur. Papatya Yayıncılık Eğitim

143 DataAdapter Kullanımı 611 Varsayılan olarak Fill() yordamı çağrıldıktan sonra satırların RowState durumu aşağıdaki gibi olur: SqlDataAdapter oda = new SqlDataAdapter("SELECT * FROM Musteri", ocnn); SqlCommandBuilder ocb = new SqlCommandBuilder(oDa); DataSet ods = new DataSet(); Console.WriteLine("Fill() den önce: {0",oDs.HasChanges()); // False oda.fill(ods, "Musteri"); Console.WriteLine("Fill() den sonra: {0", ods.haschanges()); // True Console.WriteLine(); foreach (DataRow odr in ods.tables[0].rows) Console.WriteLine("{0\t{1\t{2", odr["musteriid"], odr["adsoyad"], odr.rowstate); Fill() den önce: False Fill() den sonra: False 1 Ali Korkmaz Unchanged 2 Ahmet Kaymaz Unchanged 5 Mert İkiz Unchanged oda nesnesinin AcceptChangesDuringFill özelliğini false olarak düzenlersek sonuç aşağıdaki gibi değişir: oda.acceptchangesduringfill = false; Fill() den önce: False Fill() den sonra: True 1 Ali Korkmaz Added 2 Ahmet Kaymaz Added 5 Mert İkiz Added Bu iki özelliğin amacı Fill() ve Update() yordamlarından sonra DataSet veya DataTable üzerinde yapılmış değişiklikleri kaybetmemektir. Bu durum normal bir örnekte anlamlı görünmese de tabloların aynı veya farklı adaptörler arasında aktarılmasında önemli rol oynar. Aşağıdaki örnekte ODBC provider üzerinden alınmış bir tablo SQL Sunucudaki başka bir tabloya aktarılmıştır. Her iki tablonun şemasının aynı olduğu düşünülmüştür. string CnnStrSql SqlConnection ocnnsql = new SqlConnection(CnnStrSql); string CnnStrOdbc OdbcConnection ocnnodbc = new OdbcConnection(CnnStrOdbc); OdbcDataAdapter odaodbc = new OdbcDataAdapter("SELECT stock_id UrunKod,long_name UrunAd FROM l_product", ocnnodbc); SqlDataAdapter odasql = new SqlDataAdapter("SELECT UrunKod,UrunAd FROM Urun2", ocnnsql); SqlCommandBuilder ocb = new SqlCommandBuilder(oDaSql); Bölüm 26

144 612 C# Programlama Dili // odaodbc'deki satırlar Added olarak yerleştirilecek. odaodbc.acceptchangesduringfill = false; DataSet ods = new DataSet(); // Her iki adapterün verileri DataSet'teki aynı tabloya aktarılmıştır. odaodbc.fill(ods, "Urun"); odasql.fill(ods, "Urun"); // odaodbc'deki satırlar Added olduğu için SQL'deki tabloya eklenmiş olacak. odasql.update(ods, "Urun"); AcceptChangesDuringUpdate özelliği de güncelleme işleminden sonra tablodaki satırlar için AcceptChanges()'in çalıştırılıp çalıştırılmayacağını belirtir. Örnekte, üzerinde birincil anahtar bulunan Musteri tablosu çekilmiş ve tabloya istemci tarafında yeni bir satır eklenmiştir. Değişen durumları daha açık görmek için sunucu tarafında tablonun son iki kaydını silelim. Bu durumda yeni bir satır eklendiği zaman o satırın identity değeri (MusteriId) 5 olur. Yani tabloda 2 den 5 e atlanmış olur. Bu tabloyu birincil anahtar bilgisiyle birlikte istemci tarafında bir DataTable (odtmusteri) nesnesine alıp yeni satır (AdSoyad=Zeynep Akkuş) eklediğimizde, istemci tarafında bu satırın identity değeri 3 olur. Bu satırı başka bir tabloya alacağız (odtdegisen). Ardından bu tabloyu DataAdapter aracılığıyla sunucu üzerinde güncelleyeceğiz. Bu durumda yeni satır SQL Sunucuya gelir ve identity değeri 5 olarak yerleştirilir. Sonuçta bu satırın DataTable ve veritabanı tarafındaki identity değerleri farklı oldu. Bu yüzden eklemeden hemen sonra bu satırın sunucu tarafındaki bilgilerini de alıp DataTable (odtdegisen) tarafında güncelleyeceğiz. Bu durumda odtdegisen tablosu güncellenmiş olur; yani o satırın MusteriId kolonunun Original değeri 3, Current değeri 5 olur. Eğer Update() yordamından sonra AcceptChanges() yordamı çağrılırsa buradaki 3 ün yerine 5 değeri yazılır ve 3 değeri ezilmiş olur. Oysa bu 3 değerine ihtiyacımız var. Neden mi? odtdegisen tablosu güncellendi, fakat kayıtların ilk alındığı ve yeni satırın oluşturulduğu odtmusteri tablosunda bu satırın identity değeri hala 3 görünür. Bu değeri güncellemek üzere odtmusteri tablosunu odtdegisen tablosuyla birleştireceğiz. Birleştirme işleminin doğru gerçekleşmesi için her iki tarafta bulunan yeni satırın birincil anahtar bilgilerinin eşleşmesi gerekir. İşte bu noktada söz konusu yeni satı- Papatya Yayıncılık Eğitim

145 DataAdapter Kullanımı 613 rın odtdegisen tablosundaki orijinal identity değerine ihtiyacımız olacaktır. Şimdi yapmak istediklerimizi kodlayalım. static void Main(string[] args) { SqlDataAdapter oda = new SqlDataAdapter("SELECT * FROM Musteri", ocnn); oda.insertcommand = new SqlCommand( "INSERT INTO Musteri (AdSoyad) VALUES (@AdSoyad); " + "SELECT MusteriId,AdSoyad FROM Musteri " + "WHERE MusteriId = SCOPE_IDENTITY();", ocnn); parametresi tanımlayalım. oda.insertcommand.parameters.add( new SqlParameter("@AdSoyad", SqlDbType.VarChar, 40, "AdSoyad")); // InsertCommand'ın döndürdüğü kayıt, tablonun son satırını güncellesin. oda.insertcommand.updatedrowsource = UpdateRowSource.Both; // Birincil anahtar bilgisini alalım. oda.missingschemaaction = MissingSchemaAction.AddWithKey; // Update() yordamından sonra AcceptChanges() yordamını çağırmasın. oda.acceptchangesduringupdate = false; DataTable odtmusteri = new DataTable(); oda.fill(odtmusteri); DataRow odr = odtmusteri.newrow(); odr["adsoyad"] = "Zeynep Akkuş"; odtmusteri.rows.add(odr); // Musteri tablosundaki değişiklikleri yeni bir tabloya aktaralım. DataTable odtdegisen = odtmusteri.getchanges(); Console.WriteLine("--- odtdegisen tablosu değişiklikleri ---"); odtdegisen.rowchanged += new System.Data.DataRowChangeEventHandler(Row_Changed); // odtdegisen tablosunu SQL Sunucue'e gönderelim. oda.update(odtdegisen); Console.WriteLine("\n ---odtdegisen Tablosu ---"); foreach (DataRow odr1 in odtdegisen.rows) Console.WriteLine("{0\t{1\t{2\t{3", odr1["musteriid", DataRowVersion.Original], odr1["musteriid"], odr1["adsoyad"], odr1.rowstate); Console.WriteLine("\n ---odtmusteri Tablosu [Merge Öncesi] ---"); foreach (DataRow odr1 in odtmusteri.rows) Console.WriteLine("{0\t{1\t{2", odr1["musteriid"], odr1["adsoyad"], odr1.rowstate); // İki tabloyu merge edelim, Original birincil anahtar üzerinden güncellem yapılır. odtmusteri.merge(odtdegisen); Console.WriteLine("\n ---odtmusteri Tablosu [Merge Sonrası] ---"); foreach (DataRow odr1 in odtmusteri.rows) Console.WriteLine("{0\t{1\t{2", odr1["musteriid"], odr1["adsoyad"], odr1.rowstate); Console.ReadLine(); Bölüm 26

146 614 C# Programlama Dili static void Row_Changed(object sender, DataRowChangeEventArgs e) { Console.WriteLine("{0\t{1\t [{2]", e.row.itemarray[0], e.row.itemarray[1], e.action); --- odtdegisen tablosu değişiklikleri Zeynep Akkuş [Commit] 5 Zeynep Akkuş [Change] MusteriId değişti 5 Zeynep Akkuş [Change] AdSoyad değişti --- odtdegisen Tablosu Zeynep Akkuş Modified 3->DataTable tarafındaki identity (DataRowVersion.Original) 5->Database tarafındaki identity --- odtmusteri Tablosu [Merge Öncesi] Ali Korkmaz Unchanged 2 Mert Cesur Unchanged 3 Zeynep Akkuş Added --- odtmusteri Tablosu [Merge Sonrası] Ali Korkmaz Unchanged 2 Mert Cesur Unchanged 5 Zeynep Akkuş Modified Eş Zamanlı Uyumsuzluk Sorunu (Concurrency Violation) Eş zamanlılık kavramı, veritabanı işlemlerinde birden fazla kullanıcının aynı anda aynı verilere erişmesi durumudur. Çok kullanıcılı sistemlerde eş zamanlı veri erişim işlemlerinde en önemli tehlike veri tutarsızlığı oluşmasıdır. Büyük ölçekli veri tabanları, sürpriz sonuçların ortaya çıkmaması için concurrency control denilen mekanizmayı kullanırlar. Temelde kaynak-kilitleme (data locking) yapısını kullanan bu mekanizma aynı anda aynı veri kaynaklarını kullanacak işlemleri yöneterek doğru verilerle çalışılmasını sağlar. Kilitleme (locking) işlemi için bir örnek verelim: Kullanıcı1, veritabanındaki bir satırı kendi tarafına aldı. Daha sonra Kullanıcı2 de aynı satırı kendi tarafına aldı. Kullanıcı2 okuduğu satır üzerinde güncelleme yaptı ve gerçek veritabanı üzerinde güncelledi. Bu işlemden haberdar edilmeyen Kullanıcı2 de aldığı aynı satır üzerinde güncelleme yaptı ve gerçek veritabanı üzerinde güncelledi. Bu senaryoda veri bütünlüğü nasıl sağlanacak? Hangi kullanıcının verileri kabul edilecek? Veritabanları bu soruları yanıtlamak için iki çeşit kilitleme işlemi uygular: Pessimistic Locking (Kötümser Kilitleme): İlk kullanıcı söz konusu kaynağı okumaya başladığı zaman kaynağa kilit konur. Böylece sonraki kullanıcıların kaynağa erişmesi engellenir. Böylece verilerin doğruluğu garanti altına alınmış olunur. Optimistic Locking (İyimser Kilitleme): İki kullanıcının aynı anda aynı kayıt üzerinde güncelleme yapma ihtimalinin az olacağı yaklaşımına dayanan bu yöntemde okunma esnasında kaynak kilitlenmez; ancak kullanıcı tarafından yapılan güncelleme güncellenmeden önce değişiklik olup olmadığına dair durumu kontrol edilir. Papatya Yayıncılık Eğitim

147 DataAdapter Kullanımı 615 Optimistic Locking yaklaşımında veri bütünlüğünü korumak amacıyla güncelleme a- şamasında birkaç kontrol yöntemi uygulanır: Hiçbir kontrol yapmamak, bütün kolonları kontrol etmek, sadece güncellenecek kolonları sınamak, satırların uyarlamasını kontrol etmek. İlk yöntemde güncelleme birincil anahtar referans alınarak yapılır. Birincil anahtar dışındaki kolonların en son okumadan sonra değişip değişmediğine bakılmaz. UPDATE Musteri SET WHERE Genel olarak tek-kullanıcılı sistemlerde tercih edilen bu yöntem, çok kullanıcılı uygulamalarda uygulandığı zaman, eğer başka kullanıcılar tarafından bir güncelleme yapılmışsa yeni güncellemeler onların üzerine yazılır. İkinci yöntemde güncelleme yapılmadan önce o satırdaki kolonların son okumadan sonra değişip değişmediğine bakılır. UPDATE Musteri SET AdSoyad=@YeniAdSoyad WHERE MusteriId=@MusteriId AND AdSoyad=@OrjinalAdSoyad AND Telefon=@OrjinalTelefon Bu yöntemde veritabanına gönderilecek update cümlesinde bütün kolonların en son a- lınmış orijinal değerleri de eklenir böylece ikinci kullanıcı, varsa ilk kullanıcının yaptığı güncellemeden haberdar olur. ADO.NET te dataset için wizard tool aracını kullandığımızda dataadapter ün komut nesnelerine ait sorgular bu yöntemde hazırlanır. Üçüncü yöntem olarak güncelleme yapılmadan önce o satırda sadece güncellenecek kolonların değişip değişmediği kontrol edilir. UPDATE Musteri SET AdSoyad=@YeniAdSoyad WHERE MusteriId=@MusteriId AND AdSoyad=@OrjinalAdSoyad Son yöntem olarak satırın versiyonu kontrol edilir. Bu amaçla SQL Sunucu tarafında her satırın üzerinde timestamp veya uniqueidentifier türünde bir kolon konulur. Bu türden tanımlanmış bir kolon bulunduğu satırın versiyon kolonu olup her güncellemeden sonra tekil bir değer alır. UPDATE Musteri SET AdSoyad=@YeniAdSoyad WHERE MusteriId=@MusteriId AND TimestampID Bu yöntemin olumlu yanı çok kolonlu bir tabloda her kolonu tek tek sınamak yerine yalnızca birincil anahtar ve bu türdeki kolonu kontrol etmek yeterli olacaktır. Bu yöntemler ışığında güncelleme esnasında herhangi bir uyumsuzluk meydana gelirse DataAdapter nesnesi DbConcurrencyException türünde bir hata üretir. Bununla ilgili basit bir örnek yapalım. Üzerinde herhangi bir birincil anahtarın tanımlı olmadığı Musteri tablosunda AdSoyad kolonuna göre güncelleme yapalım. Verileri SQL Sunucudan kendi tarafımıza alıp veri-kümesi üzerinde güncelleme yapacağız. Güncellemeyi sunucuya yeniden göndermeden önce sunucu tarafına geçip verikümesinde güncellenen kaydın AdSoyad kolonunu değiştirelim. Bölüm 26

148 616 C# Programlama Dili SqlCommand ocmd = new SqlCommand("SELECT * FROM Musteri", ocnn); oda.selectcommand = ocmd; // UpdateCommand özellik. ocmd = new SqlCommand("UPDATE Musteri SET Telefon=@Telefon WHERE AdSoyad=@AdSoyad", ocnn); ocmd.parameters.add("@adsoyad", SqlDbType.VarChar, 50, "AdSoyad"); ocmd.parameters.add("@telefon", SqlDbType.VarChar, 15, "Telefon"); oda.updatecommand = ocmd; DataSet ods = new DataSet(); oda.fill(ods, "Musteri"); DataTable odt = ods.tables["musteri"]; // İlk kaydın telefonunu değiştirelim. odt.rows[0]["telefon"] = "(0342) "; Console.ReadLine(); // SATIRI SQL SUNUCUDA DEĞİŞTİR. // Değişiklikleri veritabanına gönderelim. int RowCount = oda.update(ods, "Musteri"); Programı bu şekilde çalıştırıp SQL Sunucu tarafında el ile güncelleme yaptıktan sonra programa geri dönüp Update() yordamını çalıştırdığımızda aşağıdaki hata ekranıyla karşılaşırız: Verileri ilk çektiğimizde sunucu tarafında ilk satırın AdSoyad kolonunda Ahmet Çelik yazıyordu. DataSet tarafında bu satıra ait telefon bilgisini değiştirdik. Update() yordamını çağırmadan önce sunucuya gidip AdSoyad kolonunu Mehmet Çelik olarak değiştirdik. Daha sonra programı çalıştırmaya devam ettiğimizde SQL Sunucuya aşağıdaki güncelleme cümlesi gitmiş oldu. exec sp_executesql N'UPDATE Musteri SET Telefon=@Telefon WHERE AdSoyad=@AdSoyad',N'@AdSoyad varchar(50),@telefon varchar(15)',@adsoyad='ahmet Çelik',@Telefon='(0342) ' Papatya Yayıncılık Eğitim

149 DataAdapter Kullanımı 617 Bu ifade hata verdi. Çünkü SQL Sunucudaki tabloda AdSoyad kolonu Ahmet Çelik olan herhangi bir satır bulunmamaktadır. Bu tür hatalarda kullanıcı bilgilendirilmelidir. Hatayı yönetmek için ilk akla gelen Update() yordamını try-catch bloğunda çağırmaktır. Bu durumda kullanıcının yaptığı diğer doğru güncellemeler de geri alınır. Bu yönteme alternatif olarak DataAdapter sınıfının RowUpdated olayını yönetebiliriz. Burada hataya neden olan satırları yakalama imkanımız da olur. Tablodaki satırların hatalı olup olmadığını DataRow nesnesinin HasErrors özelliğinden anlayabiliriz. Ayrıca hatanın ne olduğu bilgisini DataRow nesnesinin string türünde değer döndüren RowError özelliğinden öğrenebiliriz. Bu bilgilere göre güncellediğimiz cümlemizi aşağıdaki gibi düzenleyelim. Bu arada veri-kümesindeki iki satırda güncelleme ve bir satırda da silme işlemi yapalım. static void Main(string[] args) { SqlDataAdapter oda = new SqlDataAdapter(); // SelectCommand özellik. SqlCommand ocmd = new SqlCommand("SELECT * FROM Musteri", ocnn); oda.selectcommand = ocmd; // DeleteCommand özellik. ocmd = new SqlCommand("DELETE FROM Musteri WHERE MusteriId=@MusteriId",oCnn); ocmd.parameters.add("@musteriid", SqlDbType.Int, 4, "MusteriId"); oda.deletecommand = ocmd; // UpdateCommand özellik. ocmd = new SqlCommand("UPDATE Musteri SET Telefon=@Telefon WHERE AdSoyad=@AdSoyad", ocnn); ocmd.parameters.add("@adsoyad", SqlDbType.VarChar, 50, "AdSoyad"); ocmd.parameters.add("@telefon", SqlDbType.VarChar, 15, "Telefon"); oda.updatecommand = ocmd; oda.rowupdated += new SqlRowUpdatedEventHandler(Guncellendi); DataSet ods = new DataSet(); oda.fillschema(ods, SchemaType.Source, "Musteri"); oda.fill(ods, "Musteri"); DataTable odt = ods.tables["musteri"]; // ID'si 7 olan kolonu silelim. odt.rows.find(7).delete(); // İlk iki kaydın telefonunu değiştirelim. odt.rows[0]["telefon"] = "(0212) "; odt.rows[1]["telefon"] = "(0212) "; ; Console.ReadLine();// 2. SATIRI SQL SUNUCUDA DEĞİŞTİR. // Değişiklikleri veritabanına gönderelim. int RowCount = oda.update(ods, "Musteri"); Console.WriteLine("{0 kayıt güncellendi.", RowCount); Bölüm 26

150 618 C# Programlama Dili // Hatalı satırları yazdıralım. foreach (DataRow odr in odt.rows) { if (odr.haserrors) Console.WriteLine(oDr["AdSoyad"] + " -> " + odr.rowerror); Console.ReadLine(); // Main static void Guncellendi(object sender, SqlRowUpdatedEventArgs e) { if (e.recordsaffected == 0) { e.row.rowerror = "Optimistic Concurrency Violation hatasi olustu."; e.status = UpdateStatus.SkipCurrentRow; 2 kayıt güncellendi. Mehmet -> Optimistic Concurrency Violation hatası oluştu. Yine aynı şekilde programı durduğumuz satırda SQL Sunucu tarafında ikinci satırın AdSoyad kolonu değiştirelim. Programın sonucu SQL Sunucuya aşağıdaki gibi yansır: exec sp_executesql N'UPDATE Musteri SET Telefon=@Telefon WHERE AdSoyad=@AdSoyad',N'@AdSoyad varchar(50),@telefon varchar(15)',@adsoyad='ahmet',@telefon='(0212) exec sp_executesql N'UPDATE Musteri SET Telefon=@Telefon WHERE AdSoyad=@AdSoyad',N'@AdSoyad varchar(50),@telefon varchar(15)',@adsoyad='mehmet',@telefon='(0212) exec sp_executesql N'DELETE FROM Musteri WHERE MusteriId=@MusteriId',N'@MusteriId int',@musteriid=7 Başka bir örnek olarak SQL Sunucu tarafında birincil anahtar olan MusteriId kolonu istemci tarafında güncellenebilir. Bu durumda güncelleme ve silme işlemleri bu kolon üzerinden yapıldığı için bu kolonun veri-kümesindeki güncellenmiş değeri görünmezden gelinmeli, SQL Sunucu tarafındaki değeri korunmalıdır. Bunun için bu kolonu parametre olarak tanımlarken alacağı değerin asıl uyarlamasındaki değeri olacağını belirtmemiz gerekir. SqlParameter oprm= ocmd.parameters.add("@musteriid", SqlDbType.Int, 4, "MusteriId"); oprm.sourceversion = DataRowVersion.Original; DbCommandBuilder Nesnesi DbCommandBuilder sınıfı DataAdapter nesnesinin SelectCommand özelliği için tanımlı olan SQL sorguya göre update, delete ve insert ifadelerini oluşturan, bağlantısız katmanın kodlanmasını kısaltan bir sınıftır. OleDb ve SqlClient için uyarlanabilen bu nesne adaptörün select ifadesinde kullanılmış kolon ve türlerine göre Papatya Yayıncılık Eğitim

151 DataAdapter Kullanımı 619 diğer komut ifadelerini oluşturur. Bu işlemleri yapmak için sınıfın yapıcı yordamına kullanılan adaptörü parametre olarak göndermemiz yeterlidir. DataAdapter nesnesine ait update, delete ve insert ifadelerini oluşturmak için CommandBuilder sınıfının GetInsertCommand(), GetUpdateCommand() ve GetDeleteCommand() yordamları kullanılır. SqlDataAdapter oda = new SqlDataAdapter("SELECT MusteriId, AdSoyad FROM Musteri", ocnn); DataSet ods = new DataSet(); oda.fill(ods, "Musteri"); SqlCommandBuilder ocb = new SqlCommandBuilder(oDa); oda.insertcommand = ocb.getinsertcommand(); oda.updatecommand = ocb.getupdatecommand(); oda.deletecommand = ocb.getdeletecommand(); // Oluşan sorguları yazdıralım. Console.WriteLine(oDa.InsertCommand.CommandText); Console.WriteLine(oDa.UpdateCommand.CommandText); Console.WriteLine(oDa.DeleteCommand.CommandText); INSERT INTO [Musteri] ([AdSoyad]) VALUES (@p1) UPDATE [Musteri] SET [AdSoyad] WHERE (([MusteriId] AND ((@p3 = 1 AND [AdSoyad] IS NULL) OR ([AdSoyad] DELETE FROM [Musteri] WHERE (([MusteriId] AND ((@p2 = 1 AND [AdSoyad] ISNULL) OR ([AdSoyad] SQL Sunucuya gönderilmek üzere oluşturulan şeklinde ardışık olarak numaralandırılmaktadır. ADO.NET 2.0 ile birlikte bunların kolon adlarına göre isimlendirilmesi sağlanabilmektedir. Bunun için GetInsert- Command(), GetUpdateCommand() ve GetDeleteCommand() yordamlarının usecolumnsforparameternames parametre alan uyarlaması kullanılır. oda.insertcommand = ocb.getinsertcommand(true) oda.updatecommand = ocb.getupdatecommand(true) oda.deletecommand = ocb.getdeletecommand(true) INSERT INTO [Musteri] ([AdSoyad]) VALUES (@AdSoyad) UPDATE [Musteri] SET [AdSoyad] WHERE (([MusteriId] AND ((@IsNull_AdSoyad = 1 AND [AdSoyad] IS NULL) OR ([AdSoyad] DELETE FROM [Musteri] WHERE (([MusteriId] AND ((@IsNull_AdSoyad = 1 AND [AdSoyad] IS NULL) OR ([AdSoyad] Aynı şekilde kullanılan komut nesnesinin türü stored procedure ise çalışma anında bu yordamın üst-verisi (metadata) doğrultusunda parametlerine erişilip komut nes- Bölüm 26

152 620 C# Programlama Dili nesinin parametre koleksiyonu oluşturulur. Bu işlem için SqlCommandBuilder sınıfının statik DeriveParameters() yordamı kullanılır. public static void DeriveParameters(SqlCommand command); Bu yordamı yalnızca bağlantısız katman için değil bağlantılı katman için de kullanabiliriz. Önceki örneklerde kullandığımız MusteriEkle saklı yordamını hatırlayalım. CREATE PROCEDURE int OUT AS INSERT INTO Musteri(AdSoyad) = SCOPE_IDENTITY() Bu prosedür dışarıdan AdSoyad parametresini almakta ve dışarıya MusteriId parametresini döndürmektedir. SqlCommand nesnesi için bu yordamı kullanmak istiyoruz: Fakat SqlCommand nesnesinin parametre koleksiyonunun tasarım sürecinde değil de çalışma anında oluşturulmasını istiyoruz. SqlCommand ocmd = new SqlCommand("MusteriEkle", ocnn); ocmd.commandtype = CommandType.StoredProcedure; ocnn.open(); // Stored Procedure'in parametrelerini okuyup ona göre. // SqlCommand.Parameters koleksiyonunu oluşturalım. SqlCommandBuilder.DeriveParameters(oCmd); // Komut nesnesinin parametrelerini okuyalım. foreach (SqlParameter osp in ocmd.parameters) { Console.WriteLine("{0 -> {1", osp.parametername, -> -> -> InputOutput CommandBuilder nesnesinin saklı yordamın parametre yapısını öğrenme talebi SQL Sunucu 2005 tarafında aşağıdaki komut satırını çalıştırır. exec Bu yöntemde ADO.NET yordamı çalıştırmadan önce yordamın üst-verisini okumak üzere veritabanına gidip gelmektedir. Bu da performans sorunu olarak değerlendirilmektedir. CommandBuilder sınıfının diğer önemli üyesi ConflictOption ve SetAllValues özellikleridir. ConflictOption özelliği concurrency violation durumunda adaptö- Papatya Yayıncılık Eğitim

153 DataAdapter Kullanımı 621 rün davranışını belirler. Bilindiği gibi bağlantısız katmanda farklı kullanıcıların birbirinden habersiz olarak aynı veriler üzerinde değişiklik yapması veri çakışmasına neden olabilir. Bu durumda ya son güncelleme yapan kişinin güncellemesi kabul edilir ya da güncelleme kabul edilmez; kullanıcı da bu konudan haberdar edilir. ConflictOption özelliği bu çakışma durumlarını yönetir. Alabileceği değerler: CompareAllSearchableValues: Delete, Update cümleleri için hazırlanan sorguda tablonun timestamp türündeki kolon hariç tüm kolonlar WHERE bölümüne konulur. Bilindiği gibi timestamp türündeki kolon, bulunduğu satırın tekilliğini garanti etmek ve o satırın güncellenip güncellenmediğini öğrenmek için kullanılır. CompareRowVersion: Bu durumda koşul bölümüne birincil anahtar ve zamandamgası kolonu eklenir. OverwriteChanges: Bu seçenek o satırda varsa daha önce yapılmış güncelleme görmezden gelinir. Bu yüzden koşul bölümüne yalnızca birincil anahtar alanı konulur. Musteri tablosuna timestamp türünde SatirTarih kolonunu ekleyip bu seçenekleri uygulayalım. SqlDataAdapter oda = new SqlDataAdapter("SELECT MusteriId, AdSoyad, SatirTarih FROM Musteri", ocnn); SqlCommandBuilder ocb = new SqlCommandBuilder(oDa); ocb.conflictoption = ConflictOption.CompareAllSearchableValues; oda.insertcommand = ocb.getinsertcommand(); oda.updatecommand = ocb.getupdatecommand(); oda.deletecommand = ocb.getdeletecommand(); // Oluşan sorguları yazdıralım. Console.WriteLine(oDa.InsertCommand.CommandText); Console.WriteLine(); Console.WriteLine(oDa.UpdateCommand.CommandText); Console.WriteLine(); Console.WriteLine(oDa.DeleteCommand.CommandText); INSERT INTO [Musteri] ([AdSoyad]) VALUES (@p1) UPDATE [Musteri] SET [AdSoyad] WHERE (([MusteriId] AND ((@p3 = 1 AND [AdSoyad] IS NULL) OR ([AdSoyad] DELETE FROM [Musteri] WHERE (([MusteriId] AND ((@p2 = 1 AND [AdSoyad] IS NULL) OR ([AdSoyad] Diğer seçenekleri de uygulayalım. ocb.conflictoption = ConflictOption.CompareRowVersion INSERT INTO [Musteri] ([AdSoyad]) VALUES (@p1) UPDATE [Musteri] SET [AdSoyad] WHERE (([MusteriId] AND ((@p3 = 1 AND [SatirTarih] IS NULL) OR ([SatirTarih] DELETE FROM [Musteri] WHERE (([MusteriId] AND ((@p2 = 1 AND [SatirTarih] IS NULL) OR ([SatirTarih] ocb.conflictoption = ConflictOption.OverwriteChanges Bölüm 26

154 622 C# Programlama Dili INSERT INTO [Musteri] ([AdSoyad]) VALUES UPDATE [Musteri] SET [AdSoyad] WHERE (([MusteriId] DELETE FROM [Musteri] WHERE (([MusteriId] DbCommandBuilder sınıfının diğer önemli üyesi SetAllValues özelliğidir. Bilindiği gibi normal şartlar altında DataAdapter nesnesinin Update() yordamını çalıştırdığımızda değişiklik yapılmış satırlar tespit edilir ve o satırların tüm kolonları güncellenmek üzere veritabanına gönderilir. Tek bir kolonda bile değişiklik yapıldığı zaman tüm kolonların değerleriyle birlikte veritabanına gönderiliyor olması hem zaman hem de performans açısından olumsuz etkileri olabilmektedir. Sadece değişiklik yapılmış alanları update cümlesine almak daha mantıklı olacaktır. bool türünde değer alan SetAllValues özelliği bu işlemi yapmaktadır. Varsayılan olarak true değerine sahip olan bu özelliği false olarak atadığımızda güncelleme cümlesine yalnızca değişikliğe uğramış alanlar eklenir. Aşağıdaki örnekte Musteri tablosunun ilk kaydının Telefon bilgisi ikinci kaydının da AdSoyad bilgisi güncellenmiştir. SqlDataAdapter oda = new SqlDataAdapter("SELECT MusteriId, AdSoyad,Telefon, SatirTarih FROM Musteri", ocnn); SqlCommandBuilder ocb = new SqlCommandBuilder(oDa); ocb.conflictoption = ConflictOption.OverwriteChanges; ocb.setallvalues=false; oda.updatecommand = ocb.getupdatecommand(); DataTable odt = new DataTable(); oda.fill(odt); odt.rows[0]["telefon"] = "(0212) "; odt.rows[1]["adsoyad"] = "Ahmet Kaymaz"; oda.update(odt); DataAdapter nesnesinin Update() yordamını çalıştırdığımızda SQL Sunucu tarafına nasıl bir sorgu ifadesinin gittiğini SQL Sunucu Profiler ürününden bakalım. exec sp_executesql N'UPDATE [Musteri] SET [Telefon] WHERE (([MusteriId] char(16),@p2 int',@p1='(0212) ',@p2=1 go exec sp_executesql N'UPDATE [Musteri] SET [AdSoyad] WHERE (([MusteriId] varchar(12),@p2 int',@p1='ahmet Kaymaz',@p2=2 go Görüldüğü gibi ilk satır için sadece Telefon alanı ikinci satır için de sadece AdSoyad alanı güncelleme cümlesine eklenmiştir. Eğer SetAllValues özelliğini true olarak düzenlemiş olsaydık aşağıdaki ifadeler oluşurdu. Papatya Yayıncılık Eğitim

155 DataAdapter Kullanımı 623 exec sp_executesql N'UPDATE [Musteri] SET [AdSoyad] [Telefon] WHERE (([MusteriId] varchar(11),@p2 char(16),@p3 int',@p1='ali Korkmaz',@p2='(0212) ',@p3=1 go exec sp_executesql N'UPDATE [Musteri] SET [AdSoyad] [Telefon] WHERE (([MusteriId] varchar(12),@p2 char(8000),@p3 int',@p1='ahmet Kaymaz',@p2=NULL,@p3=2 go DataAdapter UpdateBatchSize Özelliği İstemci tarafında veri tabloları üzerinde yapılan değişiklikleri veri kaynağına yansıtmak üzere DataAdapter sınıfının Update() yordamını çalıştırdığımızda veritabanına birim zamanda tek satır gönderilir. Kaç satırda değişiklik varsa veritabanına network üzerinden o kadar gidip gelinir. Bu durumda her satır için güncelleme yapılmadan önce RowUpdating olayı, güncelleme yapıldıktan sonra RowUpdated olayı çağrılır. Aşağıdaki tabloda Musteri tablosuna 5 kayıt ekleniyor. Bu durumda sözkonusu iki olayın davranışı gösterilmiştir. static int GuncellenecekKayit, GuncellenenKayit; static void Main(string[] args) { SqlDataAdapter oda = new SqlDataAdapter("SELECT MusteriId, AdSoyad FROM Musteri", ocnn); SqlCommandBuilder ocb = new SqlCommandBuilder(oDa); oda.insertcommand = ocb.getinsertcommand(); oda.rowupdating += new SqlRowUpdatingEventHandler(Guncelliyor); oda.rowupdated += new SqlRowUpdatedEventHandler(Guncellendi); DataSet ods = new DataSet(); oda.fill(ods, "Musteri"); DataRow odr; for (byte i = 1; i <= 5; i++) { odr = ods.tables[0].newrow(); odr["adsoyad"] = "AdSoyad_" + i; ods.tables[0].rows.add(odr); oda.update(ods.tables[0]); Console.WriteLine("Güncellenecek Kayıt Sayısı: {0", GuncellenecekKayit); Console.WriteLine("Güncellenen Kayıt Sayısı: {0", GuncellenenKayit); static void Guncelliyor(object sender, SqlRowUpdatingEventArgs e) { GuncellenecekKayit++; Console.WriteLine("{0 -> Güncelleniyor", e.row["adsoyad"]); static void Guncellendi(object sender, SqlRowUpdatedEventArgs e) { GuncellenenKayit++; Console.WriteLine("{0 -> Güncellendi", e.row["adsoyad"]); Bölüm 26

156 624 C# Programlama Dili AdSoyad_1 -> Güncelleniyor AdSoyad_1 -> Güncellendi AdSoyad_2 -> Güncelleniyor AdSoyad_2 -> Güncellendi AdSoyad_3 -> Güncelleniyor AdSoyad_3 -> Güncellendi AdSoyad_4 -> Güncelleniyor AdSoyad_4 -> Güncellendi AdSoyad_5 -> Güncelleniyor AdSoyad_5 -> Güncellendi Güncellenecek Kayıt Sayısı: 5 Güncellenen Kayıt(Batch) Sayısı: 5 Sonuç satırlarından görüldüğü gibi her satır için istemci ile veri kaynağı arasında gidip gelinmektedir. Beş kayıt için ciddi bir sorun olmasa da kayıt sayısı artıkça performans sorunu yaşayacağımız aşikâr bir durumdur. ADO.NET 2.0 ile birlikte bu durumu iyileştirmek amacıyla DataAdapter nesnesi için UpdateBatchSize özelliği geliştirildi. Integer türünde değer alan bu özellik veri kaynağına gönderilecek deyimler için oluşturulacak veri yığınların (batch) sayısını belirtir. Örneğin bu özelliğe 5 değeri verildiği zaman 5 tane sorgu ifadesi, tek toplu işlem olarak veritabanı sunucusuna gönderilir. Bu özelliğin varsayılan değeri 1 olup her güncelleme satırı için gidip gelinir. 0 değeri verildiği zaman veritabanı sunucusunun izin verdiği limit kadar tüm güncelleme işlemlerini bir kerede gönderir. 0 ve 1 haricinde bir değer verildiği zaman o kadar güncelleme deyimi toplu olarak veri kaynağına gönderilir. Böylece her satır için bilgisayar ağı meşgul edilmek yerine güncelleme deyimleri parti parti veritabanına gönderilir. Bu durumda yine her satır için RowUpdating olayı çağrılır fakat sadece veritabanı sisteminden geri dönüldüğü zaman RowUpdated olayı çağrılır. Örneğimizde kullandığımız DataAdapter nesnesi için UpdateBatchSize özelliğini 3 olarak yerleştirip olayların durumuna bakalım. Ayrıca döngü sayısını 9 a çıkaralım. Bu durumda her 3 satırda bir RowUpdated olayı çağrılacaktır. oda.updatebatchsize = 3; DataRow odr; for (byte i = 1; i <= 9; i++) { odr = ods.tables[0].newrow(); odr["adsoyad"] = "AdSoyad_"+ i; ods.tables[0].rows.add(odr); AdSoyad_1 -> Güncelleniyor AdSoyad_2 -> Güncelleniyor AdSoyad_3 -> Güncelleniyor AdSoyad_3 -> Güncellendi AdSoyad_4 -> Güncelleniyor AdSoyad_5 -> Güncelleniyor AdSoyad_6 -> Güncelleniyor AdSoyad_6 -> Güncellendi Papatya Yayıncılık Eğitim

157 DataAdapter Kullanımı 625 AdSoyad_7 -> Güncelleniyor AdSoyad_8 -> Güncelleniyor AdSoyad_9 -> Güncelleniyor AdSoyad_9 -> Güncellendi Güncellenecek Kayıt Sayısı: 9 Güncellenen Kayıt(Batch) Sayısı: 3 Konuyla ilgili diğer önemli madde şudur: DataAdapter nesnesi için batch update özelliğini etkinleştirdiğimizde adaptörün UpdateCommand, InsertCommand ve DeleteCommand komut nesneleri için UpdatedRowSource değerinin None veya OutputParameters olarak düzenlenmesi önerilir Özet Bağlantılı katman ile bağlantısız katman arasındaki geçişi sağlayan DataAdapter sınıfının en çok kullanılan üyeleri şunlardır: TableMappings özelliği, gerçek veri kaynağıyla DataSet arasında tablo ve kolon bazında eşleştirmerme yapmamızı sağlar. AcceptChangesDuringFill özelliği DataSet nesnesi adaptör tarafından yüklendikten sonra AcceptChanges() yordamının çağrılıp çağrılmayacağını belirtir. Bu özellik false olarak düzenlenirse yüklemenin ardından AcceptChanges() yordamı çağrılmaz. AcceptChangesDuringUpdate özelliği adaptör DataSet içerisindeki değişiklikleri veri kaynağı tarafında güncelledikten sonra AcceptChanges() yordamının çağrılıp çağrılmayacağını belirtir. Bu özellik false olarak düzenlenirse güncellemenin ardından AcceptChanges() yordamı çağrılmaz yani güncelleme sonrası dataset içindeki değişiklikler henüz onaylanmamış olur. Fill() yordamı DataAdapter nesnesinin veri kaynağından çektiği verileri bir DataSet veya DataTable nesnesine doldurmasını sağlar. FillSchema() yordamı DataAdapter ün veritabanından okuduğu tablonun şemasını (kolon tipleri, null durumları, key bilgileri) DataSet veya DataTable nesnesine yükler. Update() yordamı DataSet üzerinde yapılan değişiklikleri gerçek veri kaynağına yansıtır Sorular 26.1) DataAdapter nesnesinin TableMappings özelliğini bir örnekle açıklayınız. 26.2) DataSet nesnesi DataAdapter tarafından nasıl yüklenir? 26.3) DataSet üzerinde yapılan değişiklikler veritabanına nasıl kaydedilir? 26.4) AcceptChangesDuringFill ve AcceptChangesDuringUpdate özelliklerin kullanım amacı nedir? 26.5) DataAdapter sınıfının Update() yordamını seçme, silme, güncelleme işlemleri için örneklendiriniz? Bölüm 26

158 626 C# Programlama Dili Papatya Yayıncılık Eğitim

159 27. İleri ADO.NET Konuları Bu bölümde ileri ADO.NET kapsamına giren bağlantı havuzu 2.0 ile birlikte gelmiş olan MARS özelliği, asenkron veri erişimi ve toplu kaydetme gibi konular ele alınmıştır ADO.NET te Bağlantı Havuzu Bilindiği gibi herhangi bir veritabanı işlemi gerçekleştirileceği zaman öncelikle o veritabanıyla iletişimi sağlayan bir bağlantı oluşturulur. Bu bağlantı işlemi için istemci tarafında bir DbConnection nesnesi oluşturulur; veritabanı tarafında da bir oturum açılır. Aynı uygulamada her gelen istek için VTYS üzerinde bir bağlantı nesnesi o- luşturulur. Bu da VTYS tarafında hem zaman almakta hem de yük oluşturmaktadır. Bu sıkıntı veritabanı gibi bağlantı tabanlı hizmet veren tüm servisler için geçerlidir. Servisler üzerindeki bu yükü hafifletmek amacıyla her bağlantı isteği için yeni bir bağlantı nesnesi oluşturmak yerine gelen istekleri daha önce oluşturulmuş nesne üzerinden yürütme yöntemi geliştirildi. Bağlantı havuzu (connection pooling) olarak tanımlanan bu yöntemde ilk istekle birlikte oluşturulmuş olan bağlantı nesnesi saklanır. Daha sonra kullanıcılardan gelen taleplerde bellekteki bu nesne kullanılır. Böylece her talepte veritabanı sunucusu tarafında yeni bir bağlantı nesnesi açılmayacağı için zaman kazanılmış olacak ve oturum talepleri artsa bile aynı bağlantı nesnesi üzerinden iletişim kurulacağı için ek bir yük getirilmemiş olacaktır. SQL Sunucu, Oracle ve OleDb sağlayıcıları bağlantı havuzu mekanizmasını destekler. ODBC Driver Manager 3.0 ve üstü (MDAC 1.5), yapılacak küçük bir ayarla bu yöntemi destekler. Havuz mekanizmasının kullanılıp kullanılmayacağı, bağlantı bilgilerinin bulunduğu bağlantı cümlesinde (connection string) belirtilir. Bağlantı cümlesi içerisinde havuzlama tekniğini kullanmak için SQL Sunucu ve Oracle tarafında pooling ifadesi true veya false değerleriyle, OleDb tarafında OLE DB Services ifadesi, 0, -2 veya -4 seçenekleriyle kullanılır. ADO.NET uygulamalarında bağlantı cümlesi içerisinde tersi belirtilmediği sürece varsayılan olarak bağlantı havuzlama özelliği aktif olur.

160 628 C# Programlama Dili Havuzdaki bağlantının farklı talepler tarafından paylaşılabilmesi için bu taleplerin bağlantı cümlelerinin yani bağlantı ayarlamalarının aynı olması gerekir. using (SqlConnection connection = new SqlConnection( "Server=.;Integrated Security=SSPI;Initial Catalog=Kitap")) { connection.open(); // 1 nolu Pool oluşturuldu. using (SqlConnection connection = new SqlConnection( "Server=.;Integrated Security=SSPI;Initial Catalog=CRM")) { connection.open(); // Connection string bilgisi farklı olduğu için 2 nolu Pool oluşturuldu. using (SqlConnection connection = new SqlConnection( "Server=.;Integrated Security=SSPI;Initial Catalog=Kitap")) { connection.open(); // 1 nolu Pool'daki bağlantı nesnesini kullanır. SQL Sunucusunu baz alarak bağlantı havuzlama yönteminin sağladığı avantajları örneklendirelim. Öncelikle havuzlama özelliğini kullanmadan birçok bağlantı nesnesi oluşturup bunların maliyetini çıkaralım. Aşağıdaki kodları çalıştırmadan önce SQL Sunucudaki işlemleri görmek için SQL Server Profiler aracını açalım. string CnnStr SqlConnection ocnn; // İşleme başlamadan önceki tarih. DateTime BaslangicTarih = DateTime.Now; for (int x = 0; x < 5000; x++) { ocnn = new SqlConnection(CnnStr); ocnn.open(); ocnn.close(); // İşlem bittikten sonra tarih. DateTime BitisTarih = DateTime.Now; TimeSpan TarihFark = BitisTarih - BaslangicTarih; Console.WriteLine("Bağlantı işlem süresi: {0 ms", TarihFark.TotalMilliseconds); Bağlantı işlem süresi: 14672,0628 ms Bu satırlarla SQL Sunucusuna bağlantı havuzunu kullanmadan 5000 kez bağlanmaya çalıştık. Bu da bize yaklaşık 15 saniyeye mal oldu. SQL Sunucu Profiler tarafına baktığımızda gerçekten de bu kadar oturumun açılıp kapatıldığını görürüz. Papatya Yayıncılık Eğitim

161 İleri ADO.NET Konuları 629 Bu liste 5000 kez tekrar edecektir. Aynı bağlantı cümlesinde pooling özelliğini true olarak düzenleyelim. string CnnStr Bağlantı işlem süresi: 109,3764 ms Bu durumda sürenin önemli şekilde azaldığını ve SQL Sunucu Profiler tarafında da sadece bir kere bağlantının açılıp kapatıldığını görürüz. Aynı şekilde Administrative Tools» Performance aracı kullanılarak SQL Sunucu üzerindeki bağlantı yoğunluğu izlenebilir. Daha önceki sayfalarda işlediğimiz DataAdapter nesnesi Fill(), FillSchema() ve Update() yordamlarını çalıştırdığı zaman otomatik olarak veri kaynağıyla olan bağlantıyı kendisi açıp kapatır. Bu tür bağlantısız nesne işlemlerinde bağlantı havuzu özelliği önemli bir performans sağlar. Bu mekanizmayı yapılandırmak için bağlantı cümlesinde aşağıdaki seçenekler kullanılır: Connection Lifetime: Bağlantının ne kadar saniye sonra kapatılacağını belirtir. Varsayılan değeri 0 olup bağlantının hiçbir zaman yok edilmemesini sağlar. Max Pool Size: Bir bağlantı havuzunda saklanacak en fazla bağlantı sayısını belirtir. Varsayılan değeri 100 dür. Min Pool Size: Bir bağlantı havuzunda saklanacak en az bağlantısı sayısını belirtir. Varsayılan değeri 0 dır. Bölüm 27

162 630 C# Programlama Dili Buradaki Min Pool veya Max Pool değerler, aynı havuzda ne kadar sayıda bağlantının açık kalabileceğini belirtir. Örneğimizde havuzlama özelliği aktifken ocnn.close() satırını kapatırsak SQL Sunucu tarafında en az 100 tane bağlantı açılır daha sonraki istekler havuzdaki maksimum bağlantı sayısını aştığı için aşağıdaki hata mesajını verir. ODBC, OLEDB ve SqlClient gibi API ler havuzdaki bir bağlantıyı yeniden kullanmak için SQL Sunucunun sp_reset_connection isimli dahili yordamı çağırırlar. ADO.NET 2.0 ile birlikte SQL Sunucu bağlantı havuzunu yönetmek amacıyla ClearAllPools() ve ClearPool() yordamları geliştirildi. System.Data.SqlClient altında statik nitelikte bulunan bu iki yordam, istenildiği durumda havuzu boşaltır. ClearAllPools() yordamı tüm havuzları temizler. ClearPool() yordamı ise SqlConnection türünde parametre alarak sadece belirli bir nesnenin bulunduğu havuzu temizler ADO.NET te MARS Özelliği MARS (Multiple Active Result Sets), ADO.NET 2.0 ile birlikte geliştirilmiş olup tek bağlantı nesnesi üzerinden aynı anda birden fazla sorgu veya stored procedure çalıştırmaya izin veren bir özelliktir. Bu özellik aracılığıyla aynı bağlantı nesnesini kullanarak birden fazla kayıt dizisi (recordset) üzerinde ileri-yönlü (forward-only) ve salt-okunur (read-only) işlemler yapılabilir. Bu özellik kullanılmadan bu tür işlemleri yapabilmek için aktif bağlantıyı kapatıp yeniden açmak veya birbirinden bağımsız bağlantı nesnelerini kullanmak gerekirdi. Aşağıdaki örnekte açık olan bağlantı kapatılmaksızın ikinci bir işlem için kullanmak istenmiştir: SqlConnection ocnn = new SqlConnection("Server=.;Database=Kitap;Trusted_Connection=yes;"); string SqlQry1 = "SELECT * FROM Urun"; string SqlQry2 = "SELECT * FROM Marka"; SqlCommand ocmd1 = new SqlCommand(SqlQry1, ocnn); SqlCommand ocmd2 = new SqlCommand(SqlQry2, ocnn); ocnn.open(); SqlDataReader odr1 = ocmd1.executereader(); // odr1 işlemleri. SqlDataReader odr2 = ocmd2.executereader(); // odr2 işlemleri. ocnn.close(); Papatya Yayıncılık Eğitim

163 İleri ADO.NET Konuları 631 Bu şekilde kodlarımızı çalıştırdığımızda ocmd2.executereader() satırında aşağıdaki hata mesajını alırız. Açık olan bağlantıyı yeniden kullanmak için kapatıp yeniden açmamız gerekir. SqlCommand ocmd1 = new SqlCommand(SqlQry1, ocnn); SqlCommand ocmd2 = new SqlCommand(SqlQry2, ocnn); ocnn.open(); SqlDataReader odr1 = ocmd1.executereader(); // odr1 işlemleri. // Birinci sorgu için bağlantı kapatılır. ocnn.close(); // Bağlantı başka bir sorguda kullanılmak üzere yeniden açılır. ocnn.open(); SqlDataReader odr2 = ocmd2.executereader(); // odr2 işlemleri. ocnn.close(); Bu kod esnasında SQL Sunucu Profiler aracına baktığımızda gerçekten de bağlantının kapatılıp yeniden açıldığını görebiliriz. Açık olan bağlantıyı yeniden kullanabilmek için MARS özelliğini aktifleştirmek gerekir. Bunun için bağlantı cümlesi içerisinde MultipleActiveResultSets parametresi true olarak düzenlenir. Örnekte kullandığımız bağlantı cümlesini aşağıdaki gibi düzenleyelim. Server=.;Database=Kitap;MultipleActiveResultSets=true; Uid=sa;Pwd=123 Bu sefer örneğimizi Marka ve Urun tablosu hiyerarşik yapıda listelenecek şekilde değiştirelim. Yani her markanın altında o markaya ait ürünler görünecek. Burada yapacağımız işlem performans açısından iyi bir yöntem olmasa da markaları içeren Bölüm 27

164 632 C# Programlama Dili DataReader nesnesini okuduğumuz döngüde o anki satırın MarkaId bilgisini alıp veritabanında o markaya denk ürünleri sorgulayacağız. SqlConnection ocnn = new SqlConnection(CnnStr); SqlCommand cmdmarka = new SqlCommand("SELECT * FROM Marka",oCnn); SqlCommand cmdurun = new SqlCommand("SELECT * FROM Urun WHERE MarkaId=@MarkaId", ocnn); cmdurun.parameters.add("@markaid", SqlDbType.Int); // SQL Sunucu ile bağlantıyı kuralım. ocnn.open(); int MarkaId; using (SqlDataReader drmarka = cmdmarka.executereader()) { while (drmarka.read()) { Console.WriteLine(drMarka["MarkaAd"]); // Okunan markanın altındaki ürünleri çekelim. MarkaId = (int)drmarka["markaid"]; cmdurun.parameters["@markaid"].value = MarkaId; using (SqlDataReader drurun = cmdurun.executereader()) { while (drurun.read()) { Console.WriteLine("\t{0", drurun["urunad"]); // SQL Sunucu'daki bağlantımızı keselim. ocnn.close(); Bu durumda iki kayıt dizisini işlemek için iki farklı bağlantı nesnesi oluşturup sistem üzerinde ek yük oluşturmak yerine açık olan bağlantı üzerinden ikinci sorgulama da yürütülmüş oldu. NOT MARS özelliğini destekleyen ilk SQL Sunucu sürümü SQL Sunucu 2005 tir. Bu yüzden MARS özelliğini aktifleştireceğimiz zaman karşı veritabanının sürümünü de kontrol edebiliriz. if (ocnn.serverversion.startswith("09")) { // Aynı bağlantıyı kullanacak işlemler. Papatya Yayıncılık Eğitim

165 İleri ADO.NET Konuları 633 MARS işleminde yönetimi kullanılacağı zaman aynı bağlantı içerisinde birden fazla transaction oluşturulamayacağı için aynı aktif bağlantı nesnesini kullanacak tüm nesnelerinin aynı etkileşim nesnesiyle ilişkili olması gerekir. string SqlQry1 = "SELECT * FROM Marka"; string SqlQry2 = "UPDATE Marka SET MarkaAd = MarkaAd WHERE MarkaId SqlCommand ocmd1 = new SqlCommand(SqlQry1, ocnn); SqlCommand ocmd2 = new SqlCommand(SqlQry2, ocnn); ocmd2.parameters.add("@markaid", SqlDbType.Int, 4); ocnn.open(); // Transaction nesnesi oluşturalım. SqlTransaction otrs = ocnn.begintransaction(); // Her iki sorgu, transaction yönetiminde olacak. ocmd1.transaction = otrs; ocmd2.transaction = otrs; // İlk sorgunun döndüreceği sonuçları okuyalım. SqlDataReader odr = ocmd1.executereader(); while (odr.read()) { // Satırları oku. ocmd2.parameters["@markaid"].value = odr["markaid"]; ocmd2.executenonquery(); // Her güncellemeden sonra commit etsin. otrs.commit(); ocmd1.dispose(); ocmd2.dispose(); ocnn.close(); Bu hatayı aşmak için tüm komut nesnelerini tek işlem-bilgi (transaction) içerisinde yürütmeliyiz. Bu da MARS ın performans sorunu olarak karşımıza çıkmaktadır. // İlk sorgunun döndüreceği sonuçları okuyalım. SqlDataReader odr = ocmd1.executereader(); while (odr.read()) { // Satırları oku ocmd2.parameters["@markaid"].value = odr["markaid"]; ocmd2.executenonquery(); // Tüm update işlemlerinden sonra COMMIT çalışsın. otrs.commit(); Bölüm 27

166 634 C# Programlama Dili DataSet ile DataReader Arasındaki Fark Veritabanı uygulamaları geliştirilirken veri yükleme için DataReader veya DataSet nesnelerinden hangisinin tercih edileceği hep sorulan bir soru olmuştur. Bilindiği gibi ADO.NET in sunduğu en önemli özelliklerden birisi veritabanından alınan kayıt kümelerini bağlantılı ve bağlantısız katmanda kullanabiliyor olmamızdır. Bu iki katmanın farkı aşağıdaki gibidir: Bağlantılı katmanda veritabanından çekilen kayıt listesi yalnızca ileri yönlü okunabilir ve ilerleme esnasında veritabanı bağlantısı açık kalır. Bağlantısız katmanda ise kayıt listesi tümüyle istemcinin belleğine alınır; dolayısıyla kayıt kümesinin satırları arasında ileri ve geri yönlü okuma yapılabilir ve bu işlem esnasında veritabanı bağlantısına ihtiyaç duyulmaz. Bağlantılı katmanda DataReader nesnesi, bağlantısız katmanda ise DataSet nesnesi kullanılır. DataReader, sunucudan sorgulama sonucu dönen veriler için oluşturulmuş iletimkanalı yapısıdır. Sunucuya gönderilmiş sorgu, yürütüldükten sonra ilk satır iletimkanalı üzerinden DataReader nesnesine aktarılır; ardından sonraki satıra geçilir. DataReader, birim zamanda veritabanından yalnızca bir satır okur ve bir satır ilerleyebilir; ayrıca, veritabanından satırlar okunurken programcı tarafında bu satırların kolonları güncellenemez, yalnız okunabilir. DataSet yapısından farklı olarak DataReader System.Data sınıfının altında değil veri sağlayıcılarına bağlı olduğu i- çin veri sağlayıcılara ait isim-uzayları altında bulunur. OleDbDataReader, SqlDataReader gibi tüm DataReader sınıfları System.Data.IDataReader arabiriminden türemiştir. DataSet nesnesi XML ile uyumlu bir alt yapıya sahiptir. Kendisi doğrudan bir XML dosyasını yükleyebildiği gibi sahip olduğu şema ve veriyi XML formatında dışarıya aktarabilir. Bu da onun farklı proses, katman ve ağ arasında hiçbir güvenlik riski taşımadan aktarılmasını, karşı sistem tarafından kolayca işlenmesini sağlamaktadır. Bunlara ek olarak DataSet, DataReader nesnesinden farklı olarak serileştirme destekler. Böylece uzak bir istemciye ancak DataSet nesnesi gönderilebilir. Bu yetenekler onun Web servislerinin baş aktörü olmasını sağlamıştır. DataSet içerisindeki veriler güncellenebilir; yapılan bu değişiklikler ilgili adaptör nesnesi kullanılarak veri kaynağına yansıtılır. Benzer şekilde veriler arasında ileri geri hareket edilebilir. Bu durum da DataSet içerisindeki veriler arasında kolaylıkla arama ve sıralama işlemlerinin yapılabilmesini sağlar. Bu özellikler ışığında kontrollerin sadece okunabilir olduğu uygulamalarda (Web uygulamaları gibi) DataReader nesnesi, verilerin kullanıcı tarafından değiştirileceği uygulamalarda DataSet nesnesin tercih edilmeli diyebiliriz. DataReader, DataSet nesnesinin önemli özelliği olarak görülen primary key, constraint, view ve relation kavramlarını desteklemez. DataReader, yalnızca bir ve- Papatya Yayıncılık Eğitim

167 İleri ADO.NET Konuları 635 ri kaynağına bağlanabilir. DataSet ise Fill() yordamı aracılığıyla birden fazla veri kaynağıyla doldurulabilir. DataReader nesnesi bellekte yalnızca anlık olarak bir veri satırı tutar; önceki değerleri bellekte tutmaz. Bu yönüyle klasik değer değişkenleri gibi çalışır. Bu yüzden DataReader sınıfının DataSet ten farklı olarak yapıcı yordamı bulunmaz. Kendisinden bir nesne oluşturmak yerine yalnızca değişken tanımlaması yapılır. Sonuç olarak DataSet ve DataReader nesnelerinin kullanım alanları farklı olduğu için aralarında üstünlük yoktur. DataSet nesnesini istemcinin belleğinde yaşayan geçici bir veritabanı gibi düşünebiliriz. Sadece ileri yönlü ve okuma amaçlı veri erişim işlemi yapılacaksa veya çok uzun bir veri alınacaksa DataSet yerine DataReader nesnesinin tercih edilmesi uygulamanın performansını artıracaktır Asenkron Veri Erişimi Bilindiği gibi bir programda uzun süren veritabanı işlemleri esnasında güncel thread bekletilir ve veritabanından yanıt gelmeden program diğer işlemlere geçmez. Asenkron iş görme tarzı, senkron yaklaşımının neden olduğu bu bekletme sıkıntısını aşmak için geliştirilmiştir. Asenkron modelinde, işlemler paralel bir şekilde yürütülür. Asenkron yürütmeleri için uygulamalarımızda multithread işlemler yapıyoruz. Fakat ADO.NET 2.0 ile birlikte ek multithread tabanlı kodlar yazmadan SQL Sunucu üzerinde birden fazla komut nesnesini paralel çalıştırabileceğimiz DataReader nesnesiyle ilişkili yeni üyeler geliştirildi. 1.Bağlantıyı aç 2.SqlCommand yürüt 5.Sonuçları al 6.Diğer kodlar Senkron Veri İletişimi 3.Komutu çalıştır 4.Sonuçları gönder Asenkron Veri İletişimi 1.Bağlantıyı aç 2.SqlCommand yürüt 3_1.Diğer kodlar 3_2.Komutu çalıştır 4.Sonuçları gönder 5.Sonuçları al 6.Diğer kodlar Bölüm 27

168 636 C# Programlama Dili SqlCommand nesnesinin ExecuteReader(), ExecuteNonQuery() ve ExecuteXmlReader() yordamları senkronize çalışır. ADO.NET 2.0 da bu yordamların yaptığı işlemleri asenkron yürütecek yordamlar geliştirildi. Senkron Yordam Begin Asenkron Yordam End ExecuteNonQuery BeginExecuteNonQuery EndExecuteNonQuery ExecuteReader BeginExecuteReader EndExecuteReader ExecuteXmlReader BeginExecuteXmlReader EndExecuteXmlReader ExecuteScalar() yordamı, ExecuteReader() yordamının ilk satır-ilk kolonu o- kuyan uyarlaması gibi olduğu için asenkron uyarlamasına ihtiyaç duyulmamaktadır. Begin ile başlayan yordamlar, asenkron işi başlatan yordamlar ve End ile biten yordamlar işi bitiren yordamlardır. Begin yordamları, System.IAsyncResult türünde değer döndürür; End yordamları ise temsil ettikleri senkron yordamların döndürdüğü türde değer döndürürler. IAsyncResult arabirimi, asenkron olarak çağrılmış bir yordamın durumunu ve döndürdüğü sonuçları yakalar. Bu arabirimin dört temel üyesi bulunur: AsyncState: Asenkron yapılan işlemin içeriğinin yazıldığı kullanıcı tarafından tanımlanmış nesneyi temsil eder (Callback modeli için). AsyncWaitHandle: System.Threading.WaitHandle türünde nesne oluşturur; asenkron işleminin bitmesini beklemek için kullanılır (Wait-Until-Done modeli için). CompletedSynchronously: Asenkron işleminin senkron olarak bitip bitmediğini döndürür. Pek kullanılan bir üye değildir. IsCompleted: Asenkron olarak çağrılmış olan yordamın işlemini bitirip bitirmediğini kontrol eder (Polling modeli için). End yordamları, Begin yordamlarının döndürdüğü IAsyncResult nesnesini parametre olarak alır. ADO.NET te eş zamanlı olmayan veri erişimi yapabilmek için bağlantı cümlesi içerisine async=true ifadesinin eklenmesi gerekir. ADO.NET te asenkron işlemde performans ve işlevsellik kazanmak için üç çeşit yaklaşım benimsenmiştir. Pooling Modeli: Tüm prosesler başlatıldıktan sonra her birinin bitip bitmediği kontrol edilir. Wait Modeli: Asenkron komutlarından biri veya daha fazlası veya hepsi bitinceye kadar uygulamanın bekletildiği modeldir. CallBack Modeli: Asenkron olarak çalışan yordamların çalışması bittikten sonra otomatik olarak geri-çağırma yordamlarını çalıştırılır. Papatya Yayıncılık Eğitim

169 İleri ADO.NET Konuları Havuz (Pooling) Modeli Bu modelde, asenkron işlemler başlatıldıktan sonra tamamlanıp tamamlanmadığı uygulama içerisinde sürekli denetlenir; bu amaçla Begin yordamının döndürdüğü IAsyncResult arabiriminin IsCompleted özelliği true değerini gönderinceye kadar tekrar tekrar sınanır. Bu model kısa süren asenkron işlemlerde iyidir; ancak karışık ve çok sayıda işlem olduğu durumlarda diğer modellerin kullanılması elverişlidir. Aşağıdaki örnekte SQL Sunucu üzerinde küçük bir güncelleme işlemi yapılmıştır. Sorgu, sunucuya asenkron olarak gönderilmiştir. Böylece sunucu tarafında güncelleme sürerken uygulama diğer kod satırlarını da yürütüyor olacaktır. string CnnStr SqlConnection ocnn = new SqlConnection(CnnStr); string Qry = "UPDATE Musteri SET KayitTarih=Getdate()"; SqlCommand ocmd = new SqlCommand(Qry, ocnn); ocnn.open(); IAsyncResult oar = ocmd.beginexecutenonquery(); // Tabloda onlarca kayıt olduğu için güncelleme biraz sürecek. // Ancak SQL Sunucuda güncelleme yapılırken bu satırlar da yürütülür. while (!oar.iscompleted) { Console.WriteLine("İşlem devam ediyor..."); Console.WriteLine("İşlem tamamlandı."); Console.WriteLine("Güncellenen kayıt sayısı: {0", ocmd.endexecutenonquery(oar)); ocmd.dispose(); ocnn.close(); İşlem devam ediyor... İşlem devam ediyor... İşlem devam ediyor... İşlem devam ediyor... İşlem devam ediyor... İşlem devam ediyor... İşlem tamamlandı. Güncellenen kayıt sayısı: 51 Aynı örneği bir de ExecuteReader() yordamı üzerinde gösterelim. SQL Sunucudan Musteri tablosunu çekelim; sunucu, verileri hazırlayıp bize göndermeye çalışırken uygulamamız da diğer satırları yürütecektir. string Qry = "SELECT * FROM Musteri"; SqlCommand ocmd = new SqlCommand(Qry, ocnn); ocnn.open(); IAsyncResult oar = ocmd.beginexecutereader(); // SQL Sunucu'dan verilerin gelip gelmediğini kontrol edelim. while (!oar.iscompleted) { Console.WriteLine("Data hazırlanıyor..."); Bölüm 27

170 638 C# Programlama Dili Console.WriteLine("Data, istemciye geldi."); Console.WriteLine(""); SqlDataReader odr = ocmd.endexecutereader(oar); while (odr.read()) { Console.WriteLine(oDr["AdSoyad"]); odr.close(); ocmd.dispose(); ocnn.close(); Data hazırlanıyor... Data hazırlanıyor... Data hazırlanıyor... Data hazırlanıyor... Data hazırlanıyor... Data hazırlanıyor... Data, istemciye geldi. Ali Korkmaz Mert Cesur Zeynep Akkuş Zeynep Akkuş Bekletme (Wait) Modeli En karmaşık asenkron yöntem olan bu modelde her asenkron yordam için zaman aşımı olan bir bekleme yönetimi tanımlanır. Prosesler ya sonuç döndürünceye kadar ya da zaman-aşımına düşünceye kadar uygulamanın bekletilmesini sağlar. Böylesi bekletmenin sebebi, asenkron yordamının döndüreceği sonucun programın başka bir yerinde giriş olarak kullanılıyor olmasıdır. Asp.NET uygulamaları için ideal o- lan bu modelde üç farklı yöntem kullanılır. Bu yöntemler WaitHandle sınıfının yordamlarıdır: WaitOne(): Tek bir asenkron SqlCommand nesnesi için uygulamayı bekletir. WaitAll(): Bütün asenkron çağrıların bitmesini bekler. WaitAny(): Asenkron çağrıların bitip bitmediği sırayla kontrol edilerek herhangi biri bitinceye kadar uygulama bekletilir. Bekletme modelinde BeginExecuteNonQuery, BeginExecuteReader veya BeginExecuteXmlReader yordamları tarafından döndürülen IAsyncResult nesnesinin AsyncWaitHandle özelliği kullanılır. WaitAny() ve WaitAll() yordamları WaitHandle türündeki nesneyi parametre olarak alır. System.Threading. WaitHandle nesnesi, asenkron olayları yöneten işletim sisteminin desteklediği özel bir sınıftır. WaitOne, havuz modeline alternatif olarak kullanılabilecek bir yöntemdir. Havuz modelinde uygulamada asenkron işlemin bitip bitmediğini tekrar tekrar IsCompleted değeri kontrol edilerek anlaşılabiliyordu. Aynı şekilde uygulamanın bir noktasında asenkron işleminin bitmesini beklemek için IsCompleted özelliğinin değeri true Papatya Yayıncılık Eğitim

171 İleri ADO.NET Konuları 639 oluncaya kadar uygulama while döngüsüne sokulurdu. Uygulamaları bu şekilde döngüyle askıda tutmak kötü bir yöntem olarak değerlendirilir. WaitOne yönteminde uygulamayı bekletmek için WaitOne() yordamı kullanılması yeterli olacaktır. Bu yordamı uygulamayı bekletme yöntemleri olan Thread.Sleep() ve Application.DoEvents yordamlarına benzetebiliriz. Önceki yöntemde kullanılan güncelleme örneğini bu yöntemle yapalım. Projemize WaitHandle sınıfı için System.Threading isim uzayını eklemeyi unutmayalım. string CnnStr SqlConnection ocnn = new SqlConnection(CnnStr); string Qry = "UPDATE Musteri SET KayitTarih=Getdate()"; SqlCommand ocmd = new SqlCommand(Qry, ocnn); ocnn.open(); // Asenkron işlemi başlatalım. IAsyncResult oar = ocmd.beginexecutenonquery(); // WaitHandle nesnesini alalım. WaitHandle owh = oar.asyncwaithandle; Console.WriteLine("Diğer işlemler çalışıyor..."); // Burada asenkron işleminin bitmesine ihtiyacımız var. // O yüzden uygulamayı bekletelim. owh.waitone();// İşlem bitinceye kadar bekler. Console.WriteLine("İşlem tamamlandı."); Console.WriteLine("Güncellenen kayıt sayısı: {0", ocmd.endexecutenonquery(oar)); ocmd.dispose(); ocnn.close(); Diğer işlemler çalışıyor... İşlem tamamlandı. Güncellenen kayıt sayısı: 4 Bu yöntem SQL Sunucuya birden fazla asenkron işlemin gönderildiği uygulamalarda işe yaramayacaktır. Bunun için WaitAll() ve WaitAny() yordamları daha etkili olur. WaitAll() yordamı, birden fazla asenkron SqlCommand yürütmelerin olduğu durumlarda kullanılarak, uygulamayı, bütün komutlar bitinceye kadar bekletir. WaitOne ile aynı mantıkta olup burada birden fazla prosesten bahsedildiği için her proses için bir WaitHandle tanımlanır. Bunun için WaitHandle türünde bir dizi tanımlanır ve bu dizi WaitAll() yordamına parametre olarak gönderilir. Aşağıdaki örnekte SQL Sunucuya üç farklı sorgu gönderiliyor ve uygulama, bu sorguların hepsi sonuçlanıncaya kadar beklemeye alınıyor. Bölüm 27

172 640 C# Programlama Dili string CnnStr SqlConnection ocnn1 = new SqlConnection(CnnStr); SqlConnection ocnn2 = new SqlConnection(CnnStr); SqlConnection ocnn3 = new SqlConnection(CnnStr); string Qry1 = "SELECT * FROM Musteri"; string Qry2 = "WAITFOR DELAY '00:00:05';SELECT Getdate()"; string Qry3 = "SELECT * FROM Marka"; SqlCommand ocmd1 = new SqlCommand(Qry1, ocnn1); SqlCommand ocmd2 = new SqlCommand(Qry2, ocnn2); SqlCommand ocmd3 = new SqlCommand(Qry3, ocnn3); ocnn1.open(); ocnn2.open(); ocnn3.open(); IAsyncResult oar1 = ocmd1.beginexecutereader(); IAsyncResult oar2 = ocmd2.beginexecutereader(); IAsyncResult oar3 = ocmd3.beginexecutereader(); // Her asenkron process için WaitHandle tanımlayalım. WaitHandle[] owh = new WaitHandle[3]; owh[0] = oar1.asyncwaithandle; owh[1] = oar2.asyncwaithandle; owh[2] = oar3.asyncwaithandle; Console.WriteLine("Diğer işlemler çalışıyor..."); // Bütün işlemler bitinceye kadar uygulamayı bekletelim. WaitHandle.WaitAll(oWh); Console.WriteLine("Tüm asenkron işlemler bitti."); // Asenkron işlemlerinin sonuçları alalım. SqlDataReader odr1 = ocmd1.endexecutereader(oar1); SqlDataReader odr2 = ocmd2.endexecutereader(oar2); SqlDataReader odr3 = ocmd3.endexecutereader(oar3); odr1.close(); ocmd1.dispose(); ocnn1.close(); odr2.close(); ocmd2.dispose(); ocnn2.close(); odr3.close(); ocmd3.dispose(); ocnn3.close(); Diğer işlemler çalışıyor... Tüm asenkron işlemler bitti. WaitAll() yordamı, özellikle ASP.NET uygulamalarında tercih edilir. WaitAny() yordamında, önceki yordamlardan farklı olarak sunucuya gönderilen asenkron işlemlerin bitip bitmedikleri sırasıyla kontrol edilir ve herhangi biri bitinceye kadar uygulama bekletilir. WaitAny() yordamının diğer farkı da diğer yordamlar gibi bool türünde değil int türünde değer döndürmesidir. Bu değer, tamamlanmış olan asenkron işleminin dizideki indeksine eşittir. Yani WaitAny() yordamını sorguladığımızda hangi indeks numarası dönerse dizide tanımlı o indekse sahip proses bitmiştir anlamına gelir. WaitAny() yordamı dizideki proses sayısı kadar sorgulanır. Papatya Yayıncılık Eğitim

173 İleri ADO.NET Konuları 641 SqlConnection ocnn1 = new SqlConnection(CnnStr); SqlConnection ocnn2 = new SqlConnection(CnnStr); SqlConnection ocnn3 = new SqlConnection(CnnStr); string Qry1 = "SELECT * FROM Musteri"; string Qry2 = "WAITFOR DELAY '00:00:05';SELECT Getdate()"; string Qry3 = "SELECT * FROM Marka"; SqlCommand ocmd1 = new SqlCommand(Qry1, ocnn1); SqlCommand ocmd2 = new SqlCommand(Qry2, ocnn2); SqlCommand ocmd3 = new SqlCommand(Qry3, ocnn3); ocnn1.open(); ocnn2.open(); ocnn3.open(); IAsyncResult oar1 = ocmd1.beginexecutereader(); IAsyncResult oar2 = ocmd2.beginexecutereader(); IAsyncResult oar3 = ocmd3.beginexecutereader(); // Her asenkron process için WaitHandle tanımlayalım. WaitHandle[] owh = new WaitHandle[3]; owh[0] = oar1.asyncwaithandle; owh[1] = oar2.asyncwaithandle; owh[2] = oar3.asyncwaithandle; // Her process için bitip bitmediğini kontrol edelim. for (int Sayac = 0; Sayac < owh.length; Sayac++) { // Processlerden biri bitmiş mi, bitmemişse uygulamayı beklet. int Index = WaitHandle.WaitAny(oWh); // Biten işlemin indeksine göre yönlendirme yapalım. if (Index == 0) { Console.WriteLine("1. işlem bitti."); SqlDataReader odr1 = ocmd1.endexecutereader(oar1); odr1.close(); ocmd1.dispose(); ocnn1.close(); else if (Index == 1) { Console.WriteLine("2. işlem bitti."); SqlDataReader odr2 = ocmd2.endexecutereader(oar2); odr2.close(); ocmd2.dispose(); ocnn2.close(); else if (Index == 2) { Console.WriteLine("3. işlem bitti."); SqlDataReader odr3 = ocmd3.endexecutereader(oar3); odr3.close(); ocmd3.dispose(); ocnn3.close(); Console.WriteLine("---Tüm işlemler bitti.---"); 1. işlem bitti. 3. işlem bitti. 2. işlem bitti. ---Tüm işlemler bitti.--- Bölüm 27

174 642 C# Programlama Dili WaitOne(), WaitAll() ve WaitAny() yordamları yeniden yüklenmiştir. Diğer kullanım biçimlerinde zaman-aşımı değerini de parametre olarak verebiliriz. Yani asenkron çağrının ne kadar milisaniyede dönmezse zaman aşımına düşeceğini belirleyebiliriz. Aşağıdaki örnekte SQL Sunucuya gönderilen asenkron çağrının SQL Sunucu tarafında 5 saniye beklemesini daha sonra güncelleme yapmasını sağlıyoruz. Uygulama tarafında da bu çağrı için 1 saniyelik bir ömür belirliyoruz. Örnekte WaitOne() yordamı kullanıldı. Bu durumda uygulama WaitOne() yordamında 1 saniye bekler ve sürenin sonunda senkron işleminin bitip bitmediğini sorgular. string CnnStr SqlConnection ocnn = new SqlConnection(CnnStr); // SQL Sunucu'i 5 saniye bekletelim. string Qry = "WAITFOR DELAY '0:0:5';UPDATE Musteri SET KayitTarih=Getdate()"; SqlCommand ocmd = new SqlCommand(Qry, ocnn); ocnn.open(); IAsyncResult oar = ocmd.beginexecutenonquery(); WaitHandle owh = oar.asyncwaithandle; Console.WriteLine("Diğer işlemler çalışıyor..."); // Uygulamayı 1000 ms için bekletelim. if (owh.waitone(1000, false)) { Console.WriteLine("İşlem tamamlandı."); Console.WriteLine("Güncellenen kayıt sayısı: {0", ocmd.endexecutenonquery(oar)); else Console.WriteLine("İşlem gecikti."); ocmd.dispose(); ocnn.close(); Diğer işlemler çalışıyor... İşlem gecikti Geri-Çağırma (CallBack) Modeli Asenkron veri iletişimi için en çok tercih edilen bu yöntemde Begin yordamı için yordamın çalışması bittikten sonra yürütülecek bir delege veya olay yöneticisi (callback method) tanımlanır. Çalışması bittikten sonra otomatik olarak ilişkili olduğu yordamlar geri çağrılırlar. Bu yöntemin olumlu yanı tekrar tekrar asenkron prosesin bitip bitmediğini kontrol edip uygulamayı bununla meşgul etmek yerine diğer işlerin yürütülmesi sağlanır. Bu modelde, Begin yordamlarının callback ve stateobject parametrelerini aldığı versiyonu kullanılır. callback parametresi System.AsyncCallback türünde, stateobject parametresi object türünde değeri alır. AsyncCallback, asenkron işlem bittikten hemen sonra tetikleyeceği geri-çağırma yordamına işaret eden ve IAsyncResult türünde parametre alan özel bir delegedir. public delegate void AsyncCallback(IAsyncResult ar); Papatya Yayıncılık Eğitim

175 İleri ADO.NET Konuları 643 Dolayısıyla callback olarak tanımlanacak olan yordam IAsyncResult türünde parametre alan ve geriye parametre döndürmeyen bir yordam olmalıdır. Begin yordamının aldığı ikinci parametre object türündeki stateobject parametresidir. Bu parametre asenkron işleminin ilişkili olduğu SqlCommand nesnesini temsil eder. static string CnnStr static SqlConnection ocnn = new SqlConnection(CnnStr); static SqlCommand ocmd; static void Main(string[] args) { // SQL Sunucuyu 5 saniye bekletip ardından güncelleme yapalım. string Qry = "WAITFOR DELAY '0:0:5';UPDATE Musteri SET KayitTarih=Getdate()"; ocmd = new SqlCommand(Qry, ocnn); ocnn.open(); // Process bittikten sonra çağıracağı yordamın adresini verelim. AsyncCallback ocallback = new AsyncCallback(HandleCallback); // ocmd public tanımlandığı için ikinci parametre null yapılabilir. ocmd.beginexecutenonquery(ocallback, null); // veya // ocmd.beginexecutenonquery(ocallback, ocmd); Console.WriteLine("Diğer işlemler çalışıyor..."); Console.ReadLine(); static void HandleCallback(IAsyncResult oar) { // ocmd public tanımlandığı için oar.asyncstate ihtiyaç duymuyoruz. // SqlCommand ocmd = (SqlCommand)oAr.AsyncState; Console.WriteLine("Güncellenen kayıt sayısı: {0", ocmd.endexecutenonquery(oar)); ocmd.dispose(); ocnn.close(); Diğer işlemler çalışıyor... Güncellenen kayıt sayısı: 4 Geri-çağırma modelinin bekleme modeline göre olumlu yanı uygulamanın herhangi bir noktada bekletilmiyor olmasıdır. Uygulama asenkron çağrıları gönderir ve işlerine devam eder; sonuçlanan her çağrı da ilişkili olduğu geri-çağırma yordamını tetikler. Aynı şekilde havuz modeline göre üstünlüğü de her an asenkron işleminin bitip bitmediğini sınanma gerekmemektedir. Geri-çağırma modelinde herhangi bir zaman-aşımı tanımlama şansı olmadığı için bu işlemleri try-catch bloğunda yapılması uygulama yönetimimizi artıracaktır. PÜF! ADO.NET te asenkron veri iletişiminde MARS özelliğini etkinleştirmek yerine her Command nesnesi için ayrı Connection nesnesi kullanılması önerilir. Bölüm 27

176 644 C# Programlama Dili SqlDependency ve SqlBulkCopy Nesneleri ADO.NET 2.0 ile birlikte sunulmuş olan System.Data.SqlClient kütüphanesinin ö- nemli bu iki sınıfı veritabanı uygulamalarının gücünü artırmış ve etki alanını genişletmiştir. Öncelikle SqlDependency sınıfını inceleyelim. SqlDependency sınıfı SQL Sunucu 2005 in önemli özelliklerinden biri olan uyarı hizmeti ile ilişkili olup sunucu ile istemci uygulama arasında sorgu bazlı uyarı sistemi kurar. Yani veritabanından uygulamamıza çektiğimiz veriler üzerinde başka bir uygulama değişiklik yaptığı zaman uygulamamız haberdar edilir. Bu özelliği sunan sistem SQL Sunucu 2005 tir; varsayılan olarak sorgu uyarılarını tuttuğu ve gönderdiği bir kuyruk yapısı sunar. Uyarı sistemine dahil olmak isteyen istemcilerin Service Broker Queue denilen bu kanala abone edilmesi gerekir. Değişiklik bildirimleri abone olmuş uygulamalara yapılır. Bu abonelerin listesi SQL Sunucu üzerinde sys.dm_qn_subscriptions'den alınabilir. Aşağıdaki şekilde uyarı sisteminin nasıl çalıştığı gösterilmiştir: Bu uyarı sisteminde yararlanmak ADO.NET tarafında SqlDependency sınıfı kullanılır; bunun yapıcı yordamı yeniden yüklenmiştir. Genellikle SqlCommand türünde parametre alan uyarlaması kullanılır. SqlCommand nesnesi SqlDependency sınıfının AddCommandDependency() yordamı aracılığıyla ilişkilendirilebilir. SQL Sunucudan gelecek sorgu bildirimlerini dinlemeyi başlatmak için Start() ve durdurmak için Stop() yordamları kullanılır; yordamlar bağlantı cümlesini parametre olarak alır. Bildirim geldiğinde uygulamayı tetiklemek için SqlDependency sınıfının OnChange-EventHandler olayı devreye girer; SqlNotificationEventArgs parametresi kullanılarak sunucu tarafında yapılan değişikliğin türü de öğrenilebilir. Sorgu bildirimi tabanlı bir uygulama yapmak için SQL Sunucu tarafında kullanılacak veritabanının Service Broker servisini desteklemesi gerekir. Bir veritabanının bu servisi kullanabilmesi için aşağıdaki gibi düzenleme yapılır. ALTER DATABASE Kitap SET ENABLE_BROKER Papatya Yayıncılık Eğitim

177 İleri ADO.NET Konuları 645 ADO.NET te uyarı işlemlerinde kullanılacak sorgunun doğal olarak UPDATE ve INSERT ifadelerini içermesi ve SELECT cümlesinde * yerine kolon isimlerinin a- çık yazılması, tablo isminin de şemasıyla birlikte belirtilmesi gerekir. Aşağıdaki örnekte Kitap veritabanındaki Musteri tablosu sorgulanmış ve tabloda bir değişiklik olursa uygulama haberdar olsun diye SqlDependency nesnesi kullanılmıştır. static void Main(string[] args) { string Qry = "SELECT MusteriId,AdSoyad FROM dbo.musteri"; SqlCommand ocmd = new SqlCommand(Qry, ocnn); // SqlDependency nesnesini oluşturalım. SqlDependency odp = new SqlDependency(oCmd); // Start yordamını DataAdapter yürütmeden önce çalıştırmalıyız. SqlDependency.Start(CnnStr); // Sunucuda değişiklik olduğu zaman çalışacak yordamı tetikleyelim. odp.onchange += new OnChangeEventHandler(dp_OnChange); SqlDataAdapter oda = new SqlDataAdapter(oCmd); DataTable odt = new DataTable(); oda.fill(odt); foreach (DataRow odr in odt.rows) Console.WriteLine("{0\t{1", odr["musteriid"], odr["adsoyad"]); Console.ReadLine(); static void dp_onchange(object sender, SqlNotificationEventArgs e) { Console.WriteLine("\nSunucudaki veriler değişti."); Console.WriteLine("Info:" + e.info); Console.WriteLine("Source:" + e.source); Console.WriteLine("Type:" + e.type); 1 Ali Korkmaz 2 Mert Cesur 6 Zeynep Akkuş 11 Ahmet Mert Program çalıştırıldığında öncelikle SQL Sunucu tarafında o veritabanı için yeni bir abone ve kuyruk tanımlanır. Bölüm 27

178 646 C# Programlama Dili Böylece bu istek Kitap veritabanı altında hizmet veren tarafından kuyruğa alınmış olur. Program çalıştırıldığı zaman Musteri tablosundaki dört kayıt getirilmiş olur. Şimdi SQL Sunucu tarafına geçip tablodaki en son kayıdı silelim. Bu durumda açık olan program şu şekilde değişir: 1 Ali Korkmaz 2 Mert Cesur 6 Zeynep Akkuş 11 Ahmet Mert Sunucudaki veriler değişti. Info:Delete Source:Data Type:Change Bu örnekte veritabanında ikinci bir değişiklik yapıldığı zaman bu istemciye bildirilmeyecektir. Bu yüzden değişiklikten sonra veriler yeniden çekilmeli ve yeniden dinlenmeye geçilmelidir. Böylece uygulama daha işe yarar hale getirilmiş olur. Ayrıca daha önce oluşturulan bağımlılık bağlantısı nesnesi kaldırılmalıdır. static string CnnStr static SqlConnection ocnn = new SqlConnection(CnnStr); static void Main(string[] args) { // Varsa bizim oluşturduğumuz dependency connection'ı durdur. SqlDependency.Stop(CnnStr); VeriCek(); Console.ReadLine(); Papatya Yayıncılık Eğitim

179 İleri ADO.NET Konuları 647 static void VeriCek() { string Qry = "SELECT MusteriId,AdSoyad FROM dbo.musteri"; SqlCommand ocmd = new SqlCommand(Qry, ocnn); // SqlDependency nesnesini oluşturalım. SqlDependency odp = new SqlDependency(oCmd); // Bağımlılık bağlantısı BAŞLAT. SqlDependency.Start(CnnStr); odp.onchange += new OnChangeEventHandler(dp_OnChange); SqlDataAdapter oda = new SqlDataAdapter(oCmd); DataTable odt = new DataTable(); oda.fill(odt); foreach (DataRow odr in odt.rows) Console.WriteLine("{0\t{1", odr["musteriid"], odr["adsoyad"]); static void dp_onchange(object sender, SqlNotificationEventArgs e) { Console.WriteLine("\nSunucudaki veriler değişti. [{0]", e.info.tostring()); // Veritabanından verileri yeniden çekelim. VeriCek(); 1 Ali Korkmaz 2 Mert Cesur 6 Zeynep Akkuş Sunucudaki veriler değişti. [Insert] 1 Ali Korkmaz 2 Mert Cesur 6 Zeynep Akkuş 16 Ahmet Mert Sunucudaki veriler değişti. [Delete] 1 Ali Korkmaz 2 Mert Cesur 6 Zeynep Akkuş Program çalıştırıldıktan sonra sunucuda tablo üzerine ekleme ve silme yapalım. Bu durumda her değişiklik istemcideki uygulamaya bildirilmiş olacaktır. Verilen örnekte öncelikle Ahmet Mert kayıdı eklenmiş ardından da silinmiştir. Bu bölümde işleyeceğimiz diğer sınıf SqBulkCopy sınıfıdır. Veritabanı uygulamalarında bazen uygulamadan SQL Sunucuya veya iki SQL Sunucu veritabanı arasında toplu data (bulk copy) aktarımı yapılması ihtiyacı duyulur. İki veritabanı arasında çok sayıda veri aktarımı olacağı zaman çoğunlukla SQL Sunucu üzerindeki içeri al/dışarı ver (import/export) aracı kullanılır. Fakat doğrudan sunucuya erişilmediği zamanlarda bu işlemin dışarıdan bir uygulamayla yapılması kaçınılmaz olur. Bu amaçla System.Data.SqlClient kütüphanesi altında bulunan SqlBulkCopy sınıfı kullanılarak iki SQL Sunucu veritabanı arasında veri taşıyacak bir yapı oluşturulabilir. Bölüm 27

180 648 C# Programlama Dili SqlBulkCopy sınıfının yapıcı yordamı yeniden yüklenmiştir. public SqlBulkCopy(SqlConnection connection); public SqlBulkCopy(string connectionstring); public SqlBulkCopy(string connectionstring, SqlBulkCopyOptions copyoptions); public SqlBulkCopy(SqlConnection connection, SqlBulkCopyOptions copyoptions, SqlTransaction externaltransaction); copyoptions parametresi kaynaktaki hangi veri satırlarının hedefe kopyalacağını bildirir. SqlBulkCopyOptions türünde parametre alıp aşağıdaki değerleri alabilir: CheckConstraints: Hedef tabloda varsa kısıtlamaları kontrol ederek veri kopyalar. Bu değer seçilmezse varsayılan olarak kısıtlamalar dikkate alınmaz. Default: Bütün seçenekler için varsayılan değerleri kullanılır. FireTriggers: Hedef tablo üzerinde varsa Insert trigger in tetiklenmesini sağlar. KeepIdentity: Kaynak tablodaki identity değerleri korunacak şekilde hedef tabloya kopyalanır. KeepNulls: Kaynak tablodaki NULL değerleri korunacak şekilde toplu kaydetme yapılır. TableLock: İşlem esnasında hedef tabloyu kilitler. Eğer bu seçenek seçilmezse varsayılan olarak satır kilitlenir. UseInternalTransaction: Bulk insert işleminde her yığının (batch), bir iç transaction kullanmasını sağlar. Bu seçeneği seçmeyip doğrudan SqlBulkCopy sınıfının üç parametre kabul eden yapıcı yordamına üçüncü parametre olarak SqlTransaction nesnesi geçilerek de el ile transaction yönetimi sağlanabilir. SqlBulkCopy sınıfının asıl işlemi yapan üyesi WriteToServer() yordamıdır; yeniden yüklenmiş olan bu yordam DataRow dizisi, DataTable nesnesi ve IDataReader arabirimi türünde parametre alabilen türevlere sahiptir. Ayrıca DataTable ve DataRowState türünde iki parametre alan uyarlaması da özellikle kaynak tabloda belirli duruma sahip satırları kopyalamak için kolaylık sağlar. Az kullanılsa da performans açısından önemli olan BatchSize özelliği her yığında olması gereken satır sayısını bildirir. Batch ifadesi, veritabanına bir seferde gönderilen yığını belirtir. Integer türünde değer alan BatchSize özelliği hedef sunucuya bir adımda gönderilecek satır sayısını belirler. Papatya Yayıncılık Eğitim

181 İleri ADO.NET Konuları 649 SqlBulkCopy sınıfı NotifyAfter özelliğinde belirlenmiş sayıda satırı kopyaladığında SqlRowsCopied olayını tetikler. Örneğin NotifyAfter özelliğine 100 değeri verildiği zaman kopyalanan her 100 satırın sonunda SqlRowsCopied olayının bağlı olduğu yordam çalıştırılır. Kaynak ile hedef tabloları farklı şemalara sahip olduğu zaman kaynaktaki kolonun hedefteki hangi kolona kopyalanacağı gibi tablolardaki kolonlar arasındaki ilişki için eşleme işlemi yapılırdı. ColumnMappings özelliği bu eşleştirme işleminin yapılmasını sağlıyor. Şimdi bu üyeleri kullanarak basit bir örnek yapalım. Aşağıdaki örnekte SQL Sunucu 2005 ile birlikte hazır gelen AdventureWorks veritabanı kullanılacaktır; 700 kayıtlı Store tablosunu kendisiyle aynı şemaya sahip olan StoreNew tablosuna aktaracağız. static void Main(string[] args) { string CnnStr MultipleActiveResultSets=true"; string Qry = "SELECT * FROM Sales.Store"; SqlConnection ocnn = new SqlConnection(CnnStr); SqlCommand ocmd = new SqlCommand(Qry, ocnn); // Verileri çekip DataReader'e yerleştirelim. ocnn.open(); SqlDataReader odr = ocmd.executereader(); // Aynı sunucuda toplu işlem yapacağız. // Veriler bloklar şeklinde gönderileceği için etkileşim gereklidir. SqlBulkCopy obc = new SqlBulkCopy(CnnStr, SqlBulkCopyOptions.UseInternalTransaction); obc.destinationtablename = "Sales.StoreNew"; // Tek bir seferde aktarılacak satır sayısı(yığın Boyutu) obc.batchsize = 250; // Her 300 satırdan sonra SqlRowsCopied tetiklensin. obc.notifyafter = 300; // Olay için tetiklenecek yordam. obc.sqlrowscopied += new SqlRowsCopiedEventHandler(bc_SqlRowsCopied); // Hedef tablonun boş olduğundan emin olalım. new SqlCommand("TRUNCATE TABLE Sales.StoreNew", ocnn).executenonquery(); // İstemcideki odr nesnesindeki kayıtları hedefe yazdıralım. obc.writetoserver(odr); obc.close(); odr.close(); ocnn.close(); Console.WriteLine("Aktarım tamamlandı."); Console.ReadLine(); static void bc_sqlrowscopied(object sender, SqlRowsCopiedEventArgs e){ Console.WriteLine("--{0 satır kopyalandı.--", e.rowscopied); Bölüm 27

182 650 C# Programlama Dili satır kopyalandı satır kopyalandı.-- Aktarım tamamlandı. 700 kayıt içeren Store tablosu SqlDataReader veriyi 250 şer bloklar şeklinde okuyup hedef veritabanına gönderecektir. Burada BatchSize özelliğini kullanarak verileri birden fazla yığında gönderdiğimiz için SqlBulkCopy nesnesini etkileşim kullanacak şeklinde oluşturduk. Eğer kaynak ve hedef tablo şemaları farklıysa kolonlar arasında eşleştirme yapılmalıdır. Örneğimizi hedef olarak Kitap isimli veritabanının altındaki Musteri tablosu kullanılacak şekilde düzenleyelim. Musteri tablosunda MusteriId ve AdSoyad kolonları bulunmaktadır. static void Main(string[] args) { string CnnStr MultipleActiveResultSets=true"; string Qry = "SELECT * FROM Sales.Store"; SqlConnection ocnn = new SqlConnection(CnnStr); SqlCommand ocmd = new SqlCommand(Qry, ocnn); // Verileri çekip DataReader'e yerleştirelim. ocnn.open(); SqlDataReader odr = ocmd.executereader(); // Aynı sunucu üzerinde toplu işlem yapacağız. SqlBulkCopy obc = new SqlBulkCopy(oCnn); // Olay için tetiklenecek yordam. obc.sqlrowscopied += new SqlRowsCopiedEventHandler(bc_SqlRowsCopied); // Kaynak ile hedef arasında kolon eşleştirmesi yapalım. obc.columnmappings.add("customerid","musteriid"); obc.columnmappings.add("name","adsoyad"); // Aynı sunucuda oldukları için Veritabanı adı yeterlidir. obc.destinationtablename = "Kitap..Musteri"; // İstemcideki odr nesnesindeki kayıtları hedefe yazdıralım. Papatya Yayıncılık Eğitim

183 İleri ADO.NET Konuları 651 obc.writetoserver(odr); obc.close(); odr.close(); ocnn.close(); Console.WriteLine("Aktarım tamamlandı."); Console.ReadLine(); static void bc_sqlrowscopied(object sender, SqlRowsCopiedEventArgs e) { Console.WriteLine("--{0 satır kopyalandı.--", e.rowscopied); Aktarım tamamlandı. Burada kaynak ile hedef arasında sadece eşleştirme yapılmış kolonlar dikkate alınır; eşleştirme yapıldığında her iki tablodaki kolon sayısının farklı olması önemsenmez. Eşleştirme işleminde kolon ismi yerine kolonların indisleri de kullanılabilir. Burada eşleştirilen kolonların SQL Sunucunun hassas konusu olan collation bilgilerinin aynı olmasına dikkat edilmelidir. Ayrıca NotifyAfter özelliği kullanılmadığı için SqlRowsCopied olayı da tetiklenmemiş oldu ve toplu işlemi yığınlara bölünmediği için de bir transaction yönetimine ihtiyaç kalmamış oldu. Her iki örnekte de aynı aktif bağlantı nesnesi birden fazla işlem için kullanıldığından dolayı bağlantı cümlesindeki MARS özelliği etkinleştirildi. PÜF Eğer toplu veri aktarımı yapılacak kaynak ve hedef veritabanları aynı SQL Sunucu ü- zerindeyse SqlBulkCopy nesnesi yerine doğrudan SQL Sunucu tarafında INSERT SELECT ifadesinin kullanılması daha performanslı olacaktır. SqlBulkCopy sınıfının yeteneği veri aktarımında kaynak ve hedefin yalnızca SQL Sunucu olmasıyla sınırlı değildir; yani iki veritabanı arasında aktarım yapmakla birlikte flat file ve Excel gibi kaynaklardan da toplu veri aktarımı yapabilmektedir. Elimizde aşağıdaki gibi bir xml dosyasının olduğunu düşünelim. <?xml version="1.0" standalone="yes"?> <Musteriler> <Musteri> <Kod>1</Kod> <AdSoyad>Zeynep Ilhan</AdSoyad> </Musteri> <Musteri> <Kod>2</Kod> <AdSoyad>Cem Can</AdSoyad> </Musteri> <Musteri> <Kod>3</Kod> <AdSoyad>Metin Ak</AdSoyad> </Musteri> </Musteriler> Bölüm 27

184 652 C# Programlama Dili Bu xml dosyasını bir hamlede SQL Sunucudaki Kitap veritabanının altındaki Musteri tablosuna aktaralım. string CnnStr DataTable odt = new DataTable("Musteri"); odt.readxmlschema(@"c:\musteri.xml"); odt.readxml(@"c:\musteri.xml"); using (SqlConnection ocnn = new SqlConnection(CnnStr)) { ocnn.open(); using (SqlBulkCopy obc = new SqlBulkCopy(CnnStr)) { obc.columnmappings.add("kod","musteriid"); obc.columnmappings.add("adsoyad", "AdSoyad"); obc.destinationtablename = "Musteri"; obc.writetoserver(odt); Console.WriteLine("Aktarım tamamlandı."); Son olarak Excel örneğini de gösterip konumuzu tamamlayalım. Yine aynı mantıkla Excel dosyasından verileri okuyup bir DataReader nesnesine aktaracağız. Excel den verileri okumak için OleDbConnection sınıfını kullanacağız. Bu yüzden projemize System.Data.OleDb kütüphanesini aktarmayı unutmamalıyız. string XclCnnStr Source=C:\Musteri.xls;Extended Properties=""Excel 8.0;HDR=YES;"""; // Excel ile bağlantı kuralım. using (OleDbConnection oxclcnn = new OleDbConnection(XclCnnStr)) { // Excele gönderilecek sorgu. OleDbCommand oxclcmd = new OleDbCommand("Select Kod,AdSoyad FROM [Sheet1$]", oxclcnn); // Excel bağlantısını açalım. oxclcnn.open(); // Excel Worksheet'teki verileri DataReader'e aktaralım. using (OleDbDataReader odr = oxclcmd.executereader()) { Papatya Yayıncılık Eğitim

185 İleri ADO.NET Konuları 653 // SQL Sunucu bağlantı cümlesi. string SqlCnnStr using (SqlBulkCopy obc = new SqlBulkCopy(SqlCnnStr)) { obc.columnmappings.add("kod", "MusteriId"); obc.columnmappings.add("adsoyad", "AdSoyad"); obc.destinationtablename = "Musteri"; obc.writetoserver(odr); Console.WriteLine("Aktarım tamamlandı."); Aktarım tamamlandı Veri Sağlayıcı Bağımsızlığı (DbProviderFactory) Şu ana kadar verilen örneklerde veritabanı olarak hep SQL Sunucu kullanıldı; dolayısıyla ADO.NET içerisinde SQL sunucuya özgü SqlConnection, SqlDataReader, SqlCommand ve SqlDataAdapter gibi nesneler kullanıldı. Eğer örnekler Oracle veya MS Access gibi farklı bir veritabanında uygulanmak istenirse onlara özgü nesnelerle değiştirilmelidir. örneğin SqlConnection yerine OraConnection veya OleDbConnection nesneleri kullanılmalıdır. Çünkü ADO.NET, ADO dan farklı o- larak her veritabanı yönetim sistemine (VTYS) özgü veri sağlayıcıları içerir. VTYS nin tipine göre aşağıdaki sağlayıcılar kullanılır: ODBC için.net Framework veri sağlayıcı OLEDB için.net Framework veri sağlayıcı Oracle için.net Framework veri sağlayıcı SQL sunucu için.net Framework veri sağlayıcı. Bu nedenle.net ortamında geliştirilecek bir veritabanı uygulamasının ilk adımı hangi VTYS nin kullanılacağıdır. Örneğin Oracle veritabanına ait bazı özellikler SQL Sunucudan farklı olabilmektedir. ADO.NET'te veri sağlayıcılarından bağımsız bir mimari oluşturulması için System.- Data.Common kütüphanesi vardır. Bu kütüphane ADO.NET in kalbi olan yönetilebilir sağlayıcıların temelini oluşturan ve birçok VTYS için ortak sınıfları içerir. IDbConnection, IDbCommand ve IDataReader gibi arabirimler kullanılarak kodların hem SQL hem de Oracle için çalışması sağlanabilirdi. Bununla ilgili küçük bir örneği ADO.NET 2.0 daki çözümlerle inceleyelim. Projemizde DataAccessLayer.cs isminde yeni bir sınıf dosyası oluşturalım. using System; using System.Data; using System.Data.SqlClient; using System.Data.OleDb; Bölüm 27

186 654 C# Programlama Dili // Aynı şekilde System.Data.Odbc ve System.Data.OracleClient kütüphaneleri eklenir. // Seçilecek veri sağlayacıların listesi. public enum DataProvider { Oracle, SqlServer, OleDb, Odbc public class DBManager { DataProvider _providertype; String _cnnstr; IDbConnection _ocnn; IDbCommand _ocmd; IDataReader _odr; // =====DBManager Yapılandırıcı========= public DBManager(DataProvider ProviderType, string CnnStr){ // Sağlayıcı türü ve bağlantı cümlesi parametre olarak girilsin. this._providertype = ProviderType; this._cnnstr = CnnStr; // Dışarıdan erişilecek DataReader nesnesi. public IDataReader DataReader { get { return _odr; set { _odr = value; // =====Connection Nesnesi========= public IDbConnection GetConnection() { // Seçilen sağlayıcıya göre Connection nesnesini döndüreceğiz. switch (_providertype) { case DataProvider.SqlServer: return new SqlConnection(_cnnStr); case DataProvider.OleDb: return new OleDbConnection(_cnnStr); // OdbcConnection ve OracleConnection için de tanımlama yapılır. default: return null; // =====Command Nesnesi========= public IDbCommand GetCommand() { // Seçilen sağlayıcıya göre Command nesnesini döndüreceğiz. switch (_providertype) { case DataProvider.SqlServer: return new SqlCommand(); case DataProvider.OleDb: return new OleDbCommand(); // OdbcCommand ve OracleCommand için de tanımlama yapılır. default: return null; // =====Connection.Open() Yordamı========= public void Open() { _ocnn = GetConnection(); _ocnn.connectionstring = this._cnnstr; Papatya Yayıncılık Eğitim

187 İleri ADO.NET Konuları 655 if (_ocnn.state!= ConnectionState.Open) _ocnn.open(); // =====Connection.Close() Yordamı========= public void Close() { if (_ocnn.state!= ConnectionState.Closed) _ocnn.close(); // =====DataReader.ExecuteReader() Yordamı========= public IDataReader ExecuteReader(CommandType CmdType, string CmdText) { this._ocmd = GetCommand(); _ocmd.connection = this._ocnn; _ocmd.commandtext = CmdText; _ocmd.commandtype = CmdType; this._odr = _ocmd.executereader(); return this._odr; // =====DataReader.Close() Yordamı========= public void CloseReader() { if (this._odr!= null) this._odr.close(); // =====Object.Dispose() Yordamı========= public void Dispose() { this.close(); this._ocnn = null; Böylece birden fazla VTYS i destekleyecek bir veritabanı katmanı oluşturmuş olduk. Bölüm 27

188 656 C# Programlama Dili Şimdi bunu hem SQL Sunucu hem de MS Access için kullanalım. // SQL Sunucu için bağlantı cümlesi tanımlayalım. string SqlCnnStr // SQL Sunucuda çalışacak sorguyu yazalım. string SqlQry ="SELECT MusteriId, AdSoyad FROM Musteri"; DBManager odbm = new DBManager(DataProvider.SqlServer, SqlCnnStr); odbm.open(); odbm.executereader(commandtype.text, SqlQry); // Oluşturulan DataReader'i okuyalım. Console.WriteLine("SQL Server->Musteri tablosu"); Console.WriteLine(" "); while (odbm.datareader.read()) Console.WriteLine(oDbM.DataReader["AdSoyad"]); // Nesneyi yok edelim. odbm.dispose(); SQL Server->Musteri tablosu Zeynep Ilhan Cem Can Metin Ak Aynı nesneyi MS Access için de kullanmak için bağlantı cümlesini değiştirmemiz ve sağlayıcı olarak DataProvider.OleDb seçmemiz yeterlidir. Ornek.mdb dosyasındaki Sehir tablosunu okuyalım. // MS Access için bağlantı cümlesi tanımlayalım. string OleCnnStr Source=C:\Ornek.mdb"; // MS Access'de çalışacak sorguyu yazalım. string OleQry ="SELECT SehirId, SehirAd FROM Sehir"; DBManager odbm = new DBManager(DataProvider.OleDb, OleCnnStr); odbm.open(); odbm.executereader(commandtype.text, OleQry); // Oluşturulan DataReader'i okuyalım. Console.WriteLine("MS Access->Sehir tablosu"); Console.WriteLine(" "); while (odbm.datareader.read()) Console.WriteLine(oDbM.DataReader["SehirAd"]); // Nesneyi yok edelim. odbm.dispose(); MS Access->Sehir tablosu Ankara İstanbul Gaziantep Papatya Yayıncılık Eğitim

189 İleri ADO.NET Konuları 657 Örneğimizde sadece OleDb veya SqlClient sağlayıcıları için genel işlemler yazıldı. Aynı şekilde Odbc veya OracleClient için de yazılabilir. Hatta DataAdapter, DataParameter, DataTransaction ve DataSet nesneleri için de ilgili yordamlar yazılarak ve örnekteki bağlantı cümlesi, sağlayıcı bilgisi gibi dışarıdan atanabilecek bilgiler uygulamanın konfigürasyon (App.Config) dosyasında tutularak bütünüyle bir veri katmanı oluşturulabilir. Generic Data Access için standart bir model o- luşturmak zordur; ancak genel olarak bu formatta oluşturulur. ADO.NET 2.0 bu arabirim yaklaşımını desteklemekle birlikte bütün sağlayıcı sınıflarının türetildiği ve sözkonusu arabirimleri uygulamış sınıflar geliştirerek bu işlemleri daha da kolaylaştıracak kalıtım (inheritance) yaklaşımını sunmaktadır. Veri sağlayıcı sınıf Connection Command Parameter DataReader Dataadapter Transaction CommandBuilder Temel sınıf DbConnection DbCommand DbParameter DbDataReader DbDataAdapter DbTransaction DbCommandBuilder Bu yeni sınıflarla birlikte üretim modeli şu şekilde olmuş oldu: IDb* arabirimleri (IDbConnection gibi) Db* abstract base sınıflar(dbconnection gibi) Sağlayıcı bağımsız katman SQL OleDb ODBC Oracle 3rd Party Sağlayıcı bağımlı katman Bu şekil SqlConnection ve OracleConnection gibi sınıfların DbConnection isimli temel sınıftan türetildiğini göstermektedir. Yani System.Data.Common kütüphanesi şimdiye kadar işlediğimiz ve birçok sağlayıcı için kullanılan Connection, Command, DataAdapter ve DataReader gibi nesnelerinin temel sınıflarını içerir. Bu Bölüm 27

190 658 C# Programlama Dili da demektir ki, bu temel sınıfları kullanarak aynı anda farklı VTYS leri destekleyecek jenerik kodlar yazılabilir. ADO.NET bu sınıfların amacına uygun kullanılması için "Factory Classes denilen üretim sınıflarını sunmuştur. SqlClientFactory ve OracleClientFactory şeklinde her sağlayıcı için bir üretici sınıf geliştirildi. Abstract Factory tasarım desenine dayanan bu sınıflar birer fabrika görevi görüp o sağlayıcı için kullanılacak DbConnection, DbCommand ve DbDataAdapter gibi sınıfları üretir. Genel Üretim Modeli (Generic Factory Model) denilen bu yöntemde çalışma anında bir veri sağlayıcı belirlenir ve o sağlayıcıyla ilişkili nesneler o- luşturulur. Yani uygulamamızdaki veritabanı işlem kodlarını değiştirmek yerine küçük bir yönlendirmeyle ortama uygun sağlayıcıyı seçtirerek uygulamanın farklı VTYS lerde çalışmasını sağlayabileceğiz. Sağlayıcılar için tanımlı üretim sınıfları machine.config dosyasına kayıt edilmiş durumdadır. Programcı, makinedeki tüm.net uygulamaları tarafından erişilebilen bu konfigürasyon dosyasına DbProviderFactory yapısına uygun yeni bir sağlayıcı tanımlayabilir. [machine.config] <DbProviderFactories> <add name="odbc Data Provider" invariant="system.data.odbc" description=".net Framework Data Provider for Odbc" type="system.data.odbc.odbcfactory, System.Data, Version= , Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <add name="oledb Data Provider" invariant="system.data.oledb" description=".net Framework Data Provider for OleDb" type="system.data.oledb.oledbfactory, System.Data, Version= , Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <add name="oracleclient Data Provider" invariant="system.data.oracleclient" description=".net Framework Data Provider for Oracle" type="system.data.oracleclient.oracleclientfactory, System.Data.OracleClient, Version= , Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <add name="sqlclient Data Provider" invariant="system.data.sqlclient" description=".net Framework Data Provider for SqlServer" type="system.data.sqlclient.sqlclientfactory, System.Data, Version= , Culture=neutral, PublicKeyToken=b77a5c561934e089" /> </DbProviderFactories> Generic Factory yönteminin başrol oyuncuları DbProviderFactories ve DbProviderFactory sınıflarıdır. System.Data.Common kütüphanesinin altına bulunan bu sınıflar üretimlik nesneler olup sağlayıcı temelli veritabanı işlemlerini gerçekleştirecek nesneleri oluştururlar. DbProviderFactories sınıfı uygulamanın bulunduğu makinede kayıt edilmiş sağlayıcıları yakalamamızı sağlar; bu amaçla iki temel yordama sahiptir: GetFactoryClasses() ve GetFactory(). GetFactoryClasses() yordamı makinede yüklü olan veri sağlayıcılarını DataTable türünde döndürür. Papatya Yayıncılık Eğitim

191 İleri ADO.NET Konuları 659 GetFactory() yordamı ise seçtiğimiz veri sağlayıcısına uygun Command ve Connection nesneleri oluşturmak üzere DbProviderFactory türünde bir nesne döndürür. Bu bölümün asıl konusu bu nesnedir. DbProviderFactory sınıfı ise önceki paragrafta bahsedilen sağlayıcı temelli üretici sınıfların temel sınıfıdır. Dolayısıyla SqlClientFactory ve OracleClientFactory gibi sınıflar yalnızca kendi sağlayıcılarıyla ilişkili nesneler üretebilirken doğrudan DbProviderFactory sınıfının kullanılması her çeşit sağlayıcıyı destekleyecek nesnelerin üretilmesine olanak sağlamış olur. Şimdi bu ana üretim sınıflarını kullanarak sağlayıcı-bağımsız örnekler yapalım. Öncelikle sistem üzerindeki veri sağlayıcıları listeleyelim. Bu listenin içeriğini daha açık görmek için kitabın formatı dışına çıkıp bir Windows formunda DataGridView kontrolünde göstereceğiz. private System.Windows.Forms.DataGridView datagridview1; private void InitializeComponent() { this.datagridview1 = new System.Windows.Forms.DataGridView(); private void Form1_Load(object sender, EventArgs e) { datagridview1.datasource = DbProviderFactories.GetFactoryClasses(); DbProviderFactories sınıfı static, DbProviderFactory sınıfı ise abstract özelliğine sahiptir. Yani DbProviderFactory sınıfından örnek oluşturulamaz. Bu sınıfın türünden nesneyi DbProviderFactories sınıfının GetFactory() yordamı aracılığıyla elde edebiliriz. Yeniden yüklenmiş olan GetFactory() yordamı aşağıdaki uyarlamalara sahiptir. Bölüm 27

192 660 C# Programlama Dili public static DbProviderFactory GetFactory(DataRow providerrow); public static DbProviderFactory GetFactory(string providerinvariantname); İlk uyarlamada DataRow türünde parametre alır. Bu parametre DbProviderFactories sınıfının GetFactoryClasses() yordamının döndürdüğü tablonun satırını temsil eder. Yani önceki tabloda listesini aldığımız sağlayıcı satırlardan birisi bu parametreye değer olarak girilir. DataRow odr = DbProviderFactories.GetFactoryClasses().Rows[2]; DbProviderFactory odbf ; odbf= DbProviderFactories.GetFactory(oDr); Console.WriteLine(oDbf.ToString()); // System.Data.OracleClient.OracleClientFactory GetFactory() yordamının ikinci uyarlaması string türünde parametre alır. Bu parametre veri sağlayıcısının sistemdeki adını temsil eder. DbProviderFactory odbf ; odbf = DbProviderFactories.GetFactory("System.Data.SqlClient"); Console.WriteLine(oDbf.ToString()); // System.Data.SqlClient.SqlClientFactory GetFactory() yordamına parametre olarak geçtiğimiz sağlayıcı ismini, programın kendi içine gömmekten ziyade dışarıdan okutmamız daha doğru olacaktır. Bunun için de en çok tercih edilen yöntem bu bilginin uygulamanın konfigürasyon dosyasında (App.Config veya Web.Config) dosyasında tutulmasıdır. Bu amaçla config dosyasına aşağıdaki gibi bir kayıt girelim. [web.config] <connectionstrings> <add name="vtbilgi" providername="system.data.sqlclient" connectionstring="data source=.;integrated Security= true;initial Catalog=Kitap;"/> </connectionstrings> Konfigürasyon dosyasındaki bu alanlara erişmek için System.Configuration. ConfigurationManager sınıfı kullanılır. string PrvrTip; PrvrTip=ConfigurationManager.ConnectionStrings["VtBilgi"].ProviderName; DbProviderFactory odbf = DbProviderFactories.GetFactory(PrvrTip); Console.WriteLine(oDbf.ToString()); System.Data.SqlClient.SqlClientFactory Dolayısıyla uygulamanın farklı bir VTYS ile çalışmasını istediğimizde App.Config dosyasında VtBilgi için oluşturulan sağlayıcının adı ve bağlantı cümlesi değiştirilmesi yeterli olacaktır. Belirlenmiş olan sağlayıcıya göre veritabanı işlem nesnelerinin oluşturulması için DBProviderFactory sınıfı create yordamları içerir. Papatya Yayıncılık Eğitim

193 İleri ADO.NET Konuları 661 Bu yordamların isimlerinden de anlaşılacağı üzere CreateCommand() DbCommand nesnesi, CreateConnection() DbConnection nesnesini oluşturmak için kullanılır. Aşağıdaki örnekte DbProviderFactory sınıfı kullanılarak veritabanıyla bağlantı kurulmuş ve Musteri tablosu çekilerek DataTable nesnesine atılmıştır. string PrvrTip; PrvrTip = ConfigurationManager.ConnectionStrings["VtBilgi"].ProviderName; DbProviderFactory odbf = DbProviderFactories.GetFactory(PrvrTip); // App.config'de bulunan bağlantı cümlesini okuyalım. string CnnStr=ConfigurationManager.ConnectionStrings["VtBilgi"].ConnectionString; // Connection nesnesi. DbConnection ocnn = odbf.createconnection(); ocnn.connectionstring = CnnStr; // Command nesnesini. DbCommand ocmd = odbf.createcommand(); ocmd.commandtext = "SELECT * FROM Musteri"; ocmd.connection = ocnn; // DbDataAdapter nesnesi. DbDataAdapter oda = odbf.createdataadapter(); oda.selectcommand = ocmd; // DataTable sağlayıcıdan bağımsız olduğu için özel işlem yapılmıyor. DataTable odt = new DataTable(); oda.fill(odt); foreach (DataRow odr in odt.rows) Console.WriteLine("{0\t{1", odr["musteriid"], odr["adsoyad"]); 1 Zeynep İlhan 2 Cem Can Uygulamayı MS Access için uyarlayalım; kodlara karışmıyoruz; yalnızca App.Config dosyasında OleDb sağlayıcı kullanacağı ve.mdb dosyası yolu veriliyor. Bölüm 27

194 662 C# Programlama Dili [App.Config] <connectionstrings> <add name="vtbilgi" providername="system.data.oledb" connectionstring="provider=microsoft.jet.oledb.4.0;data Source=C:\Ornek.mdb"/> </connectionstrings SqlClientFactory, OracleClientFactory, OleDbFactory ve DbProviderFactory gibi üretici sınıflarından bir örnek nesne oluşturmak için bunların kendi türünde değer döndüren salt-okunur Instance özelliği kullanılır. Mevcut makine üzerinde seçilmiş olan sağlayıcıyla uyumlu veri kaynak listesinin olup olmadığı bilgisi bu üretici sınıflarının bool türünde değer döndüren CanCreateDataSource-Enumerator özelliğiyle öğrenilir. Eğer sunucu üzerinde veri kaynağı olarak kullanılabilecek sistem varsa, onların listesine üretici sınıflarının CreateDataSourceEnumerator() yordamıyla erişilir. Bu yordam geriye DbDataSourceEnumerator sınıfı türünde değer döndürür. Bu sınıfın DataTable türünde değer döndüren GetDataSources() yordamı kullanılarak sunucu üzerinde muhtemel veri kaynakların listesi elde edilebilir. Bu yordamları bir örnekte kullanalım. Konunun başında Windows DataGridView kontrolü üzerinde sunucu üzerinde tanımlı sağlayıcıların listesini almıştık. Aynı şekilde bu sefer herhangi bir sağlayıcıyla ilişkili veri kaynakları listeleyeceğiz. Örnek olarak SqlClient sağlayıcı seçilmiştir. private System.Windows.Forms.DataGridView datagridview1; private void InitializeComponent() { this.datagridview1 = new System.Windows.Forms.DataGridView(); private void Form1_Load(object sender, EventArgs e) { // Bir factory nesnesi elde edelim DbProviderFactory odbf = SqlClientFactory.Instance; // Bu sınıfın kullanabileceği veri kaynağı listesi var mıdır if (odbf.cancreatedatasourceenumerator) { DbDataSourceEnumerator oinstance = odbf.createdatasourceenumerator(); DataTable odt = oinstance.getdatasources(); datagridview1.datasource = odt; else MessageBox.Show("Herhangi bir datasource bulunamadı."); Papatya Yayıncılık Eğitim

195 İleri ADO.NET Konuları 663 Yazarımızın sisteminde SqlClientFactory sınıfı için kullanabileceği iki adet veri kaynağı bulundu. Bunlardan birisi SQL 2000, diğeri de SQL 2005 kaynağıdır. Aslında ağ üzerindeki SQL Sunucu sistemlerini sorgulamış olduk. Bu yordam çalışma anında dinamik olarak veri kaynağı belirlemek için de kullanılabilir. DBProviderFactory sınıfı yordamlarına bakıldığı zaman DataReader nesnesi oluşturmak için CreateDataReader şeklinde bir yordamın bulunmadığı görülür. Sağlayıcı-bağımsız mimaride DataReader nesnesini kullanmak için DbCommand nesnesinin ExecuteReader() yordamı kullanılır. SQL Sunucudaki Musteri tablosunu DataReader ile okuyalım. string PrvrTip; PrvrTip = ConfigurationManager.ConnectionStrings["VtBilgi"].ProviderName; DbProviderFactory odbf = DbProviderFactories.GetFactory(PrvrTip); string CnnStr=ConfigurationManager.ConnectionStrings["VtBilgi"].ConnectionString; using (DbConnection ocnn = odbf.createconnection()) { ocnn.connectionstring = CnnStr; using (DbCommand ocmd = ocnn.createcommand()) { ocmd.commandtext = "SELECT MusteriId,AdSoyad FROM Musteri"; ocnn.open(); using (DbDataReader odr = ocmd.executereader()) { while (odr.read()) { Console.WriteLine(oDr["AdSoyad"]); Zeynep İlhan Cem Can Veri sağlayıcı-bağımsız mimari için sonuç olarak şu söylenebilir: Yazılan uygulamanın birden fazla VTYS ile uyumlu çalışabilmesi için doğrudan bir sağlayıcıyla ilişkili nesneleri seçmek yerine çalışma esnasında parametrik olarak seçilecek herhangi bir sağlayıcıyla uyumlu nesne yapısını tercih etmeliyiz. Yani hangi sağlayıcı nesnesini kullanılacağı tasarım aşamasında değil, çalışma anında belirlenmelidir Özet Uygulamalarda veritabanıyla ilgili işlem yapıldığı zaman her defasında yeni bir bağlantı nesnesi oluşturmak yerine, varsa daha önce oluşturulmuş bağlantı nesnesini kullanmak, uygulama ve veritabanı tarafında zaman kazandırır. Bağlantı havuzu o- larak tanımlanan mekanizma sayesinde ilk istekle birlikte oluşturulmuş olan bağlantı nesnesi saklanır; daha sonra aynı veya farklı kullanıcılardan gelen taleplerde bellekteki bu nesne kullanılır ve işlem bittikten sonra nesne yeniden belleğe atılır. Bölüm 27

196 664 C# Programlama Dili MARS, tek bağlantı nesnesi üzerinden birden fazla sorguya izin verir. ADO.NET, varsayılan olarak veritabanı işlemlerini senkronize yürütür. ADO.NET 2.0 ile birlikte veritabanı işlemlerinin asenkron yürütülmesi için çeşitlik yöntemler geliştirildi. Asenkron işleminde veritabanından yanıt bekleme sürecinde istenilirse istemci uygulama başka işlemleri yürütebilir. ADO.NET te asenkron işlemde performans ve işlevsellik kazanmak için üç çeşit yaklaşım benimsenmiştir. ADO.NET teknolojisi SQL Server 2005 in bir özelliği olan uyarı servisi ile ilişkili çalışabilir. Bu servis SQL sunucu ile istemci uygulama arasında sorgu bazlı uyarı sistemi kurar. ADO.NET te bu uyarı sistemine dahil olmak için System.Data.SqlClient kütüphanesinin SqlDependency sınıfı kullanılır Sorular 27.1) Bağlantı havuzu (connection pooling) modeli ne amaçla kullanılmaktadır? Örnek vererek açıklayınız. 27.2) DataSet ile DataReader nesneleri arasındaki farkları açıklayınız. 27.3) Bekleme (wait) modeliyle geri-çağırma (callback) arasındaki farkı açıklayınız. Hangi tür uygulamalarda hangisi tercih edilir? 27.4) ADO.NET içerisinde veri ağlayıcı bağımsızlığı nasıl sağlanır? 27.5) MARS özelliği nedir? Bir örnekle açıklayınız? 27.6) Asenkron veri erişimi nedir? Hangi modellerde kullanılır? 27.7) ADO.NET te veri sağlayıcı-bağımsızlığı için hangi sınıf ve yöntemler kullanılır? Papatya Yayıncılık Eğitim

197 28. XML Nedir? Verilerimizi düzenli ve sorgulanabilir yapıda saklamak için kullandığımız Oracle, SQL Server, MS Access ve Excel gibi veritabanı sistemleri genel olarak verileri kendilerine özgü formatta saklarlar. Dolayısıyla birinde oluşturulan bir dosyanın formatı bir başka veritabanı yönetim sistemi ile doğrudan uyuşmaz. Böylesi bir durum bu tür sistemlerden verilerin taşınmasını, başka sistemlere aktarılmasını veya farklı sistemler arasında paylaştırılmasını zorlaştırmaktadır. Veritabanlarının bu anlamda sahip olamadığı esnekliği sağlamak ve verileri farklı ortamlar arasında kolayca taşımak için XML isminde metin tabanlı belge teknolojisi geliştirildi XML Nedir? Genişletilebilir işaretleme dili olarak adlandırılan XML (Extensible Markup Language) teknolojisi, özellikle İnternet gibi farklı ortam ve sistemlerin kullanıldığı heterojen ortamlarda veriyi kolayca paylaşmak amacıyla, bağımsız bir organizasyon olan W3C (Wide Web Consortium) tarafından IBM, Microsoft ve Sun gibi büyük şirketlerin katılımıyla 1996 da duyuruldu. Güvenlik duvarlarına takılmayan ve birçok protokol üzerinden aktarılabilen, kolaylıkla okunabilir ve işlenebilir yapıya sahip XML formatı, başlangıçta elektronik ticaretin gelişimiyle birlikte tedarikçi ve alıcı arasında veri transferinin belirli standartta ve kolaylıkta yapılması üzere çıkmış olsa da bugün, bankacılık, finans, sağlık, eğitim, ulaşım ve otomotiv gibi birçok sektörün bilgi teknolojileri bacağının vazgeçilmez formatı oldu. XML teknolojisinin bu esnekliği, temelini oluşturan işaretleme (markup) dilinden gelmektedir. İşaretleme dilleri, verilerin programcının belirlediği etiketlerle işaretlenmesini sağlar. Farklı türdeki belgeleri tanımlamak üzere tasarlanmış böylesi dillerin ana yapısı 1986 yılında ANSI tarafından standart hale getirilmiş olan SGML (Standard Generalized Markup Language) dilidir. Ardından daha az uzmanlık gerektiren ve programlanması kolay olan bir dil olan Web'in de temelini oluşturan HTML (Hypertext Markup Language) dili geliştirildi. Günümüzde, özellikle ticari hayatın teknolojiden beklentisinin artması, B2B uygulamalarının gelişmesi ve hemen her şeyin veri ile ölçülmesi karşısında HTML yetersiz kaldı. HTML dilinin yeteneklerini geliştirmek için SGML kadar güçlü ve esnek olan XML dili geliştirildi.

198 666 C# Programlama Dili XML, HTML nin aksine yalnızca işaretleme yapmaz; aynı zamanda SGML gibi işaretleme dilini oluşturma imkanı verir. Belgede kullanılan işaretleme etiketlerinin ve onların uygulandığı içeriğin ne anlama geldiğini tanımlar. XML, temelde kurumların uygun verileri, güvenlik duvarı üzerinde karşı taraf için bir tanımlama yapmadan ve farklı kişilere farklı formatlarda gösterilebilecek şekilde paylaşmasını sağlar. XML belgesinin farklı formatlarda gösterimi CSS/XSL/XSLT gibi biçimlendirici teknolojiler aracılığıyla gerçekleştirilir. Bir XML belgesini oluşturmak için herhangi bir metin editörü kullanılabilir. XML formatında bir belgeyi herhangi bir İnternet tarayıcısıyla veya ofis ürünleriyle kolaylıkla açabiliriz. Aşağıdaki XML belgesinde ADO.NET örneklerinde verilen Musteri tablosu temsil edilmiştir. <?xml version="1.0" encoding="iso "?> <Musteriler> <Musteri> <MusteriId>1</MusteriId> <AdSoyad>Murat Tatlı</AdSoyad> </Musteri> <Musteri> <MusteriId>3</MusteriId> <AdSoyad>Zeynep Beril</AdSoyad> </Musteri> </Musteriler> XML belgesinin ilk satırı XML bildirimi olarak tanımlanır. Bu satır, belgenin bir XML belgesi olduğunu, kodlandığı XML sürümünü ve belgedeki karakter kodlamasını bildirir. Buradaki encoding özniteliği belgenin işlenmesinde önemli rol oynayıp belgede kullanılan karakterlerin, belirlenmiş karakter kümesinin üyesi olmasını zorunlu kılar. Örneğimizde Türkçe karakter desteği için ISO değeri verildi. İlk satırda kullanılan version ve encoding öznitelikleriyle birlikte Papatya Yayıncılık Eğitim

199 XML Nedir? 667 standalone özniteliği de kullanılır. Yes veya No değerleri alabilen bu nitelik, sözkonusu belgenin bir dış tanımlayıcı belgeyle ilişkili, ona bağlı olup olmadığını gösterir. XML kurallarının yazıldığı DTD dosyaları bu şekilde XML belgelerine bağlanılır. XML belgesi görsel olarak veri (data) ve veriyi tanımlayan etiket/element (tag) ve öznitelik (attribute) bileşenlerinden oluşur. Etiketler, örnekteki Musteri öğesi gibi alt-öğe veya AdSoyad öğesi gibi doğrudan veri içerebilir. Elementleri nitelendirmek için öznitelikler kullanılır. Örnekte ilk satırda version ve encoding nitelikleri kullanılmıştır. Öznitelikler daha çok filtreleme işlemleri için kullanılır. XML belgesi belirli kurallar dahilinde hiyerarşik olarak düzenlenir. Her XML belgesinin tüm kayıt kümesinin başlangıcını ve bitişini belirten bir kök (root) elementi vardır (Örnekteki Musteriler etiketi). Belgedeki diğer elementler (child element) bu kök elementin alt-elemanları olarak tanımlanır. XML belgesinde herhangi bir veri içermeyen elementlere boş element/öğe denilir. XML belgesi düzenlenirken açılan her element mutlaka açıldığı sıraya göre kapatılmalı ve özniteliklerin değerleri çift veya tek tırnak içinde yazılmalıdır; ayrıca XML elemanlarının büyük ve küçük harfe duyarlı özelliğine sahip olduğu gözönünde bulundurulmalıdır! Bir XML belgesinde <, >, & veya gibi etiketlemede kullanılan özel işaretlerin veri olarak kullanılması için CDATA alanı kullanılır. Yazım biçimi <![CDATA[... ]]> şeklinde olan bu kısım XML deki işaretleme ifadelerinin göz ardı edilmesini sağlar. XML dosyasının herhangi bir yerinde açıklama satırları eklenebilir. HTML de olduğu gibi XML belgesinde de açıklama metinleri, <! ile --> işaretleri arasına yazılır. XML belgelerinin işlenebilmesi için iyi oluşumlu (well formed) ve geçerli (valid) olması gerekir. Şu ana kadar XML formatına dair verilen yazım kuralları dahilinde oluşturulmuş XML belgesi iyi oluşumlu olarak tanımlanır. İyi oluşturulmamış XML belgesi XML işleyici tarafından yorumlanamaz; nitekim böyle bir belge İnternet Explorer ile görüntülenmek istendiğinde hata mesajıyla karşılaşılır. Örneğin aşağıdaki XML belgesinde kök element/öğe bulunmadığı için belge düzgün oluşturulmamış olarak değerlendirilir. <?xml version="1.0" encoding="iso "?> <Musteri> <MusteriId>1</MusteriId> <AdSoyad>Murat Tatlı</AdSoyad> </Musteri> <Musteri> <MusteriId>3</MusteriId> <AdSoyad>Zeynep Beril</AdSoyad> </Musteri> Bölüm 28

200 668 C# Programlama Dili Belgenin altıncı satırında hata verilir; çünkü kök element bulunmadığından Musteri isminde iki üst düzey elementi oluşturulmuş olur; oysa XML belgesinde sadece bir üst düzey element oluşturulabilir. XML belgelerinin taşıması gereken ikinci önemli özellik belgenin geçerliliğidir. XML in genişleyebilir özelliğinden dolayı aynı belgeye ait element ve öznitelikler üzerinde farklı düzenlemelerde bulunabilir. Özellikle bilgi dağıtımı esnasında sadece veriyi düzenlemek üzere belgeyi almış bir alıcının belgenin etiketleriyle oynaması, belgenin asıl sahibinin sisteminde işlenme sorununa neden olacaktır. Bu istenmeyen durumu engellemek için XML belgesinin bağlı olacağı bir gramer dosyası oluşturulur. Bu dosya, belgede kullanılan element ve öznitelik yapısı ve bunların tanımladığı verilerin veri türleri hakkında bilgiler içerir. Dosyada belirtilen kurallar kümesine uyan belgeler, geçerli ve doğru belge olarak nitelendirilir. XML belgelerinin geçerliliği, yine W3C tarafından geliştirilmiş olan DTD ve XML Schema standartları tarafından denetlenir. XML belgesine ait dilbilgisi kurallarının tanımlandığı bu yapılar, belgeyi paylaşan iki alıcı arasında bir standart oluşturmayı garanti eder XML ve DTD Kullanımı Belge türü bildirimi olarak adlandırılan DTD (Document Type Declaration), belgede hangi element ve özniteliklerin kullanılacağı, bunların sırasının nasıl olacağı, elementlerin kaç kez tekrarlanabileceği, hangi element veya özniteliklerin boş geçilebileceği gibi geçerlilik kurallarını içerir. DTD kuralları, XML yazım biçimine sahip olup belge içerisinde <!DOCTYPE> bloğunda ya XML belgesinin kendisinde ya da ayrı bir dosya olarak tanımlanır. Ayrıca bir XML belgesi birden fazla DTD kullanabilir. Papatya Yayıncılık Eğitim

201 XML Nedir? 669 <!DOCTYPE kök-element[ tanımlama elementleri ] Veya SYSTEM ifadesiyle ayrı bir dosya olarak tanımlanmış olan DTD dosyasının adresi verilir. Bu adres bir URL olabildiği gibi bir sistem dizini de olabilir. <!DOCTYPE kök-element SYSTEM "dtd dosyası"> XML belgesinin içeriğine göre o belgenin yapısını belirlemek üzere çeşitli DTD türleri kullanılır. Bunlardan birkaç tanesi şunlardır: Docbook-XML: Makale ve kitap gibi hiyerarşik yapıya sahip belgeler. MathML (Mathematical Markup Language), matematiksel ifade ve formül için kullanılır. HTML belgelerinde bazı matematiksel ifadeleri oluşturmak mümkün olmamaktadır. Böyle bir durumda MathML kullanılması ve bu dili yorumlayabilen tarayıcılarda istenilen biçim elde edilmiş olunur. Benzer şekilde kimya ile ilgili ifadeleri oluşturmak için de CML (Chemical Markup Language) kullanılır. RDF (Resource Description Framework), kaynak tanımlama çerçevesi belgenin fiziksel yapısından ziyade mantıksal yapısını düzenlemek için kullanılan bir standarttır. HTML in belge içeriğinden çok görüntüsüyle ilgili olmasından dolayı sadece HTML in kullanıldığı kaynaklar anlam bakımından eksik kalacaktır. Bu kaynakları anlamlandırmak, belge içeriğinin anlamsallığını oluşturmak ve Internet teki veri çöplüğünü temizlemek için RDF standardı geliştirildi. RDF, meta-veriyi şifrelemek ve yeniden kullanabilmek için kullanılır. SOAP (Simple Object Access Protocol), basit nesne erişim protokolü ortam ve dilden bağımsız bir iletişim protokolü olup dağıtık uygulamaların İnternet üzerinden veri alış-verişi yapmasını sağlar. SVG (Scalable Vector Graphics), ölçeklenebilir vektörel grafikler XML dilini kullanarak iki-boyutlu sabit veya hareketli vektörel grafik tanımlamakta kullanılır. XHTML (Extensible HyperText Markup Language), genişletilebilir yüksek metin işaretleme dili tarayıcılar arasında standart bir HTML yapısı oluşturmak ve HTML e XML den yetenekler kazandırmak amacıyla HTML 4.01'in XML versiyonu olarak geliştirildi. Oluşturulan belgenin hangi türden olduğu bu DTD tanımları kullanılarak belirtilir. İşlemciler de XML belgesinin belirlenmiş bir formata uygun olup olmadığını bu DTD ler aracılığıyla denetlenir. <!DOCTYPE svg:svg PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" " <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " Bölüm 28

202 670 C# Programlama Dili DTD belgesinde tanımlama amaçlı kullanılan temel deyimler şunlardır: Bu deyimleri ve bunlara ait ayrıntıları işleyelim DTD İçerisinde Elementlerin Tanımlanması DTD dosyasında XML belgesinin öğelerini tanımlamak için ELEMENT etiketi kullanılır. Element, alt elementler içerecekse veya mutlaka bir içeriğe sahip olacaksa parantez içerisinde gerekli tanımlamalar yapılır. Element bir değere sahip ise parantez içinde #PCDATA (Parsed Character Data) etiketi yer alır; alt-elementler varsa adları parantez içine virgülle ayrılarak yazılır. [Musteri.XML] <?xml version="1.0" encoding="iso "?> <!DOCTYPE Musteri [ <!ELEMENT Musteri (MusteriId,AdSoyad)> <!ELEMENT MusteriId (#PCDATA)> <!ELEMENT AdSoyad (#PCDATA)> ]> <Musteri> <MusteriId>2</MusteriId> <AdSoyad>Zeynep Beril</AdSoyad> </Musteri> DTD kuralları ayrı bir dosyada da tanımlanabilir. [Musteri.DTD] <!ELEMENT Musteri (MusteriId,AdSoyad)> <!ELEMENT MusteriId (#PCDATA)> <!ELEMENT AdSoyad (#PCDATA)> [Musteri.XML] <?xml version="1.0" encoding="iso "?> <!DOCTYPE Musteri SYSTEM "Musteri.dtd"> <Musteri> <MusteriId>2</MusteriId> <AdSoyad>Zeynep Beril</AdSoyad> </Musteri> Papatya Yayıncılık Eğitim

203 XML Nedir? 671 Yazılan DTD belgesini kontrol etmek veya XML belgesinin kurallara uygunluğunu denetlemek için XML Notepad ürünü veya.net Framework nesneleri kullanılabilir. XML içerisinde dört çeşit element/öğe tanımlanabilir: Empty (Boş): İçeriği olmayan boş öğedir. DTD içerisindeki kuralı EMPTY anahtar sözcüğüyle tanımlanır. <!ELEMENT element_adı EMPTY> XML içerisinde <img/> veya <img></img> şeklinde tanımlanabilir. Boş ö- ğelerin öznitelikleri olabilir. <img src="image1.jpg" /> <img src="image1.jpg"></img> Element Only (Yalnızca Öğe): Sadece alt-seviye eleman (child element) içeren öğelerdir. DTD belgesinde bu elementlerin kuralı yazılırken alt-elementler parantez içerisinde virgülle ayrılacak şekilde belirtilir. Örneğimizdeki Musteri elementi bu türden bir öğedir. <!ELEMENT Musteri (MusteriId,AdSoyad)> ifadesi Musteri öğesinde MusteriId ve AdSoyad elementlerinin bulunacağını gösterir. Alt-element içeren üst elementlerin tanımlamalarında +,, * gibi çeşitli o- peratörler kullanılır. Bu operatörler çoğul ve seçime bağlı elementlerin durumunu tanımlamak için kullanılır. Tanım Açıklama E? E alt-elemanı bir kez kullanılabilir veya hiç kullanılmayabilir. E+ E alt-elemanı en az bir kere kullanılmalı ve sonsuz kere kullanılabilir. E* E alt-elemanı istenildiği kadar kullanılabilir; hiç kullanılmayabilir de. E,D D elemanı E elemanından sonra görünmeli. E D E veya B den birisi görünebilir. () Parantez içindeki elemanları gruplar. Örneğin (E,D)+ ifadesi, E ve D nin ardışık olarak bir veya daha fazla kullanılabileceğini bildirir. <!ELEMENT Kitap(yayinevi,yazar+,editor+,cevirmen*,ozet?)> Bu ifadeye göre Kitap öğesinde yayinevi öğesi sadece birkez görünmeli; yazar ve editor öğeleri en az birkez görünmeli ancak daha fazla sayıda da görünebi- Bölüm 28

204 672 C# Programlama Dili lir. cevirmen öğesi belge içerisinde herhangi bir sayıda görünebilir. Son öğe olarak ozet elementi birkez kullanılabilir veya hiç kullanılmayabilir. Mixed (Karışık): Hem alt-seviye element/öğe hem de karakter türündeki veriyi içerebilen elementlerdir. DTD içerisinde bu şekilde bir element tanımlanırken parantez içerisinde #PCDATA simgesi ilk sırada olmalı ve seçim listesinin sonunda seçimlik olduğunu bildirmek için * simgesi kullanılmalıdır. <?xml version="1.0" encoding="iso "?> <!DOCTYPE Musteri [ <!ELEMENT Sehir (#PCDATA)> <!ELEMENT Semt (#PCDATA)> <!ELEMENT Musteri (#PCDATA Sehir Semt)*> ]> <Musteri> <Sehir> İstanbul </Sehir> <Semt> Üsküdar </Semt> Umut Mah. Yıldız Sok. No:18/2 </Musteri> ANY Element: Herhangi bir değer alabilen öğelerdir. Yani hem karakter türü veriyi hem de alt-seviye element içerebilir. <?xml version="1.0" encoding="iso "?> <!DOCTYPE Musteriler [ <!ELEMENT Musteriler (Musteri*)> <!ELEMENT Musteri (MusteriId, AdSoyad, Adres)> <!ELEMENT MusteriId (#PCDATA)> <!ELEMENT AdSoyad (#PCDATA)> <!ELEMENT Adres ANY> <!ELEMENT Sehir (#PCDATA)> <!ELEMENT Semt (#PCDATA)> ]> <Musteriler> <Musteri> <MusteriId> 1 </MusteriId> <AdSoyad> Mert Ekşi </AdSoyad> <Adres> <Sehir> İstanbul </Sehir> <Semt> Üsküdar </Semt> Umut Mah. Yıldız Sok. No:18/2 </Adres> </Musteri> <Musteri> <MusteriId> 2 </MusteriId> <AdSoyad> Zeynep Beril </AdSoyad> <Adres></Adres> </Musteri> </Musteriler> Örnekte ilk müşteri kaydı için Adres öğesi hem alt-element hem de metin-elementi içerirken, ikinci müşteri kaydında boş element olarak tanımlanmıştır. Papatya Yayıncılık Eğitim

205 XML Nedir? DTD İçerisinde Özniteliklerin Tanımlanması XML belgesinde öğelerin özelliklerini belirtmek için öznitelik kullanılır. Öznitelikler HTML de olduğu gibi öğenin açılış etiketi içerisinde yazılır. Aşağıdaki ifadede src özelliği img etiketinin bir özniteliği olarak tanımlanmıştır. <img src="image1.jpg"></img> Bir element için hangi özniteliklerin kullanılacağı DTD belgesinde kurala bağlanabilir. XML elemanın alabileceği öznitelikler ve bunların alabileceği değerler DTD içerisinde ATTLIST ifadesiyle belirtilir. <!ATTLIST element_adı attribute_adı attribute_türü default_değeri> Tanımlama cümlesindeki default_değeri ifadesi niteliğin değerinin nasıl tanımlanacağını belirtir; dört tür değer alır: #REQUIRED: Element içerisinde özniteliğin zorunlu olarak kullanılmasını belirtir. Aşağıdaki satırlarda Urun öğesinin alt üyeleri olan Ad ve Fiyat elementleri için karakter türünde (CDATA) özellikler tanımlanmıştır. <!ELEMENT Urun (Ad,Fiyat)> <!ELEMENT Ad (#PCDATA)> <!ATTLIST Ad urun_id CDATA #REQUIRED> <!ELEMENT Fiyat (#PCDATA)> <!ATTLIST Fiyat para_birimi (TRY USD EUR) #REQUIRED> Ad ve Fiyat öğelerinin özniteliklerinin zorunlu olmasından dolayı XML belgesini oluştururken bunları tanımlamamız gerekir. para_birimi niteliği için sunulan üç değerden birinin girilmesi gerekir. Bu kurallar doğrultusunda VS.NET içerisinde XML belgesini oluştururken gerekli yönlendirmeler yapılacaktır. Bölüm 28

206 674 C# Programlama Dili #IMPLIED: Öğeye ait özniteliğin zorunlu olmadığını, isteğe bağlı olduğunu bildirir. Fiyat etiketi için seçmeli indirimli_fiyat niteliğini tanımlayalım. <!ELEMENT Fiyat (#PCDATA)> <!--Zorunlu attribute--> <!ATTLIST Fiyat para_birimi (TRY USD EUR) #REQUIRED> <!--Opsiyonel attribute--> <!ATTLIST Fiyat indirimli_fiyat CDATA #IMPLIED> #FIXED Değer: Özniteliğin belirlenmiş olan değere sabitlendiğini bildirir. XML belgesinde element için bu öznitelik kullanılmazsa bu sabit değer referans alınır. Özniteliğe belirlenmiş olan sabit değer dışında bir değer atanırsa işlemci hata verecektir. <!ELEMENT Firma (#PCDATA)> <!ATTLIST Firma firma_kod CDATA #FIXED "FRM01"> Firma etiketinin firma_kod özelliği ancak FRM01 değerini alabilir. Başka bir değer girmeye çalıştığımızda XML çözümleyici (parser) bizi uyaracaktır. Değer: Öznitelik için varsayılan değer tanımlamak için kullanılır. Kullanıcı herhangi bir değer girebilir. Eğer bu öznitelik tanımlı olmazsa XML işlemcisi bu varsayılan değeri referans alır. <!ATTLIST Fiyat para_birimi CDATA "TL"> para_birimi niteliği için varsayılan olarak TL değeri tanımlanmıştır. Papatya Yayıncılık Eğitim

207 XML Nedir? DTD İçerisinde Varlıkların Tanımlanması Varlıklar (Entity), bir veri parçasına isim verilerek XML belgesinde bu veri parçasına referans verilmesini sağlar. DTD içerisinde ENTITY ifadesiyle tanımlanan varlıklar genel ve parametre olmak üzere iki çeşittir. Genel varlıklar XML belgesinde kullanılabilirken, parametre varlıklar yalnızca DTD belgesinde kullanılabilir. Varlık olarak tanımlanan veri parçası bir karakter de olabilir. Aşağıdaki ifadede bir Sirket isminde bir genel varlık ve bu varlığın referans gösterdiği ifade tanımlanmıştır. <!ENTITY Sirket "ABC yazılım evi"> Bu varlık XML belgesinde &Sirket; şeklinde kullanılır. Böylece XML belgesinde şirket ismi parametrik yapılmış oldu. İhtiyaç duyulduğu yerde şirket adını yazmak yerine &Sirket; kısaltmasını çağırmak yeterli olacaktır. Aynı şekilde referans olarak bir dosyanın içeriği de gösterilebilir. Dış varlık (external entity) olarak tanımlanan bu türde işaret edilecek dosyasının konumu SYSTEM ifadesiyle verilmelidir. <!ENTITY aciklama SYSTEM " XML belgesinde bu dosyasının içeriğini göstermemiz için &aciklama; kısaltmasını kullanmamız yeterli olacaktır. Parametre varlıklar % işaretiyle tanımlanır. Yalnızca DTD belgesinde kullanabileceğimiz bu varlıklar %varlık_adı; olarak kullanılır. DTD belgelerinde kullanacağımız diğer etiket NOTATION ifadesidir; bu ifade resim ve ses dosyaları gibi XML dışı içeriğin tanımlanmasını sağlar. Bu içerik, işlemci tarafından XML belgesinin bir parçası olarak işlenmez. Bu verileri ancak bu veri formatlarıyla ilişkili uygulamalar işleyebilir. Burada egitim_video isimli varlık için XML işlemcisinin işleyemeyeceği bir dosya tanımlanmıştır. <!NOTATION video SYSTEM "video/avi"> <!ENTITY egitim_video SYSTEM " NDATA video> Bu dosyanın bulunan ortamda ilgili uygulama tarafından işlenebilmesi için dosyasının MIME türü girilmiştir. Buradaki NDATA (Notation Data) ifadesi, bu içeriğinin çözümlenebilir bir veri olmadığını belirtir. Bölüm 28

208 676 C# Programlama Dili XML İsim-uzayı İki ayrı XML belgesinde aynı isimli öğeler bulunabilir. Eğer bu iki XML belgesi aynı belge içerisinde kullanmak istenilirse isim çakışmasıyla karşılaşılır. Sorun teşkil eden bu çakışmayı önlemek için isim-uzayı kullanılır. XML belgelerinde XML ad alanları kullanılarak amacı farklı ancak adları aynı öğelerin aynı belge içinde kullanılabilmesi sağlanır. Örneğin <TABLE> etiketinin bir kopyası satırları ve sütunları olan bir veri kaynağına başvuru yapabilirken diğer bir <TABLE> etiketi kopyası dört ayaklı bir mobilyaya başvuru yapabilir. XML belgesinde ad alanları xmlns ifadesiyle tanımlanır ve değer olarak URL veya URN gibi adres değerleri alır. Bunun nedeni XML belgeleri İnternet üzerinde farklı sistemler tarafından paylaşıldığından belgedeki isim-uzaylarını tekilleştirmektir. Dünyada aynı URL den iki tane olmadığı için belge içerisindeki öğeler için tekil uzaylar oluşturulmuş olur. Ad alanı URL'si yalnızca diğer bir ad alanı URL'sinden farklı olması için seçilen bir metindir. Şema belgesi veya XML işlemcisi tarafından geçerliliği denetlenmez. İki tane Ogrenci.xml dosyasının olduğunu düşünelim. İlk dosya yazılım eğitimi veren bir kuruma ait verileri içeriyor. <Ogrenci> <Numara>1</Numara> <AdSoyad>Mert Şensoy</AdSoyad> <Dil>C#</Dil> </Ogrenci> Buradaki Numara öğesi sistemin verdiği sıralı bir değerdir. Dil öğesi o öğrencinin hangi programlama dili eğitimini aldığını bildirmektedir. İkinci dosya ise yabancı dil eğitimi veren bir kuruma aittir. <Ogrenci> <Numara> </Numara> <AdSoyad>Ayşe Şen</AdSoyad> <Dil>İngilizce</Dil> </Ogrenci> Buradaki Numara öğesi öğrenciye ait kimlik numarasını tutmaktadır. Dil öğesi o öğrencinin hangi yabancı dil eğitimi aldığını belirtmektedir. Görüldüğü gibi aynı öğeler farklı belgelerde farklı anlamlara gelmektedir. Bunları aynı xml belgesinde kullandığımızda isim çakışmasını engellemek için isim-uzayı kullanılır. <Belge> <Ogrenci xmlns=" <Numara>1</Numara> <AdSoyad>Mert Şensoy</AdSoyad> <Dil>C#</Dil> </Ogrenci> <Ogrenci xmlns=" <Numara> </Numara> <AdSoyad>Ayşe Şen</AdSoyad> <Dil>İngilizce</Dil> </Ogrenci> </Belge> Papatya Yayıncılık Eğitim

209 XML Nedir? 677 Böylece belgede birbirleriyle çakışmayacak iki element kümesi oluşturulmuş oldu. Belgede isim-uzayı olarak verilmiş değer uzun olduğu için bunu temsil edecek bir ön-ek kullanılır. Oluşturulan öğenin hangi isim-uzayına dahil olduğu bu ön-ek ile önek:element şeklinde belirtilir. <Belge> <sr:ogrenci xmlns:sr=" <sr:numara>1</sr:numara> <sr:adsoyad>mert Şensoy</sr:AdSoyad> <sr:dil>c#</sr:dil> </sr:ogrenci> <yd:ogrenci xmlns:yd=" <yd:numara> </yd:numara> <yd:adsoyad>ayşe Şen</yd:AdSoyad> <yd:dil>ingilizce</yd:dil> </yd:ogrenci> </Belge> Böylece hangi öğenin hangi isim-uzayına ait olduğunu ayrı ayrı belirtmiş olduk XML Şeması XML belgelerinin denetlenmesi ve doğrulanması için kullanılan diğer yöntem de XML şema tanımlamasıdır (XML Schema Definition-XSD). DTD nin ifade ve veri türleri konusunda yetersiz kalması nedeniyle W3C tarafından yayınlanmış olan XML şema standardı DTD ye oranla daha zengin veri türlerini içerir. Kendileri de birer XML belgesi olan XML şema belgeleri de bir metin editörüyle yazılabildiği gibi VS.NET tarafından da kolaylıkla oluşturulabilir. XSD dili XML belgelerin yapılarını tanımlayan düzinelerce tanım ve bildiri komutu içerir. Burada bunların hepsine değinmeyeceğiz. Sadece şema belgesinin genel taslağının nasıl oluşturulacağını öğreneceğiz. Standart bir şema belgesinin formatı şu şekildedir: [Musteri.xsd] <?xml version="1.0"?> <xs:schema xmlns:xs=" targetnamespace=" xmlns=" </xs:schema> Şema belgesinin ilk satırında W3C isim-uzayı kullanılması zorunludur. Genel olarak iki isim-uzayı tanımı yapılır: Birisi şema isim-uzayı, diğeri de XML belgesinin varsayılan isim-uzayıdır. Örnek olarak verdiğimiz şema (.xsd) dosyasında xs öneki W3C ad alanıyla ilişkilendirilmiştir. Bu belgede xs: ön-eki, W3C XML şema tanım dili önerisiyle tanımlanan deyimlere gönderme yapar. Yani belgede kullanılacak veri türü, element, öznitelik gibi öğelerin, alanından geldiğini bildirir. xmlns= ise XML belge- Bölüm 28

210 678 C# Programlama Dili sinin varsayılan ad alanını belirtir. Şemanın hangi isim-uzayı altında olduğu targetnamespace niteliği altında belirtilir. XML belgesini bir şemaya bağlamak için belgedeki kök elementin isim-uzayı alanında şemanın adresi verilir. Şemayı, bulunduğu isim-uzayıyla birlikte göstermek için schemalocation niteliği kullanılır. [Musteri.xml] <Musteriler xmlns=" xmlns:xsi=" xsi:schemalocation=" Musteri.xsd">... </Musteriler> Herhangi bir ad alanına (targetnamespace) bağlı olmayan şemayı kullanmak için nonamespaceschemalocation niteliği kullanılır. [Musteri.xsd] <?xml version="1.0"?> <xs:schema xmlns:xs=" xmlns=" </xs:schema> [Musteri.xml] <Musteriler xmlns=" xmlns:xsi=" xsi: nonamespaceschemalocation="musteri.xsd">... </Musteriler> Şemada Element ve Öznitelik Tanımlama XML belgesinde olduğu gibi XML şemasında da element ve onları nitelendiren öznitelikler kullanılır. Elementler tanımlanırken adı ve türü de belirtilir. Aşağıdaki satırda sözce/string türünde AdSoyad elementi tanımlanmıştır. <xs:element name="adsoyad" type="xs:string" /> Aynı şekilde bir öznitelik oluşturmak için de <xs:attribute> ifadesi kullanılır. Aşağıdaki satırda Byte türünde MusteriId özniteliği tanımlanmıştır. <xs:attribute name="musteriid" type="xs:unsignedbyte" /> Aynı element veya özniteliği belge içerisinde bir çok yerde kullanmak için ref niteliği kullanılır. Biraz önce tanımlanan AdSoyad elementini veya MusteriId özniteliğini başka bir yerde kullanmak için aşağıdaki satırlar yazılır. <xs:element ref="adsoyad" /> Öznitelikler varsayılan olarak seçmelidir; yani tanımlanıp tanımlanmaması isteğe bağlıdır. Bir özniteliğin alacağı değerin isteğe bağlı ve zorunlu olup olmaması use Papatya Yayıncılık Eğitim

211 XML Nedir? 679 niteliğiyle belirtilir. Değer olarak seçmeli (optional) ve gerekli (required) seçeneklerinden birini alır. XML şema belgesinde en çok kullanılan veri türleri şunlardır: Sözce / string Ondalık / decimal Tamsayı ve türevleri / integer Mantıksal / boolean Tarih / date (gyear, gmonth, gyear, gyearmonth) Zaman / time Bu veri türlerinin dışında kendi oluşturduğumuz veri türlerimizi de kullanabiliriz. XML şemasında basit ve karmaşık olmak üzere iki tür element tanımlanır. Basit tür elementler sadece veri içeren alt-element veya öznitelik içermeyen elemanlardır. Bu tür elementler <xs:simpletype> ifadesiyle veya yukarıda örnek olarak gösterdiğimiz gibi tek satırda tanımlanır. Karmaşık tip elementler alt-element veya öznitelik içeren elemanlardır. Bu tür elementler <xs:complextype> ifadesiyle tanımlanır. Karmaşık elementlerin içereceği alt-elementlerin belirli sıraya göre tanımlanması için <xs:sequence> belirteci kullanılır. Şimdiye kadar öğrendiğimiz deyimleri kullanarak aşağıdaki XML belgesi için bir şema dosyası oluşturalım. [Musteri.xml] <?xml version="1.0" encoding="iso "?> <Musteriler> <Musteri MusteriId="1"> <AdSoyad>Murat Tatlı</AdSoyad> <Sehir>Ankara</Sehir> </Musteri> </Musteriler> Bu XML yapısını tanımlayacak XML şemasını yazalım. Belgede görüldüğü gibi Musteriler isminde bir kök element bulunmaktadır. Bu element Musteri isimli alt-elementlerden oluşmaktadır. Musteri elementinin tamsayı türünde MusteriId özniteliği ve sözce/string türünde AdSoyad ve Sehir alt-elementleri bulunmaktadır. MusteriId özniteliği seçmeli değil gerekli bir alan olarak tanımlanacak. Şema bilgilerinin daha açık okunması ve anlaşılması için öğeleri içiçe değil ayrı ayrı tanımlayacağız. [Musteri.xsd] <?xml version="1.0" encoding="iso "?> <xs:schema xmlns:xs=" <!-- Basit elementleri tanımlayalım --> <xs:element name="adsoyad" type="xs:string"/> <xs:element name="sehir" type="xs:string"/> <!-- Öznitelikleri tanımlayalım --> <xs:attribute name="musteriid" type="xs:integer"/> <!-- Kompleks elementleri tanımlayalım --> <xs:element name="musteri"> <xs:complextype> Bölüm 28

212 680 C# Programlama Dili <xs:sequence> <xs:element ref="adsoyad"/> <xs:element ref="sehir"/> </xs:sequence> <!--Zorunlu MusteriId niteliği--> <xs:attribute ref="musteriid" use="required"/> </xs:complextype> </xs:element> <xs:element name="musteriler"> <xs:complextype> <xs:sequence> <xs:element ref="musteri"/> </xs:sequence> </xs:complextype> </xs:element> </xs:schema> Musteri.xml dosyasını bu şema dosyasıyla ilişkilendirelim. [Musteri.xml] <?xml version="1.0" encoding="iso "?> <Musteriler xmlns:xsi=" xsi:nonamespaceschemalocation="musteri.xsd"> <Musteri MusteriId="1"> <AdSoyad>Murat Tatlı</AdSoyad> <Sehir>Ankara</Sehir> </Musteri> </Musteriler> Böylece bu XML belgesi üzerinde diğer uygulamaların yapacağı değişikliklerin hedeflenen yapıya uygun olup olmadığı denetlenmiş olur. Nitekim VS.NET gibi bir araç içerisinde şemada belirtilmiş kurallara aykırı bir işlem yaptığımızda ilgili uyarıyla karşılaşırız. XML belgesinde MusteriId özniteliğine sözce/string türünde bir değer girmeye çalışalım. XML şema dilinde yüzlerce deyim olduğu için bunların bu şekilde elle yazılması kolay olmayacaktır. Bu noktada VS.NET önemli bir XML tasarım aracı sunmaktadır. Bu araç aracılığıyla XML belgesi ve ona ait şema daha kolay oluşturulabilir. XML belgesini açıp üst menüden Create Schema bölümünü kullanarak şemanın VS.NET tarafından oluşturulmasını sağlayabiliriz. Papatya Yayıncılık Eğitim

213 XML Nedir? 681 Bu durumda aşağıdaki gibi bir şema belgesi oluşturulmuş olur: <?xml version="1.0" encoding="iso "?> <xs:schema attributeformdefault="unqualified" elementformdefault="qualified" xmlns:xs=" <xs:element name="musteriler"> <xs:complextype> <xs:sequence> <xs:element name="musteri"> <xs:complextype> <xs:sequence> <xs:element name="adsoyad" type="xs:string" /> </xs:sequence> <xs:attribute name="musteriid" type="xs:integer" use="required"/> </xs:complextype> </xs:element> </xs:sequence> </xs:complextype> </xs:element> </xs:schema> XSD dosyasının içerisinde sağ tıklayıp View Designer menüsünü kullanarak şemayı grafiksel ortamda da tasarlayabiliriz. Bu grafik ortamında şemaya element ve öznitelik eklenebilir; ve bunlarla ilgili kurallar düzenlenebilir Kullanıcı Tanımlı Veri Türleri XML şema dilinde basit ve karmaşık olmak üzere iki tür veri tanımlayabiliriz. Basit veri türleri sadece metin içeren türler olup <xs:simpletype> ifadesiyle oluşturulur. Karmaşık veri türleri salt metin dışında alt öğeler içeren türler olup <xs:complextype> ifadesiyle oluşturulur. Kullanıcı tanımlı veri türü oluşturulurken temel veri türlerinden yararlanılır. Bu türler üzerinde sınırlandırmalar yapılarak kendimize özgü yeni türler oluşturabiliriz. Bu süreçte kullanılan kısıtlama ifadeleri şunlardır: enumeration length maxlength minlength enumeration pattern whitespac Bölüm 28

214 682 C# Programlama Dili Veri türünde herhangi bir kısıtlama ifadesi kullanmak için <xs:restriction> öğesi kullanılır. Bu öğe için base özniteliği kullanılarak bu yeni türün temelde hangi XML şema veri türünü kullandığı belirtilir. Enumeration Bir değer listesi tanımlayıp o element için girilecek verinin listedeki seçeneklerden biri olması gerektiğini belirtir. Aşağıdaki tabloda Erkek ve Bayan değerlerini alabilecek Cinsiyet türü tanımlanmıştır. <xs:simpletype name="cinsiyet"> <!--Girilecek değer, restriction bloğunda belirlenmiş kurallarla kısıtlanır --> <!--Değer string türünde olacak--> <xs:restriction base="xs:string"> <xs:enumeration value="erkek"/> <xs:enumeration value="bayan"/> </xs:restriction> </xs:simpletype> Bu türü aşağıdaki gibi kullanabiliriz: <xs:element name="cinsiyeti" type="cinsiyet" /> VS.NET içerisinde bu şemayı kullanan XML belgesinde Cinsiyeti öğesini tanımlarken ilgili seçenekler listelenir. Uzunluk (Length) Verinin uzunluğunu belirtmek için kullanılır. Uzunluğu 8 karakter olmak zorunda olan sözce/string türünde Sifre isimli veri türü aşağıdaki gibi oluşturulur: <xs:simpletype name="sifre"> <xs:restriction base="xs:string"> <xs:length value="8" /> </xs:restriction> </xs:simpletype> En-büyük Uzunluk (maxlength) ve En-küçük Uzunluk (minlength) maxlength metin veya listelerin maksimum uzunluğunu; minlength metin veya listelerin minimum uzunluğunu özelliğini belirtir. <xs:simpletype name="sifre"> <xs:restriction base="xs:string"> <xs:minlength value="5" /> <xs:maxlength value="10" /> Papatya Yayıncılık Eğitim

215 XML Nedir? 683 </xs:restriction> </xs:simpletype> Sifre türündeki değerin uzunluğu en az 5 en fazla 10 karakter olabilir. Ondalık Hane Sayısı Belirteci (fractiondigits ve totaldigits) Ondalık sayı türleri için kullanılan bu iki nitelik sayılardaki hane uzunluğunu sınırlamak için kullanılır. fractiondigits, sayının ondalık kısmının hane sayısını, totaldigits ise tüm sayının nokta dahil edilmeden hane sayısını belirtir. Alt-üst Değer Sınırlaması-Sınırlar Dahil (mininclusive ve maxinclusive) Bu iki nitelik sayısal türü veri türlerinde alt ve üst sınırları belirlemek üzere kullanılır. Aşağıdaki veri türünde girilecek değerin 1000 ile 9000 arasında olması gerektiği ve ondalık kısmının en fazla 2 hane olabileceği bildirilmiştir. Ayrıca değerin toplam basamağı da en fazla 6 hane olabilir. <xs:simpletype name="maas"> <xs:restriction base="xs:decimal"> <xs:mininclusive value="1000"/> <xs:maxinclusive value="9000"/> <xs:fractiondigits value="2"/> <xs:totaldigits value="6"/> </xs:restriction> </xs:simpletype> Bu veri türünü aşağıdaki gibi bir değer için kullanabiliriz: <xs:element name="ucret" type="maas" />... <Ucret> </Ucret> Alt-üst Değer Sınırlaması-Sınırlar Hariç (minexclusive ve maxexclusive) mininclusive ve maxinclusive nitelikleri gibi sayısal türü veri türlerinde alt ve üst sınırları belirlemek için kullanılırlar. Onlardan farkı, alt ve üst sınır dahil edilmez. Yani önceki örnekte değer 1000 veya 9000 olabilirken minexclusive ve maxexclusive ile yapılacak bir tanımlamada 1000 ve 9000 dahil edilmez. Boşul Değerlendirme (whitespace) XML belge işleyicinin eleman içindeki boşlukları (alfabe dışı karakter-white space) nasıl yorumlaması gerektiğini bildirir. Aşağıdaki değerleri alır: Korunsun (preserve): Boşlukların olduğu gibi korunmasını sağlar. Boşluklar üzerinde değişiklik yapılmaz. Yerdeğişsin (replace): Satır sonu, tab ve satır başları gibi karakterler boşluk karakteriyle değiştirilir. Birleştir (collapse): Tekrar eden boşluk karakterlerini tek bir boşluğa dönüştürür. Bölüm 28

216 684 C# Programlama Dili Desen (pattern) XML şema dilinin en önemli özniteliklerinden biri olan pattern ifadesi sözce/string türündeki alanlara girilecek değerin uyması gereken formatı belirtir. Aşağıdaki satırlarda KucukHarf isimli veri türü için kısıtlama tanımlanmıştır. Bu kısıtlamaya göre KucukHarf türündeki bir element için a-z aralığında tek bir harf girilebilir. <xs:simpletype name="kucukharf"> <xs:restriction base="xs:string"> <xs:pattern value="[a-z]" /> </xs:restriction> </xs:simpletype> Belgeye girilecek sözce/string tabanlı değerler için örnektekine benzer giriş kalıpları hazırlamak için aşağıdaki karakterler kullanılır. [abc] (abc){x a b c [a-z] \d{x Parantez içindeki değerlerden yalnızca birini kabul eder. Parantez içindeki değerin X kadar tekrarlaması gerekir. Zorunlu seçim yaptırır; yani değer olarak a, b veya c değeri girilebilir. Daha önce enumeration ifadesiyle kısıtlama yaptığımız Cinsiyet veri türü bu şekilde pattern ifadesiyle kısıtlanabilir. Aralık bildirir. [0-9] ifadesi 0-9 arası bir rakamı, [A-Z] ifadesi A-Z arası bir harfi kabul eder. \d ifadesi rakamları temsil eder. Değeri X tane rakam olarak kısıtlar. +, *,? Daha önce DTD de anlatılan bu karakterler XML şemada da aynı amaçla kullanılır. * karakteri, değerin 0 veya sınırsız sayıda kullanılabileceğini, + karakteri, değerin 1 veya sınırsız sayıda kullanılabileceğini,? karakteri, değerin 0 ya da 1 kez kullanılabileceğini belirtir. Bu karakterleri örnekler içinde kullanalım. [a-z]{3-\d{4 [A-Z][a-zA-Z] ([a-z])* ([a-z][a-z])+ [A-Za-z_]{5,10 a-z arası 3 harf, sonra tire karakteri ve sonra 4 rakam. 2 harften oluşması gereken bu değer ilk harfi A-Z aralığında, ikinci harfi A-Z veya a-z aralığında olmalıdır. a-z aralığında 0 veya sınırsız sayıda küçük harften oluşabilir. İlki küçük, ikincisi büyük harf olacak şekilde 1 ya da daha fazla harften oluşabilir. Buna göre dotnet ifadesi kabul edilebilirken DOtNeT ifadesi kabul edilmez. Değerin 5-10 arası karakterden oluşabileceğini bildirir. Bu karakterler yalnızca büyük veya küçük harf veya alt tire olabilir. Papatya Yayıncılık Eğitim

217 XML Nedir? 685 Aşağıdaki şema satırında Telefon elementi için giriş formatı tanımlanmıştır. Bu formata göre girilecek değer (212) şeklinde olmalıdır. <xs:element name="telefon"> <xs:simpletype> <xs:restriction base="xs:string"> <xs:pattern value="[(]\d{3[)]\d{3 \d{2 \d{2"/> </xs:restriction> </xs:simpletype> </xs:element> Şimdiye kadar oluşturulan türler basit veri türleriydi; yani yalnızca metinlerden oluşabilen elementler için geçerliydi. Aynı şekilde karmaşık veri türleri de tanımlayabiliriz. Karmaşık veri türleri başka alt elementleri veya öznitelikleri içeren türlerdir. Bu türe sahip elementler boş olabildiği gibi sadece metin de içerebilir veya metin ile birlikte alt element de içerebilir. Aşağıdaki tabloda bileşik veri türüne sahip AdSoyad elementi tanımlanmıştır. <xs:element name="adsoyad"> <xs:complextype> <xs:sequence> <xs:element name="ad" type="xs:string"/> <xs:element name="soyad" type="xs:string"/> </xs:sequence> </xs:complextype> </xs:element> Bu öğeyi aşağıdaki gibi kullanabiliriz: <AdSoyad> <Ad>Ahmet</Ad> <Soyad>Kaymaz</Soyad> </AdSoyad> XML Şema Belirteçleri (Adverb) XML şema dilinde öğelerin yerleşim düzenlerini, kullanım sıklıklarını ve element veya öznitelik grupları belirlemek üzerde aşağıdaki belirteçler kullanılır: Sıra (Order) belirteçleri: Öğelerin sırasını belirlemek için kullanılır. Hepsi (All): Alt-öğelerin herhangi bir sırada ve bir kez kullanılabileceğini belirtir. Seçim (Choice): Alt-öğelerin sadece birisi kullanılabilir. Sıralanım (Sequence): Alt-öğelerin tanımlanan sıraya göre oluşturulmasını belirtir. İçerme (Occurrence) belirteçleri: Öğelerin XML belgesi içerisinde kaç kere kullanılabileceğini belirtir. En az içerme (minoccurs): Öğenin en az kaç kere kullanılabileceğini belirtir. En fazla içerme (maxoccurs): Öğenin en fazla kaç kere kullanılabileceğini belirtir. Alt-öğenin sınırsız kullanılabilmesi için maxoccurs="unbounded" değeri girilmelidir. Bölüm 28

218 686 C# Programlama Dili Aşağıdaki şema tanımlamasına göre XML belgesi Musteriler isimli üst-element içeriyor. Bu üst-element sınırsız sayıda Musteri isminde alt-element içerebilir. Aynı şekilde Musteri elementi de yalnızca 1 adet AdSoyad elementi içerebilir. <xs:element name="musteriler"> <xs:complextype> <xs:sequence> <xs:element maxoccurs="unbounded" name="musteri"> <xs:complextype> <xs:sequence> <xs:element name="adsoyad" type="xs:string" /> </xs:sequence> </xs:complextype> </xs:element> </xs:sequence> </xs:complextype> Grup (Group) belirteçleri: Öğeleri ve öznitelikleri gruplandırmak için kullanılır. Gruplandırma işlemi daha çok bir kere tanımlanıp şema içerisinde birden fazla yerde referans vermek için kullanılır. Grup (Group): Öğeleri gruplandırır. Öznitelik gruplama (Attributegroup): Öznitelikleri gruplandırır. Aşağıdaki örnekte Musteri öğesinin oluşacağı temel alt-elementler gruplandırılmıştır. <xs:group name="musterigrup"> <xs:sequence> <xs:element name="adsoyad" type="xs:string"/> <xs:element name="sehir" type="xs:string"/> <xs:element name="dogumtarih" type="xs:date"/> </xs:sequence> </xs:group>... <xs:element name="musteri"> <xs:complextype> <xs:sequence> <xs:group ref="musterigrup"/> <xs:element name="aciklama" type="xs:string"/> </xs:sequence> </xs:complextype> </xs:element> Aynı şekilde öğe için girilecek öznitelikleri de gruplandırabiliriz. <xs:attributegroup name="guvenlikbilgi"> <xs:attribute name="kullaniciadi" type="xs:integer"/> <xs:attribute name="sifre" type="xs:string"/> </xs:attributegroup>... <xs:element name="musteri"> <xs:complextype> <xs:sequence> <xs:group ref="musterigrup"/> <xs:element name="aciklama" type="xs:string"/> </xs:sequence> Papatya Yayıncılık Eğitim

219 XML Nedir? 687 <xs:attributegroup ref="guvenlikbilgi"/> <xs:attribute name="musteriid" type="xs:unsignedbyte"/> </xs:complextype> </xs:element> Kullanıcı tanımlı veri türlerini oluştururken VS.NET in içindeki şema tasarım (schema designer) aracı büyük kolaylık sağlar. Bu grafiksel ortamda kendi veri türlerimizi yaratabilir ve bu türlerden elementler oluşturabiliriz. Sonuç olarak DTD veya XML şema yapıları kullanılarak XML belgesindeki verilerin belirlenmiş kurallar doğrultusunda girilmesini sağlayabiliriz. Böylece aynı XML belgesini paylaşan uygulamalardan birinin yaptığı değişiklik diğerinin doğru çalışmasını engellememiş olur. Sonuçta iki uygulama arasında işlerlik kazandırılmış olur. Bölüm 28

220 688 C# Programlama Dili XML Biçimlendirme Dili (XSL) XML belgeleri Web tarayıcılarında açıldığında okunaklı görünmeyecektir. Çünkü XML belgelerinin temel görevi belirli standartlar ölçüsünde veri barındırmaktır. Verilerin dağıtımı ve biçimi ile ilgilenmez. Bu işlemler için başka yan diller kullanılır. XML belgelerini görsel olarak biçimlenlendirmek için CSS (Cascading Style Sheets) veya XSL (Extensible Style Language) gibi biçimlendirme dilleri kullanılır. CSS, HTML belgelerinde metin ve ona ait formatı ayrı olarak belirtmek amacıyla geliştirilmiş bir Web teknolojisidir. Aynı şekilde XML belgelerinde de aynı amaç için kullanılır. XSL ise CSS den daha yetenekli bir dil olup sadece XML belgelerini biçimlendirme ve dönüştürme üzere geliştirilmiştir. XSL iki ayrı teknolojiyi barındırır. XSLT (XSL Transformations) ve XSL-FO (XSL Formatting Objects). XSLT, XML belgelerini asıl yapısını değiştirmeden başka XML belgelerine (XML, HTML, CSV) dönüştürmek için kullanılan XML tabanlı bir dildir. XSL-FO ise XML belgelerinin Web tarayıcılardaki görünümlerini düzenlemek için kullanılır. Yani XSLT belgenin şekliyle XSL-FO ise görsel olarak nasıl görüneceğiyle ilgili alandır. XSL-FO henüz standart bir teknoloji olmadığı için pek yaygın kullanılmamaktadır. Bir XSL dosyası XML belgesiyle aşağıdaki tanımlamayla ilişkilendirilir: <?xml version="1.0"?> <?xml-stylesheet type="text/xsl" href="bicimdosya.xsl"?> <Kök ELEMENT="">... </Kök ELEMENT> XML Adresleme Dili (XPath) XPath, bir XML belgenin parçalarını adreslemek için XSLT ve XPointer tarafından kullanılmak üzere tasarlanmış bir dildir. W3C tarafından standart haline getirilmiş olan XPath dili, XML belgesi içerisindeki yuvalanmış düğümleri bulmak ve onlara ait özellikleri ve değerleri sorgulamak için kullanılır. Nitekim, XSLT ile dönüşüm işlemleri gerçekleştirilirken hangi düğüm veya verilerin işleme dahil edilip edilmeyeceği XPath sorgularıyla belirlenir. XPath, XML belgesini düğümlerden oluşan bir ağaç olarak modeller. Eleman düğümleri öznitelik düğümleri ve metin düğümleri gibi farklı düğüm türleri vardır. XPath, yol (path) ifadeleri aracılığıyla bu XML düğümlerine konumlanmamızı ve onlar arasında dolaşmamızı sağlar. Bu işlemleri yaparken DOM alt yapısını kullanır. Yani XML belgesi, bellekte ağaç veri modeliyle tutulur. XPath teknolojisindeki yol kavramı bu ağaçta bir düğüme konumlanmak için tepe noktasından o düğüme ulaşmak için takip edilecek düğümler listesi anlamına gelir. XPath adresleme sisteminde mutlak ve bağıl olmak üzere iki yöntem kullanılır. Mutlak yol bir düğümün yolunun tam olarak belirtilmesi, bağıl yol ise düğüm yolunun düğümün çağrıldı- Papatya Yayıncılık Eğitim

221 XML Nedir? 689 ğı düzeye göre belirtilmesidir. Bu yöntemleri HTML sayfalarında kullanılan dosya adreslerine benzetebiliriz. Örneğin gibi bir adres xpath.pdf in tam yolunu belirtir. Bu dosya kitap klasörünün altındayken /xml/xpath.pdf şeklinde de adreslenebilir. Buda xpath.pdf dosyasının bağlı yolunu belirtmiş olur. XPath te adres tanımlaması yapılırken düğüm kırılımları / ile ifade edilir. Bir düğümün adresi / ile başlıyorsa kök öğeye,../ ile başlıyorsa bir üst öğeye işaret edilir. Örneğin kök öğenin hemen altında bulunan Musteri öğesine erişmek /Musteri tanımlaması yapılır. XPath dilinin çeşitli fonksiyon ve operatörleri bulunur. Aşağıdaki tabloda farklı sorgu ifadeleri oluşturulmuştur: XPath Sorgusu Urunler/Urun Urunler/Urun/@Id Urunler/Urun[Marka] Urunler/* Urunler/Urun[@*] Açıklama Urunler öğesinin altındaki Urun elementlerini alt elementleriyle birlikte yakalar. Urunler öğesinin altındaki Urun elementlerinin içerdiği Id niteliklerini yakalar. Marka elementine sahip ürünleri seçer. Urunler altındaki tüm elementleri seçer. Herhangi bir niteliğe sahip ürünleri seçer Urunler/Urun[@Id=25] Urunler elementinin altında bulunan Id niteliği 25 olan Irım elementleri yakalar. /Urunler/Urun[@Id=25]/@MarkaId Urunler/Urun[Adet<10] Urunler//Aciklama./@MarkaId Urunler/Urun[2] Urunler/[last()] count(/urunler/urun) count(/urunler/urun[@markaid=5]) sum(/urunler/urun/adet) sum(urunler/urun/adet[.<10]) /Urunler/*/[contains(@Ad,"Beyaz")] Id si 25 olan ürünün MarkaId bilgisini döndürür. Urunler elementinin altındaki Urun elementlerinden Adet elementi 10 dan küçük olanları seçer. Herhangi bir düzeydeki Aciklama öğelerini seçer. Aşağıdaki düğümler bu sorgu tarafından yakalanır: Urunler/Aciklama, Urunler/Urun/Aciklama Urunler/Marka/Aciklama Urunler/Urun/Yorum/Aciklama. operatörü seçili düğümü temsil eder. Bu ifade mevcut düğümün MarkaId niteliğini seçer. İkinci sıradaki Urun elementini seçer. Urunler elementinin en son elementini seçer. Urunler elementinin altındaki Urun elementlerinin sayısını döndürür. 5 nolu markaya ait ürünlerin sayısını döndürür. Ürünlerin stok toplamını verir Stoku 10 dan az olanların stok toplamını döndürür. İçerisinde Beyaz geçen bütün maddeler. Bölüm 28

222 690 C# Programlama Dili XPath sorgularında [] ifadesi birden fazla kullanılabilir. Bu durumda seçme işlemi soldan sağa doğru yapılır. ifadesi 5 nolu markaya ait ürünlerden ikisini seçer. /Urun[2][@MarkaId=5] İkinci ürünün marka kodu 5 olması durumunda kayıt döndürür. XPath dilinin tabloda gösterilen fonksiyonların dışında concat(), number(), position(), starts-with() ve substring() yordamları bulunur. XPath dilindeki bu operatör ve fonksiyonları kullanarak bir XSLT dosyası tanımlayalım. XSLT dili kendi içerisinde XPath alt yapısını kullandığı için dönüştürme işlemi esnasında verileri filtreleme, ayrıştırma ve sıralama gibi işlemler yapılabilir. <?xml version="1.0" encoding="windows-1254"?> <xsl:stylesheet xmlns:xsl=" version="1.0"> <xsl:template match="/"> <html> <head> <title>müşteri Listesi</title> </head> <body> <xsl:for-each select="musteriler/musteri[@musteriid>1]"> <xsl:sort select="adsoyad" order="ascending"></xsl:sort> <b> <xsl:value-of select="position() "/> nolu kayıt </b> <br/> Müşteri Kodu : <xsl:value-of select="@musteriid"/><br/> Adı Soyadı : <xsl:value-of select="adsoyad"/><br/> Şehir : <xsl:value-of select="sehir"/><br/> <xsl:if test="sehir='izmir'"> <i>bu KİŞİ İZMİR'LİDİR.</i> <br/> </xsl:if> <br/> </xsl:for-each> </body> </html> </xsl:template> </xsl:stylesheet> Bu XSL dosyasında öncelikle aşağıdaki satır aracılığıyla MusteriId değeri 1 den büyük olan Musteriler altında Musteri alt elementleri seçilerek for döngüsü kuruldu. <xsl:for-each select="musteriler/musteri[@musteriid>1]"> Papatya Yayıncılık Eğitim

223 XML Nedir? 691 Kriterlere uyan kayıtlar xsl:sort elementi kullanılarak AdSoyad elementindeki değere göre küçükten büyüğe sıralandı. 12. satırda position() yordamı kullanılarak dönen kaydın sorgu sonucundaki konumu yazıldı. xsl:value-of elementi select ifadesindeki seçimin değerini yazdırır. Örnekte xsl:if elementi kullanılarak akış kontrolü sağlandı. XSL de if-else yapısı bulunmamaktadır. Bunun yerine xsl:choose elementi kullanılır. Bu işlemlerden sonra tarayıcıda XML dosyamızı çağırdığımızda aşağıdaki görüntüyü elde etmiş oluruz. XSL konusunda XSLT yaklaşımını kullanarak bir XML belgesini başka bir XML belgesine dönüştürebileceğimizi yazmıştık. Bununla ilgili basit bir örnek yapalım. <?xml version="1.0"?> <xsl:stylesheet xmlns:xsl=" version="1.0"> <xsl:output method="xml" indent="yes"/> <xsl:template match="/"> <Kisiler> <xsl:apply-templates/> </Kisiler> </xsl:template> <xsl:template match="musteri"> <Kisi> <KisiNo> <xsl:value-of /> </KisiNo> <KisininAdi> <xsl:value-of select="adsoyad" /> </KisininAdi> <KisininSehri> <xsl:value-of select="sehir" /> </KisininSehri> </Kisi> </xsl:template> </xsl:stylesheet> Bölüm 28

224 692 C# Programlama Dili XSLT dilinde en çok kullanılan element olan xsl:template elementi bir şablon tanımlayıp bu şablonun match niteliğinde belirtilmiş elemente uygulanmasını sağlar. Buradaki XSLT dönüşüm dosyasını kullanarak aşağıdaki yeni XML belgesini oluşturmuş oluruz. <?xml version="1.0" encoding="utf-8"?> <Kisiler> <Kisi> <KisiNo>1</KisiNo> <KisininAdi>Ahmet Yılmaz</KisininAdi> <KisininSehri>İstanbul</KisininSehri> </Kisi> <Kisi> <KisiNo>2</KisiNo> <KisininAdi>Murat Şensoy</KisininAdi> <KisininSehri>İzmir</KisininSehri> </Kisi> <Kisi> <KisiNo>3</KisiNo> <KisininAdi>Hasan Çekmen</KisininAdi> <KisininSehri>Ankara</KisininSehri> </Kisi> </Kisiler> Özet Genişletilebilir işaretleme dili olarak çevrilen XML teknolojisi özellikle İnternet gibi farklı ortam ve sistemlerin kullanıldığı heterojen ortamlarda kolayca veri paylaşmak amacıyla tasarlandır. XML belgesinde hangi element ve özniteliklerin kullanılacağı, bunların sırasının nasıl olacağı, elementlerin kaç kez tekrarlanabileceği, hangi element veya özniteliklerin boş geçilebileceği gibi geçerlilik kuralları DTD olarak tanımlanır. DTD nin yetersiz kaldığı durumlarda XML şema kullanılır. XML belgelerini biçimlendirmek için CSS veya XSL teknolojileri kullanılır. XML belgelerini adreslemek için XPath sorgulama dili kullanılır. XML belgesini düğümlerden oluşan bir ağaç olarak yorumlayan XPath, düğüm yollarını kullanarak düğümlere konumlanmamızı ve onlar arasında gezinmemizi sağlar Sorular 28.1) XML ne amaçla tasarlanmıştır ve hangi amaçla kullanılır? 28.2) DTD, XSD, XSL ve XSLT kavramlarını tanımlayınız? 28.3) XPath sorgulama dilini örneklendiriniz. 28.4) XML'de hangi veri türleri kullanılır. 28.5) XML şema belirteçlerini kullanımını örnek içerisinde gösteriniz. Papatya Yayıncılık Eğitim

225 29..NET Framework te XML Programlama XML belgelerinin nasıl düzenleneceği, doğrulanacağı ve standart hale getirileceği konuları önceki bölümlerde ele alınmıştı. Bu bölümde.net teknolojisinin XML belgelerinin okunması, sorgulanması ve yorumlanması konusunda sunduğu yöntem ve sınıfları ele alınacaktır. XML teknolojisi,.net Framework ile birlikte birçok nesnenin temel bilgisi haline getirilerek daha da önem kazanmış oldu. XML belgelerini okuyup içerisindeki verilere ulaşılmasını sağlayan araçlara XML çözümleyici (XML parser) denilir. Çalışma davranışları açısından da DOM ve SAX olmak üzere iki tür çözümleyici modeli vardır. DOM (Document Object Model - Belge Nesne Modeli): Bu tür modelde XML belgesi bir kerede okunarak element ve öznitelikler dahil olmak üzere belgedeki tüm bilgiler belleğe taşınır. Böylece bellek üzerinde XML belgesinin bir örneği oluşmuş olur. DOM nesnesi hiyerarşik anlamında ağaç yapısındaki bu kopyayı temsil eder; bu nesnenin yordamları kullanılarak belgedeki elementlerin özniteliklerine ve değerlerine erişilir ve güncelleme yapılabilir. Belgenin tamamı bellekte bulunduğu için rasgele erişime izin verilir. SAX (Simple API for XML - XML için Basit Arayüz): SAX modeli DOM dan farklı olarak belgeyi tümüyle belleğe taşımak yerine belgeyi ileri doğru okuyarak element bazında belleğe taşır. Bu nedenle okuma işlemleri daha hızlı gerçekleşir; ve bellekte en son okunan element olduğu için daha az kaynak harcar. Yalnızca sıralı erişime izin verir. SAX modelinde veri üzerinde herhangi bir değişiklik yapılamaz. Bu özelliklerin ötesinde SAX olay temelli (event-driven) çalışır. Yani XML belgesini baştan sona tarar ve okuduğu her elementin başında ve sonunda bir olay tetikleyerek programcı tarafından belirlenmiş olan yordamı çalıştırır. Programcı da bu yordam içerisinde elemente ait bilgileri okuyarak ilgili işlemleri yapar. Bu yöntemin eksikliği XML belgesinin tamamına aynı anda erişilemiyor olmasıdır. DOM modeli W3C tarafından (Ekim 1998 de) tanımlanmıştır. SAX ise David Megginson başkanlığında XML-DEV katılımcıları tarafından (Mayıs 1998) geliştirilmiş açık kaynak kodlu bir modeldir. Heriki yaklaşım da birçok XML çözümleyici tarafından kullanılmaktadır.

226 694 C# Programlama Dili.NET Framework içerisinde XML belgeleriyle çalışmak için çeşitli sınıflar vardır. Bu sınıflar System.Xml.dll kütüphanesinde toplanmıştır. Başlı başına bir kitap olabilecek bu kütüphane XML ile ilgili onlarca sınıf içermektedir. MSDN den alınmış aşağıdaki şekilde.net XML kütüphanesinin mimarisi gösterilmiştir. XML belgelerini işlemek için genel olarak XmlReader ve XmlWriter tabanlı sınıflar kullanılır. Soyut olan bu iki sınıf XML veriyi okumak ve yazmak için gerekli yordamları içerir..net Framework te SAX yöntemi kullanılmaz. Buna alternatif olarak kendi standardı olan XmlReader nesnesi kullanılır. Belgeyi tümüyle belleğe taşımayan Xml-Reader nesnesi kanal (stream) tabanlı yaklaşıma sahip olup belgeyi sadece okunabilir olarak açar. XmlReader, davranış olarak SAX modeline benzer; ancak, ondan farklı olarak olay temelli çalışma yerine kanal tabanlı (stream-based) çalışır. Bu iki model arasındaki diğer bir fark ta SAX itme (push) tekniği kullanırken, XmlReader, çekme (pull) tekniği kullanır. Bu iki teknik XML çözümleyici ile istemci/kullanıcı arasındaki ilişkiyi belirtir. İtme tekniğinde çözümleyici veriyi kendisi gönderir; istemcinin o anda onu almak için müsait olup olmadığı önemli değildir. İtme çözümleyici, okumasını bitirdiği anda okuduğu verileri istemcideki geri bildirim yordamına gönderir. Çekme tekniğinde ise istemci çözümleyiciden veriyi kendisi çeker. Bu iki teknik arasındaki diğer bir fark ise şöyledir: İtme türündeki çözümleyiciler tüm XML belgesini bir kanaldan durmaksızın uygulamaya gönderir. Herhangi bir anda durmak için uygulamada aykırı durum üretilmesi gerekir. Yani, ihtiyaç duyulmasa da tüm XML verisi uygulama tarafından işlenir. Çekme tekniğinde ise sadece istenildiği zaman veya sonraki düğüme geçildiği zaman XML verisi uygulamaya gönderilir. Çekme tekniği Papatya Yayıncılık Eğitim

227 .NET Framework te XML Programlama 695 hem programlanmasının kolay olması hem de programcıya daha fazla kontrol sunmasından dolayı avantajlı sayılır. XML belgesini yüklemenin diğer yolu da XmlDocument sınıfıdır; bunun XmlReader dan en önemli farklı DOM modelini kullanıyor olmasıdır. Yani XML belgesini bir kerede okuyup tüm elementlerini ağaç yapısında bir nesne olarak belleğe yükler. DOM tabanlı nesneler belge üzerinde ileri-geri okuma, silme, ekleme ve güncelleme işlemlerini yapabilir Kanal (Stream) Tabanlı XML İşlemleri.NET Framework te XML belgelerini okumak için XmlReader, yazmak ve düzenlemek için de XmlWriter taban sınıfları kullanılır. Bu iki sınıf soyut sınıflar olduğu için kendilerinin Create() yordamları kullanılır. Ancak XML işlemlerinde bu sınıflar yerine daha çok bu sınıflardan türemiş olan aşağıdaki özel sınıflar kullanılır: XmlNodeReader XmlTextReader XmlValidatingReader XmlTextReader sınıfı kanal-tabanlı bir işleyici olup bu sınıflar içerisinde en hızlı okuma özelliğine sahiptir; ancak belgeyle ilişkili DTD veya XML şema bilgilerine erişemez. XmlNodeReader sınıfı belgeye DOM modeline göre erişir. Belgedeki herşeyi XmlNode türünde okur. XmlValidatingReader sınıfı XmlTextReader gibi okuma işlemi gerçekleştirir. Farklı olarak belgeyle ilişki DTD veya XML şemayı da okur ve belge üzerinde doğrulama ve denetleme işlemi yapabilir. Aynı şekilde XmlTextWriter ve XmlQueryOutput (System.Data.SqlXml.dll) yazma sınıfları da XmlWriter sınıfından türemiştir. Bölüm 29

228 696 C# Programlama Dili XmlReader Sınıfının Kullanılması Bu ayrıtta bir kanaldan XML verilerinin nasıl okunacağı örnekle incelenecektir. Bu metin kanalı bir sunucu, bir dosya veya bir TextReader nesnesi gibi farklı kaynaklardan geliyor olabilir. XmlReader soyut bir sınıf olduğu için kendisinden bir örnek oluşturulamaz. Bunun yerine Create() adlı statik yordamı kullanılır. Bu yordam geriye XmlReader türünde bir nesne döndürür. Create(), yeniden yüklenmiş olup kanal (stream) türüne göre parametre alır. En yaygın kullanımı okunacak olan XML belgesinin adresini parametre almasıdır. XmlReader nesnesi kullanım olarak ADO.NET deki DataReader nesnesine benzer. Okuma için Read() yordamı kullanılır. Geriye bool türünde değer döndüren bu yordamın olumlu değer döndürmesi belgeden okunacak veri olduğunu belirtir. XML belgesinin öğelerine erişmek için XmlReader nesnesinin indisleyicisi kullanılır. XmlReader sınıfının en çok kullanılan üyeleri şunlardır: Derinlik (Depth) : O anda okunan düğümün derinliğini belirtir. EOF : XML dosyasının sonuna gelip gelinmediğini bildirir. GetAttribute() : Mevcut niteliğe ait değeri döndürür. HasAttributes : Mevcut düğümün nitelik durumunu bildirir. HasValue : Düğümün herhangi bir değer içerip içermediğini bildirir. IsEmptyElement : Düğümün boş bir XML düğümü olup olmadığını bildirir. IsStartElement() : Okunan etiketin başlangıç etiketi olup olmadığını belirtir. MoveToAttribute(): Düğüme ait niteliklere konumlanmayı sağlar. MoveToElement() : Okunan niteliğin ait olduğu elemente konumlandırır. MoveToFirstAttribute(): Mevcut elementin ilk niteliğine konumlanır. MoveToNextAttribute(): Bir sonraki niteliğe geçişi sağlar. Name : O anda okunan öğenin adını içerir. NodeType : Düğüm türünü XmlNodeType türünde döndürür. Skip() : Mevcut düğümün alt düğümlerine atlar. Value : Düğümün değerini döndürür. Papatya Yayıncılık Eğitim

229 .NET Framework te XML Programlama 697 Aşağıdaki XML belgesini referans alarak XmlReader nesnesinin üyelerini örneklendirelim. <?xml version="1.0" encoding="utf-8"?> <Musteriler> <Musteri MusteriId="1"> <AdSoyad>Ahmet Yılmaz</AdSoyad> <Sehir>İstanbul</Sehir> </Musteri> <Musteri MusteriId="2"> <AdSoyad>Murat Şensoy</AdSoyad> <Sehir>İzmir</Sehir> </Musteri> <Musteri MusteriId="3"> <AdSoyad>Hasan Çekmen</AdSoyad> <Sehir>Ankara</Sehir> </Musteri> </Musteriler> Bu XML belgesindeki öğeleri ve onlarla ilgili bilgileri listeleyelim. Bu örnek için projeye System.Xml kütüphanesini dahil etmeyi unutmayalım! // XmlReader nesnesini oluşturalım. XmlReader string DugumBilgi = ""; byte X=0; // Read() yordamı true döndürdüğü sürece okuma işlemi yapılabilir. while (oxr.read()) { // Aktif düğümün türü Element veya Text ise if (oxr.nodetype == XmlNodeType.Element oxr.nodetype == XmlNodeType.Text) { DugumBilgi = ""; // Düğümün derinliği kadar boşluk yazdıralım. for (X = 0; X < oxr.depth; X++) DugumBilgi += " "; DugumBilgi = DugumBilgi + oxr.name + " " + oxr.nodetype; // Okuduğumuz düğüme ait değer varsa. if (oxr.hasvalue) DugumBilgi = DugumBilgi + " : " + oxr.value; Console.WriteLine(DugumBilgi); // Mevcut düğüme ait varsa nitelikleri okuyalım. if (oxr.hasattributes) { while (oxr.movetonextattribute()) { DugumBilgi = ""; // Düğümün derinliği kadar boşluk yazdıralım. for (X = 0; X < oxr.depth; X++) DugumBilgi += " "; DugumBilgi = DugumBilgi + oxr.name + " " + oxr.nodetype; if (oxr.hasvalue) DugumBilgi = DugumBilgi + " : " + oxr.value; Console.WriteLine(DugumBilgi); // Belgeyi okumak için kullandığımız kaynakları serbest bırakalım. oxr.close(); Bölüm 29

230 698 C# Programlama Dili Musteriler Element Musteri Element MusteriId Attribute : 1 AdSoyad Element Text : Ahmet Yılmaz Sehir Element Text : İstanbul Musteri Element MusteriId Attribute : 2 AdSoyad Element Text : Murat Şensoy Sehir Element Text : İzmir Musteri Element MusteriId Attribute : 3 AdSoyad Element Text : Hasan Çekmen Sehir Element Text : Ankara XmlReader sınıfının diğer önemli üyeleri değer dönüştürme yordamlarıdır. XML belgesindeki bir öğeye ait değeri XmlReader nesnesinin Value özelliğiyle okuruz. Bu özellik sözce/string türünde değer döndürür. Framework 2.0 ile birlikte sözceyi farklı veri türlerine otomatik dönüştüren ReadContentAsXXX ve ReadElement-ContentAsXXX yordamları vardır. Belgedeki imleç, metin veya nitelik türünde bir öğeye konumlanmışsa öğenin değeri ReadContentAsXXX ile okunabilir. Okunan bir elementin değerini istenen bir türden elde etmek için ReadElementContentAsXXX yordamı kullanılabilir. Örnekte MusteriId niteliklerinin okunduğu satırda oxr.value+8 şeklinde bir işlem yapılırsa, çözümleyici, bunu sözceye karakter ekleme gibi algılar ve 18, 28, 38 sözcesinin 8 karakterini ekler; ancak sözce olarak değil de tamsayı olarak okunması sağlanırsa gerçekten toplama işlemi yapılmış olur. oxr.readcontentasint()+8 Papatya Yayıncılık Eğitim

231 .NET Framework te XML Programlama XmlReader ile Belgenin Doğrulanması Bir önceki örnek XmlTextReader sınıfı kullanılarak ta yapılabilir. XmlReader ve XmlTextReader sınıfları XML belgesini sadece okumak için kullanılır. Belgeye ait DTD veya şema kurallarına erişmek veya belgeyi doğrulamak için XmlValidatingReader sınıfı kullanılır. Fakat.NET Framework 2.0 ile birlikte bu sınıf tedavülden kaldırıldı. Bunun yerine tür işlemler için XmlReaderSettings sınıfının kullanılması önerilir. Bu sınıf XmlReader nesnesinin belgeyi hangi kurallar doğrultusunda okuyacağını belirlemek için kullanılır. Bu kurallar çalışma anında tanımlanabildiği gibi XML belgesinde DTD ve XSD aracılığıyla da tanımlanabilir. XmlReaderSettings sınıfının birkaç özelliği ve bir tane olayı bulunur; özelliklerden bazıları çalışma-anında XmlReader nesnesini yönlendirmek, bazıları da XmlReader nesnesinin referans olarak kullanacağı şema dosyasını belirtmek için kullanılır. Öncelikle basit bir örnek yapalım. Önceki örnekte XmlReader nesnesi XML belgesindeki her öğeyi okuduğu için NodeType özelliğini kullanarak sadece Element ve Text türündeki öğeleri seçtik. Böylece belge içerisindeki açıklama öğelerini önemsemedik. Aynı işlemi XmlReaderSettings sınıfıyla da yapabiliriz. Bunun için öncelikle XmlReaderSettings sınıfından bir nesne oluşturulur; ardından bu nesnenin nitelikleri belirtilir ve nesne XmlReader sınıfının Create() yordamına ikinci parametre olarak gönderilir. Aşağıdaki örnekte XmlReader nesnesinin açıklama alanlarını ve beyaz-karakter öğelerini engellemesini sağlıyoruz. // XmlReaderSettings nesnesini tanımlayalım. XmlReaderSettings oxrs = new XmlReaderSettings(); // Beyaz karakterleri engellesin. oxrs.ignorewhitespace = true; // Açıklama alanlarını engellesin. oxrs.ignorecomments = true; // XmlReader nesnesini oluşturalım ve oxrs nesnesinin parametre olarak geçelim. XmlReader oxr = XmlReader.Create(@"C:\Musteri.xml",oXrs); while (oxr.read()){ Console.WriteLine(oXr.Name + " " + oxr.nodetype); Bilindiği gibi XML belgelerini etiketleme veya var olan etiketlere daha önce belirlenmiş kurallar doğrultusunda veri girmek için DTD (Document Type Definitions) ve XSD (Xml Schema Definitions) gibi tanımlamalar kullanılır. Bunlar aracılığıyla XML belgesi için yapı veya veri türü bazlı kurallar tanımlanır. Farklı sistemlerden alınan belgelerinin doğruluğu da bunlarla denetlenir..net Framework te bir belgenin doğrulamasını yapmak için XmlReaderSettings aracı kullanılır. Örnek olarak kullandığımız XML belgesi için bir şema dosyası oluşturalım. Bölüm 29

232 700 C# Programlama Dili [Musteri.xsd] <?xml version="1.0" encoding="iso "?> <xs:schema xmlns:xs=" <!-- Musteri elementini tanımlayalım --> <xs:element name="musteri"> <xs:complextype> <xs:sequence> <xs:element name="adsoyad" type="xs:string"/> <xs:element name="sehir" type="xs:string"/> <xs:element name="dogumtarih" type="xs:datetime"/> </xs:sequence> <!--Zorunlu MusteriId niteliği--> <xs:attribute name="musteriid" type="xs:integer" use="required"/> </xs:complextype> </xs:element> <xs:element name="musteriler"> <xs:complextype> <xs:sequence> <!--Musteri elementi en az 1 olacak şekilde istenildiği kadar kullanılabilir--> <xs:element ref="musteri" minoccurs="1" maxoccurs="unbounded"/> </xs:sequence> </xs:complextype> </xs:element> </xs:schema> Bu şema dosyasında belirlenen kuralları şöyle açıklayabiliriz: XML belgesinin Musteriler isminde üst ve bunun altında da Musteri isminde alt elementler bulunmaktadır. Bu alt element en az bir olacak şekilde istenildiği kadar tanımlanabilir. Musteri ve AdSoyad elementleri sözce/string türünde, DogumTarih tarih türünde elementlerini ve tamsayı türünde zorunlu MusteriId niteliğini içermektedir. Bu şema dosyasını XML belgesiyle ilişkilendirelim ve XML belgesine şemanın kurallarına aykırı girişler yapalım. [Musteri.xml] <?xml version="1.0" encoding="utf-8"?> <Musteriler xmlns:xsi=" xsi:nonamespaceschemalocation="musteri.xsd"> <Musteri MusteriId="1"> <AdSoyad>Ahmet Yılmaz</AdSoyad> <Sehir>İstanbul</Sehir> <DogumTarih> T00:00:00</DogumTarih> <Aidat>2000</Aidat> </Musteri> <Musteri MusteriId="ABC"> <AdSoyad>Murat Şensoy</AdSoyad> <Sehir>İzmir</Sehir> </Musteri> <Musteri MusteriId="3"> <AdSoyad>Hasan Çekmen</AdSoyad> <Sehir>Ankara</Sehir> <DogumTarih>18/02/1975</DogumTarih> </Musteri> </Musteriler> Papatya Yayıncılık Eğitim

233 .NET Framework te XML Programlama 701 Bu XML belgesinde dört adet yanlış yapılmıştır: İlki, ilk Musteri elementine şemada tanımlanmayan Aidat isimli bir alt elementin eklenmesidir; ikincisi, ikinci Musteri elementi için MusteriId niteliğinin sözce/string olarak tanımlanmasıdır. Oysa bunun sayısal olması gerektiği bildirilmişti. Üçüncü hata ise ikinci Musteri elementi için DogumTarih alt elementinin tanımlanmamış olmasıdır. Son hata da üçüncü Müşteri elementindeki tarih formatının yanlış girilmiş olmasıdır. Şimdi bu yanlışları.net Framework ortamında yakalamaya çalışalım. Öncelikle XmlReaderSettings sınıfından bir örnek oluşturulur. Doğrulama işleminde referans olarak kullanılacak şema dosyaları bu sınıfın Schema özelliği aracılığıyla koleksiyona eklenir. Ayrıca DTD, XSD ve XDR gibi ne tür bir denetlemenin yapılacağı da sınıfın ValidationType özelliğiyle belirtilir. XmlReader nesnesi XML belgesini çözerken herhangi bir hata veya uyarıyla karşılaştığı zaman XmlReaderSettings sınıfının ValidationEventHandler olayını tetikler. using System; using System.Xml; using System.Xml.Schema; //XmlSeverityType class Program { static void Main(string[] args) { // XmlReaderSettings nesnesini tanımlayalım. XmlReaderSettings oxrs = new XmlReaderSettings(); // ValidationEventHandler eventini hataları yakalayacağımız yordama bağlayalım. oxrs.validationeventhandler += new ValidationEventHandler(DogrulamaHataOlustu); // Kullanılacak şema dosyasını ekleyelim. oxrs.schemas.add(null, XmlReader.Create(@"C:\Musteri.xsd")); // Doğrulama türünü XML şema olarak set edelim. oxrs.validationtype = ValidationType.Schema; // XmlReader nesnesini oluşturalım ve oxrs nesnesinin parametre olarak geçelim. XmlReader oxr = XmlReader.Create(@"C:\Musteri.xml", oxrs); while (oxr.read()) { // Belgenin ayrıntılarını okuduğumuz varsayalım. oxr.close(); // Belgeyi okumak için kullanılan kaynakları serbest bırakalım. // Belgede hatayla karşılaşıldığı zaman çalışacak yordam. static void DogrulamaHataOlustu(object sender, System.Xml.Schema.ValidationEventArgs e) { if (e.severity == XmlSeverityType.Error) Console.WriteLine("HATA : " + e.message); HATA : The element 'Musteri' has invalid child element 'Aidat'. HATA : The 'MusteriId' attribute is invalid - The value 'ABC' is invalid according to its datatype ' - The string 'ABC' is not a valid Integer value. HATA : The element 'Musteri' has incomplete content. List of possible elements expected: 'DogumTarih'. HATA : The 'DogumTarih' element is invalid - The value '18/02/1975' is invalid according to its datatype ' - The string '18/02/1975' is not a valid XsdDateTime value. Bölüm 29

234 702 C# Programlama Dili XmlWriter Sınıfının Kullanılması XmlWriter sınıfı XML belgesi oluşturmak veya var olan belgeye kanal (stream) üzerinden veri yazmak için kullanılır; soyut bir sınıf olduğu için kendisinden doğrudan bir örnek nesne oluşturulamaz. XML yazma işlemleri için genel olarak bu sınıftan türemiş olan XmlTextWriter sınıfı kullanılır. Yazma işleminin hangi belge üzerinde yapılacağı XmlTextWriter sınıfının yeniden yüklenmiş olan yapıcı yordamında belirtilir. XmlTextWriter sınıfının yapılandırıcısı belgeyi temsilen System-.IO.TextWriter veya System.IO.Stream türünde parametre alabildiği gibi doğrudan belgenin konumunu da parametre olarak alabilir. Ayrıca işlem yapılacak belge üzerinde hangi dil kodlamasının kullanılacağını da XmlTextWriter sınıfından nesne oluştururken parametre olarak verebiliriz. public XmlTextWriter(TextWriter w); public XmlTextWriter(Stream w, Encoding encoding); public XmlTextWriter(string filename, Encoding encoding); XmlTextWriter sınıfının önemli üyeleri şunlardır: BaseStream: XmlTextWriter üzerinde yazma işlemi yaptığı kanalı döndürür. Biçimleme (Formatting): Verinin belgeye yazılış formatını belirtir. Indented veya None değerini alabilir. İlk seçenekte verilerin girintili yazılması sağlanarak belgenin metin tabanlı editörlerde okunabilirliği artırılmış olur. None seçeneğinde ise bir biçimlendirme uygulanmaz. YazmaDurumu (WriteState): Yazıcının durumunu bildirir. Aşağıdaki değerleri alabilir. Start seçeneği Write() yordamının henüz çağrılmadığını belirtir; Prolog seçeneği prolog alanının yazılmaya başlandığını belirtir. Prolog (öndeyiş), belgenin giriş a- lanı anlamına gelir. Bu alan DTD ve XML bildirim tanımlamalarının yapıldığı satırları içerir. Bu alandan sonra belgenin kök elementi başlar. Element seçeneği bir elementin başlangıç etiketinin yazılmaya başlandığını bildirir. Attribute seçeneği bir nitelik değerinin yazılmaya başladığını belirtir. Content seçeneği elementin içeriğinin yazılmaya başlandığını bildirir. Closed seçeneği XmlWriter.Close() yordamının çağrıldığını belirtir. Error ise XmlWriter nesnesinin son işleminde bir hata ürettiğini belirtir. WriteStartDocument() : XML belgesinin uyarlama ve karakter kümesinin tanımlandığı ilk satırı (<?xml version = "1.0"?>) yazdırır. WriteStartElement() WriteEndElement() : Elementin başlangıç etiketini yazdırır. : Elementin bitiş etiketini yazdırır. WriteStartAttribute(): Bir niteliğin başlangıcını belirtir. WriteEndAttribute() WriteComment() : Bir niteliğin bitişini belirtir. : XML belgesine yorum satırları ekler. Papatya Yayıncılık Eğitim

235 .NET Framework te XML Programlama 703 Bu üyelere ek olarak bir elementi değeriyle birlikte oluşturmak için WriteElementString() yordamı, bir niteliği değeriyle birlikte oluşturmak için WriteAttributeString() yordamı da kullanılır. Bu iki yordam öğe adı ve değeri olmak üzere iki parametre alır. Örnek olarak aşağıdaki gibi bir XML belgesi oluşturalım. <?xml version="1.0" encoding="utf-8" standalone="no"?> <Musteriler> <Musteri MusteriId="1"> <!--Müşterinin AdSoyad ve Şehir bilgisi--> <AdSoyad>Ahmet Yılmaz</AdSoyad> <Sehir>İstanbul</Sehir> </Musteri> </Musteriler> Bu belgeyi C:\ nin altında Musteri.xml dosyası olarak saklayalım. // XMLTextWriter nesnesi oluşturalım. XmlTextWriter oxtw = new XmlTextWriter(@"C:\Musteri.xml", System.Text.Encoding.UTF8); // Girintili biçimde olacak. oxtw.formatting = Formatting.Indented; // Belgenin tanımlayıcı satırını oluşturalım.(standalone=false) oxtw.writestartdocument(false); // Kök elemanı yazdıralım. oxtw.writestartelement("musteriler"); // Musteri elementini oluşturalım. oxtw.writestartelement("musteri"); // Musteri elementine ait niteliği belirtelim. oxtw.writeattributestring("musteriid", "1"); // Açıklama satırı ekleyelim. oxtw.writecomment("müşterinin AdSoyad ve Şehir bilgisi"); // Musteri elementinin altındaki alt elementleri oluşturalım. oxtw.writeelementstring("adsoyad", "Ahmet Yılmaz"); oxtw.writeelementstring("sehir", "İstanbul"); // Musteri elementini kapatalım. oxtw.writeendelement(); // Kök elemanı kapatalım. oxtw.writeendelement(); // Belge yazmayı bitirelim oxtw.writeenddocument(); // Yazma nesnesini sonlandıralım. oxtw.flush(); oxtw.close(); DOM Tabanlı XML İşlemleri DOM tabanlı yaklaşımda XML belgesi sahip olduğu ağaç yapısıyla birlikte belleğe alınır. XML belgesinin bellek üzerinde bulunan bu kopyasını XmlDocument sınıfı temsil eder. Bölüm 29

236 704 C# Programlama Dili MSDN den alınmış aşağıdaki şekilde XML belgesini metin kanal tekniğine göre işleyen kütüphane yapısı gösterilmiştir: XmlDocument Sınıfının Kullanımı Kısaca XML DOM olarak tanımlanan XmlDocument sınıfı XML belgesini alt ve üst düğüm ilişkisini koruyacak şekilde ağaç yapısında belleğe taşır ve bellek üzerinde düğümler arasında ileri-geri ilerleme, arama, güncelleme ve silme işlemleri yapılmasına imkan verir. XmlDocument sınıfı XmlNode sınıfından türemiştir. XmlNode,.NET Framework te DOM tabanlı çalışan sınıfların temelini oluşturup XML belgesindeki bir düğümü temsil eder. XmlDocument sınıfını XmlNode türündeki birçok düğümü içeren bir koleksiyon olarak ta düşünülebilir. XML belgesindeki düğüm türlerine göre XmlAttribute, XmlComment ve XmlElement gibi özel sınıflar hazırlanmıştır. Bu sınıflar da XmlNode sınıfından türemiştir. XmlNode sınıfının birçok üyesi vardır. Burada çok kullanılan üyeleri ele alınmaktadır: AppendChild(): Düğümlerin sonuna yeni bir düğüm ekler. Attributes ChildNotes : Düğümlerin niteliklerini listeler. : Mevcut düğüme ait alt düğümleri döndürür. CloneNode() : Mevcut düğümün tüm niteliklerini, değerlerini ve isteğe bağlı olarak alt düğümlerin bir kopyasını oluşturur. FirstChild : Aktif düğümün ilk alt düğümünü temsil eder. HasChildNodes: Aktif düğümün alt düğüm içerip içermediğini bildirir. InnerText InnerXml : Mevcut düğümün ve ona ait alt düğümlerin değerini döndürür. : Mevcut düğüme ait alt düğümleri XML formatında döndürür. InsertAfter(): Uygulandığı düğümden sonra yeni bir düğüm ekler. Papatya Yayıncılık Eğitim

237 .NET Framework te XML Programlama 705 InsertBefore(): Uygulandığı düğümden önce yeni bir düğüm ekler. LastChild Name NextSibling NodeType OuterXml : Mevcut düğümün son alt düğümünü temsil eder. : Mevcut düğümün adını bildirir. : Mevcut düğümün ana düğümünden sonraki düğümü temsil eder. : Okunan düğümün türünü bildirir. : Mevcut düğümü ve alt düğümleri XML formatında döndürür. OwnerDocument: Düğümün bulunduğu belgeye referans eder. ParentNode : Mevcut düğümün üst düğümünü döndürür. PrependChild(): Alt düğümlerin başladığı yere yeni bir alt düğüm ekler. PreviousSibling: Mevcut düğümün ana düğümünden önceki düğümü temsil eder. RemoveAll() : Mevcut düğümün tüm alt düğümleri siler. RemoveChild(): Mevcut düğümün belirlenmiş bir alt düğümünü siler. SelectedNodes(): XPath sorgulama kriterlerine uyan düğümleri seçer. SelectSingleNode(): XPath sorgulama kriterlerine uyan düğümlerin ilkini seçer. WriteContentTo(): Mevcut düğümün tüm alt elemanlarını parametre olarak aldığı XmlWriter nesnesine yazar. XmlNode sınıfı soyut bir sınıf olduğu için kendisinden doğrudan bir nesne oluşturulamaz. Belge yazma işlemleri için XmlDocument sınıfı kullanılır. XmlDocument sınıfı yukarıda listesi verilen yordam ve özellikleri kalıtım ilkesinden dolayı desteklediği gibi ek olarak birkaç üyeye daha sahiptir. Bunların en önemlileri şunlardır: CreateAttribute(): Aldığı parametrelere uygun bir nitelik oluşturur ve geriye XmlAttribute türünde değer döndürür. CreateElement(): Aldığı parametrelere uygun bir element oluşturur ve geriye XmlElement türünde değer döndürür. CreateNode(): Parametre olarak aldığı XmlNodeType türünde bir XmlNode nesnesi o- luşturur. DocumentElement: XmlElement türüne sahip olup belgedeki kök elementini verir. DocumentType: <!DOCTYPE> düğümünü yani belgenin ilişkili olduğu DTD bildirimini içeren düğümü temsil eder. GetElementById(): Parametre olarak aldığı Id ye uygun elementi döndürür. GetElementsByTagName(): Parametre olarak aldığı etikete uyan elementleri XmlNodeList türünde döndürür. ImportNode(): Başka bir XML belgesinden bir düğümü içeri alır. Load(): Diskteki XML belgesini belleğe yüklemek için kullanılır. Yeniden yüklenmiş olan bu yordam String, Stream, TextReader veya XmlReader türünde parametre a- labilir. LoadXml(): XML formatındaki bir string değeri belleğe XML nesnesi olarak yükler. PreserveWhitespace: True değerini aldığı zaman XML belgesi yüklenirken veya saklanırken Whitespace karakterler (Enter, Tab vs.) korunmasını sağlar. Bölüm 29

238 706 C# Programlama Dili Save(): Belgeyi saklamak için kullanılır. Bu yordamın da farklı türleri vardır. String, Stream, TextWriter veya XmlWriter türünde parametre alabilir. WriteTo(): XmlDocument nesnesini bir XmlWriter nesnesine yazdırır. XmlDocument sınıfının önemli olayları da şunlardır: NodeChanged : Bir düğümün değeri değiştiği zaman tetiklenir. NodeChanging : Bir düğümün değeri değişmek üzere iken tetiklenir. NodeInserted : Belgeye yeni düğüm eklendiği zaman tetiklenir. NodeInserting: Belgeye yeni düğüm eklenirken tetiklenir. NodeRemoved : Belgeden düğüm silindiği zaman tetiklenir. NodeRemoving : Belgeden düğüm silinmek üzereyken tetiklenir. Üzerinde çalıştığımız Musteri.xml dosyasını XmlDocument sınıfıyla oluşturalım. XmlDocument odoc = new XmlDocument(); // XML bildirim satırı. XmlDeclaration xdec = odoc.createxmldeclaration("1.0", "utf-8", null); // Kök elementinin oluşturulması. XmlElement rootnode = odoc.createelement("musteriler"); odoc.insertbefore(xdec, odoc.documentelement); odoc.appendchild(rootnode); // Yeni bir <Musteri> elementi oluşturalım. XmlElement musnode = odoc.createelement("musteri"); // Musteri elementi için MusteriId niteliğini tanımlayalım. musnode.setattribute("musteriid", "1"); // Kök elementinin hemen başlangıcına ekleyelim. odoc.documentelement.prependchild(musnode); // <Musteri> elementinin alt elementlerini oluşturalım. XmlElement adnode = odoc.createelement("adsoyad"); XmlElement shrnode = odoc.createelement("sehir"); // Alt elementlere ait metinleri oluşturalım. XmlText adtext = odoc.createtextnode("ahmet Yılmaz"); XmlText shrtext = odoc.createtextnode("istanbul"); // <AdSoyad> ve <Sehir> elementlerini <Musteri> elementine aktaralım. musnode.appendchild(adnode); musnode.appendchild(shrnode); // Alt elementleri, değerleriyle ilişkilendirelim. adnode.appendchild(adtext); shrnode.appendchild(shrtext); // XML dosyasını kayıt edelim. odoc.save(@"c:\musteri.xml"); <?xml version="1.0" encoding="utf-8"?> <Musteriler> <Musteri MusteriId="1"> <AdSoyad>Ahmet Yılmaz</AdSoyad> <Sehir>İstanbul</Sehir> </Musteri> </Musteriler> Papatya Yayıncılık Eğitim

239 .NET Framework te XML Programlama 707 XML belgesinde bir kayıdı aramak için XPath sorguları kullanılır. Bu sorgu deyimleri SelectSingleNode() veya SelectNodes() yordamlarına parametre olarak geçilir. Önceki örnekte oluşturulan Musteri.xml dosyasının Musteri düğümlerini listeleyelim ve ardından belgeye yeni bir Musteri düğümü ekleyelim. XmlDocument odoc = new XmlDocument(); odoc.load(@"c:\musteri.xml"); // <Musteri> etiketlerini alalım. XmlNodeList NodLst = odoc.getelementsbytagname("musteri"); for (byte x = 0; x < NodLst.Count; x++) { Console.Write("Müşteri Id : "); Console.WriteLine(NodLst[x].Attributes["MusteriId"].Value ); Console.WriteLine(NodLst[x].InnerXml); // Belgeye yeni bir <Musteri> elementi ekleyelim. XmlElement musnode = odoc.createelement("musteri"); musnode.setattribute("musteriid","3"); odoc.documentelement.appendchild(musnode); // <Musteri> elementinin alt elementlerini oluşturalım. XmlElement adnode = odoc.createelement("adsoyad"); XmlElement shrnode = odoc.createelement("sehir"); // Alt elementlere ait metinleri oluşturalım. XmlText adtext = odoc.createtextnode("berna Güzel"); XmlText shrtext = odoc.createtextnode("istanbul"); // <AdSoyad> ve <Sehir> elementlerini <Musteri> elementine aktaralım. musnode.appendchild(adnode); musnode.appendchild(shrnode); // Alt elementleri, değerleriyle ilişkilendirelim. adnode.appendchild(adtext); shrnode.appendchild(shrtext); // Belgeyi kayıt edelim. odoc.save(@"c:\musteri.xml"); Müşteri Id : 1 <AdSoyad>Ahmet Yılmaz</AdSoyad><Sehir>İstanbul</Sehir> Müşteri Id : 2 <AdSoyad>Murat Şensoy</AdSoyad><Sehir>İzmir</Sehir> Belgeden XmlDocument sınıfı aracılığıyla bir kayıt silmek için RemoveChild() yordamı kullanılır. Belgedeki ilk müşteri kaydını silelim. XmlDocument odoc = new XmlDocument(); odoc.load(@"c:\musteri.xml"); XmlNode root = odoc.documentelement; Console.WriteLine(root.InnerXml); // İlk elemanı silelim. root.removechild(root.firstchild); // Belgeyi güncelleyelim. odoc.save(@"c:\musteri.xml"); Bölüm 29

240 708 C# Programlama Dili Başka bir örnek olarak belgedeki tüm müşteri düğümlerinin altındaki şehir elementini silelim. XmlDocument odoc = new XmlDocument(); odoc.load(@"c:\musteri.xml"); XmlNode root = odoc.documentelement; Console.WriteLine(root.InnerXml); // <Musteri> etiketlerini alalım. XmlNodeList NodLst = odoc.getelementsbytagname("musteri"); foreach (XmlNode node in NodLst) node.removechild(node.selectsinglenode("sehir")); // Belgeyi güncelleyelim. odoc.save(@"c:\musteri.xml"); XML Dönüştürme (XslTransform Sınıfı) XML dönüştürme işlemi için XSLT dili kullanılır. XSLT, XML belgelerinin asıl yapısını değiştirmeden diğer XML belgelerine (XML, HTML, CSV) dönüştürmek için kullanılan bir dildir..net Framework platformunda bir XML belgesini ilişkili olduğu XSLT dosyasını kullanarak başka bir belgeye dönüştürmek için System.Xml.Xsl kütüphanesindeki XslTransform sınıfı kullanılır. Fakat.NET Framework 2.0 ile birlikte dönüştürmeyi daha hızlı gerçekleştiren XslCompiled-Transform sınıfı geliştirildi. XslCompiledTransform sınıfının Load() ve Transform() olmak üzere iki önemli yordamı bulunur. Genel olarak Load(), dönüştürme işleminde referans alınacak XSLT dosyasını yükler. Transform() yordamı ise parametre olarak XML belgesini alarak dönüştürme işlemini gerçekleştirir. Daha önce XSL konusunda verilen XML ve XSLT dosyaları kullanılarak basit bir dönüştürme işlemi şu şekilde olur: XslCompiledTransform oxsl = new XslCompiledTransform(); oxsl.load("bicimdosya.xslt"); oxsl.transform("musteri.xml", null, Console.Out); Transform() yordamı üç parametre almaktadır. İlki, XML belgesinin yolunu/adresini; ikincisi, XSLT belgesine gönderilecek parametreleri, son parametre ise dönüştürme sonucunun nereye yazılacağını belirtir. Verilen örnekte XSLT dosyasına herhangi bir argüman gönderilmediği için ikinci parametre null olarak düzenlendi. Örnekte dönüştürülen sonucun konsola yazdırılması sağlanmıştır. Dönüştürme sonucu Stream, TextWriter ve XmlWriter nesnelerine de yazdırılabilir. Papatya Yayıncılık Eğitim

241 .NET Framework te XML Programlama NET te XPath Uygulaması XML belgelerini yükleme, belge üzerinde arama, konumlandırma ve sıralama işlemlerini yapmanın en popüler yöntemi XmlDocument sınıfını kullanmaktır. DOM modeline göre çalışan XmlDocument sınıfının SelectSingleNode() veya SelectNodes() yordamlarını kullanarak belirli bir düğüme veya düğüm listesine erişebiliriz. Bu yordamlar parametre olarak belge üzerinde uygulanacak XPath sorgu deyimini alır. Aşağıdaki örnekte MüşteriId niteliğinin değeri 1 den büyük olan kayıtlar seçilmektedir. XmlDocument oxd = new XmlDocument(); oxd.load(@"c:\musteri.xml"); XmlNodeList NodLst ; // MusteriId'si 1'den büyük olan kayıtları alalım. string sorgu = "/Musteriler/Musteri[@MusteriId>1]"; NodLst = oxd.documentelement.selectnodes(sorgu); foreach (XmlNode ond in NodLst) Console.WriteLine(oNd.InnerXml); <AdSoyad>Murat Şensoy</AdSoyad><Sehir>İzmir</Sehir> <AdSoyad>Hasan Çekmen</AdSoyad><Sehir>Ankara</Sehir> ADO.NET 2.0 ile birlikte XPath sorgularını daha performanslı çalıştırmak için System.Xml.XPath kütüphanesi geliştirildi. Bu kütüphane tamamıyla XPath sorgularıyla ilişkili çalışan çeşitli sınıflar içerir. XPathDocument sınıfı XML belgesini sorgulamak veya dönüştürmek üzere, tümüyle salt okunur olarak belleğe ağaç yapısında yükler. XPathNavigator sınıfı da bu nesne üzerinde gezinilmesini sağlar. Yapıcı yordamı, yeniden yüklenmiş olan XPathDocument sınıfının dışarıya açık üye olarak sadece CreateNavigator() isimli yordamı bulunur. Bu yordam XPathNavigator sınıfı türünde bir nesne döndürür. Bu nesne bellekte sözkonusu XML belgesi üzerinde hareket edilmesi için kullanılır. XPathNavigator sınıfı soyut bir sınıf olduğu kendisinden nesne oluşturulamaz. Bu sınıftan nesne oluşturmak için XmlDocument, XmlDataDocument veya XPathDocument sınıflarının CreateNavigator() isimli yordamı kullanılır. XPathNavigator sınıfının birçok üyesi bulunmaktadır. Bunlardan en çok kullanılanları HasAttributes, HasChildren, IsEmptyElement, Name, Value özellikleri ve MoveToFirst(), MoveToFirstAttribute(), MoveToFirstChild(), MoveToNext(), MoveToNextAttribute(), MoveToParent(), MoveToPrevious(), MoveToRoot(), Select(), Evaluate() yordamlarıdır. Bölüm 29

242 710 C# Programlama Dili Üzerinde hareket edilecek kayıtları seçmek için Evaluate() veya Select() yordamları kullanılır. Bunlar belge üzerinde yürütülecek XPath sorgusunu parametre olarak alırlar. Evaluate() geriye object türünde değer döndürür; tek kayıt dönecek sorgularda kullanılır. Select() ise XPath ifadesindeki kritere uyan tüm düğümleri XPathNodeIterator koleksiyonu olarak döndürür. Ayrıca mevcut düğümün alt veya üst düğümlerini seçmek için SelectAncestors(), SelectChildren() ve SelectDescendants() yordamları kullanılabilir. MüşteriId niteliğinin değeri 1 den büyük olan kayıtları seçelim. XPathDocument oxd = new XPathDocument("C:\\Musteri.xml"); XPathNavigator onvg = oxd.createnavigator(); string Sorgu = "/Musteriler/Musteri[@MusteriId>1]"; XPathNodeIterator NodLst = onvg.select(sorgu); Console.WriteLine("Kayıt sayısı : {0", NodLst.Count); Console.WriteLine(); // Sonuçları listeleyelim. // Okunacak bir sonraki kayıt olduğu sürece. while (NodLst.MoveNext()) { Console.WriteLine(NodLst.Current.InnerXml); Kayıt sayısı : 2 <AdSoyad>Murat Şensoy</AdSoyad> <Sehir>İzmir</Sehir> <AdSoyad>Hasan Çekmen</AdSoyad> <Sehir>Ankara</Sehir> DataSet ve XML İlişkisi DataSet nesnesi veriyi temelde XML formatında sakladığı için kolaylıkla bir XML dosyasını dışarıdan veri kaynağı olarak kullanabilir veya içindeki veriyi XML olarak dışarı aktarabilir. DataSet sınıfının bu amaç için kullanılan üyeleri şunlardır: GetXml() : DataSet içindeki verileri XML formatında döndürür. GetXmlSchema() : DataSet in şemasını XML formatında döndürür. WriteXml() : DataSet deki verileri ve şema yapısını XML belgesine yazdırır. Parametre olarak string, Stream, TextWrite veya XmlWriter türünde değer alabilir. WriteXmlSchema() : DataSet nesnesinin sadece şema yapısını yazdırır. WriteXml() yordamıyla aynı parametre yapısına sahiptir. ReadXml() : XML belgesini DataSet içerisine aktarır. Yeniden yüklenmiş bir yordam olan bu yordam, parametre olarak string, Stream, TextReader ve XmlReader türünde değer alabilir. İki parametre aldığı uyarlamasında ise bu türlerle birlikle XmlReadMode türünde ikinci bir parametre de alır. ReadXmlSchema() : DataSet nesnesinin bağlı olduğu XML belgesinin veya XML tabanlı bir kanaldan sadece şema yapısını okur. Papatya Yayıncılık Eğitim

243 .NET Framework te XML Programlama DataSet i XML Olarak Yazdırmak ADO.NET platformunda DataSet nesnesi içeriği şemalı veya şemasız XML formatı şeklinde yazdırılabilir. Bir DataSet XML olarak yazdırıldığında varsayılan olarak içeriğinin güncel hali yazdırılır. Bununla birlikte DataSet nesnesi DiffGram olarak ta yazdırılabilir. DiffGram formatı, XML biçiminde olup DataSet satırlarının hem orijinal hem güncel hallerini içerir. DataSet içeriği DiffGram formatında yazdırıldığında satırların orijinal ve güncel uyarlamaları kıyaslanabilir. DataSet içeriğini şemasız yazdırmak için GetXml(), şemayla birlikte yazdırmak için GetXmlSchema() yordamları kullanılır. Bu iki yordam da XML veriyi sözce/string bir değişkene yazdırır. DataSet nesnesinin içeriğini fiziksel bir dosyaya, bir kanala veya bir XmlWriter nesnesine yazdırmak için WriteXml() yordamı tercih edilir. Tek veya iki parametre alabilen WriteXml() yordamının aldığı ilk parametre XML sonucun nereye yazılacağını, ikinci parametre ise çıktının nasıl yazılacağını belirtir. System.Data.XmlWriteMode türünde olan ikinci parametre aşağıdaki üç değerden birini alır: IgnoreSchema: DataSet nesnesinin sadece veri içeriği yazdırılır. Şemasını görmezden gelir. WriteSchema: DataSet verisini şema yapısıyla birlikte yazdırır. DiffGram: DataSet nesnesinin içeriğini DiffGram formatında oluşturur. Böylece o- luşan XML dosyasında DataSet içeriğinin hem güncel hali hem de içerik üzerinde yapılmış değişiklikler bulunur. string CnnStr SqlConnection ocnn = new SqlConnection(CnnStr); SqlCommand ocmd = new SqlCommand("SELECT * FROM Musteri", ocnn); SqlDataAdapter oda = new SqlDataAdapter(); oda.selectcommand = ocmd; DataSet ods = new DataSet(); oda.fill(ods,"musteri"); ods.writexml(@"c:\musteri.xml"); <?xml version="1.0" standalone="yes"?> <NewDataSet> <Musteri> <MusteriId>1</MusteriId> <AdSoyad>Zeynep Ilhan</AdSoyad> <KayitTarih> T22:47:00-08:00</KayitTarih> </Musteri> <Musteri> <MusteriId>2</MusteriId> <AdSoyad>Cem Can</AdSoyad> <KayitTarih> T22:47:00-08:00</KayitTarih> </Musteri> </NewDataSet> Bölüm 29

244 712 C# Programlama Dili DataSet içeriğini şemasıyla birlikte yazdıralım. XmlWriteMode.WriteSchema); <?xml version="1.0" standalone="yes"?> <NewDataSet> <xs:schema id="newdataset" xmlns="" xmlns:xs=" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xs:element name="newdataset" msdata:isdataset="true" msdata:usecurrentlocale="true"> <xs:complextype> <xs:choice minoccurs="0" maxoccurs="unbounded"> <xs:element name="musteri"> <xs:complextype> <xs:sequence> <xs:element name="musteriid" type="xs:int" minoccurs="0" /> <xs:element name="adsoyad" type="xs:string" minoccurs="0" /> <xs:element name="kayittarih" type="xs:datetime" minoccurs="0" /> </xs:sequence> </xs:complextype> </xs:element> </xs:choice> </xs:complextype> </xs:element> </xs:schema> <Musteri> <MusteriId>1</MusteriId> <AdSoyad>Zeynep Ilhan</AdSoyad> <KayitTarih> T22:47:00-08:00</KayitTarih> </Musteri> <Musteri> <MusteriId>2</MusteriId> <AdSoyad>Cem Can</AdSoyad> <KayitTarih> T22:47:00-08:00</KayitTarih> </Musteri> </NewDataSet> DataSet içeriğini XML belgesine yazdırırken kolonların belgeye nasıl yazdırılacağı DataColumn nesnesinin ColumnMapping özelliğiyle belirtilebilir. ColumnMapping özelliği MappingType türünde olup aşağıdaki seçeneklerden birini değer olarak alır: Element: Varsayılan olan bu seçenekte DataSet kolonu XML belgesine bir element olarak yazılır. <Musteri> <MusteriId>1</ MusteriId > </Musteri> Attribute: DataSet kolonunu belgeye bir nitelik olarak yazdırır. <Musteri MusteriId="1"></Musteri> SimpleContent: Kolondaki değer XML belgesinde düz bir metin olarak yazdırılır. Bu seçenek, içerisinde element türü kolon bulunan veya başka bir tabloyla ilişkili olan tablolar için kullanılamaz. <Musteri>1</Musteri> Hidden: Bu şekilde ayarlanmış olan kolon XML belgesine yazdırılmaz. Papatya Yayıncılık Eğitim

245 .NET Framework te XML Programlama 713 Aşağıdaki satırlarda MusteriId kolonunun XML belgesine bir nitelik olarak yazdırılması sağlanmıştır. oda.fill(ods,"musteri"); ods.tables["musteri"].columns["musteriid"].columnmapping = MappingType.Attribute; ods.writexml(@"c:\musteri.xml", XmlWriteMode.WriteSchema); <?xml version="1.0" standalone="yes"?> <NewDataSet> <Musteri MusteriId="1"> <AdSoyad>Zeynep Ilhan</AdSoyad> <KayitTarih> T22:47:00-08:00</KayitTarih> </Musteri> <Musteri MusteriId="2"> <AdSoyad>Cem Can</AdSoyad> <KayitTarih> T22:47:00-08:00</KayitTarih> </Musteri> </NewDataSet> Veritabanındaki Musteri tablosundan okunan bu veriler üzerinde değişiklik yapıp verileri bir XML belgesine DiffGram formatında yazdıralım. // Veri tabanından çektiğimiz verileri DataSet üzerinde kalıcı hale getirelim. ods.acceptchanges(); // Zeynep İlhan kaydını değiştirelim. ods.tables[0].rows[0]["adsoyad"] = "Zeynep Özilhan"; // XML belgesini DiffGram formatında kayıt edelim. ods.writexml(@"c:\musteri.xml", XmlWriteMode.DiffGram); <?xml version="1.0" standalone="yes"?> <diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"> <NewDataSet> <Musteri diffgr:id="musteri1" msdata:roworder="0" diffgr:haschanges="modified" MusteriId="1"> <AdSoyad>Zeynep Özilhan</AdSoyad> <KayitTarih> T22:47:00-08:00</KayitTarih> </Musteri> <Musteri diffgr:id="musteri2" msdata:roworder="1" MusteriId="2"> <AdSoyad>Cem Can</AdSoyad> <KayitTarih> T22:47:00-08:00</KayitTarih> </Musteri> </NewDataSet> <diffgr:before> <Musteri diffgr:id="musteri1" msdata:roworder="0" MusteriId="1"> <AdSoyad>Zeynep Ilhan</AdSoyad> <KayitTarih> T22:47:00-08:00</KayitTarih> </Musteri> </diffgr:before> </diffgr:diffgram> Belgenin içeriğinde gösterildiği gibi değişen kayıdın hem önceki hem de güncel uyarlaması saklanmıştır. Bölüm 29

246 714 C# Programlama Dili XML Belgesini DataSet e Yüklemek ADO.NET'in sunduğu kolaylıklardan biri de DataSet nesnesinin bir XML kanal veya belgesinden yüklenebiliyor olmasıdır. DataSet nesnesinin içeriğinin ne olacağı, ne tür şema ve ilişki yapısına sahip olacağı harici bir XML belgesinde belirtilebilir. DataSet verilerini bir XML belgesinden yükletmek için nesnenin ReadXml() yordamı kullanılır. ReadXml(), yeniden yüklenmiş olup bir veya iki parametre alabilen türevleri vardır. İlk parametre sözce/string, kanal (stream) ve TextReader türünde değer alıp XML belgesinin nerden okunacağını belirtir. İkinci parametre ise XmlReadMode türünde değer alıp XML belgesinin okuma modunu, yani belgedeki veri ve şemanın nasıl okunacağını belirtir. Bu parametre aşağıdaki seçeneklerden birini alır: Auto: XmlReadMode numaralandırmanın varsayılan seçeneğidir. Bu seçenekte XML belgesi tüm ayrıntılarıyla gözden geçirilir ve aşağıdaki sıra takip edilerek en uygun seçeneğe karar verilir. Eğer XML belgesi DiffGram ise DiffGram seçeneği kullanılır. Eğer DataSet nesnesi herhangi bir şemaya sahipse veya XML belgesi dahili bir şema içeriyorsa ReadSchema seçeneği kullanılır. Eğer DataSet nesnesi ve XML belgesi herhangi bir şema içermiyorlarsa InferSchema seçeneği kullanılır. Auto seçeneği, genel olarak veri yapısını bilmediğimiz XML belgeleri için tercih edilir. Yapısını bildiğimiz XML belgeri için Auto yerine XmlReadMode numaralandırıcısının doğru seçeneğini kullanmalıyız. ReadSchema: Belgedeki dahili şemayı okur ve belgedeki veri ile şemayı DataSet nesnesine yükler. Eğer DataSet zaten bir şema içeriyorsa belgedeki dahili şema yapısına göre yeni tablolar DataSet nesnesine yüklenir. Eğer herhangi bir şema içermiyorsa ve belgede tanımlı bir şema yoksa ReadSchema() yordamı herhangi bir veri yüklemez. IgnoreSchema: XML belgesindeki şema dikkate alınmayıp sadece veriler DataSet in şemasına uygun olarak DataSet e yüklenir. DataSet şemasıyla uyuşmayan kayıtlar yüklenmez; ayrıca DataSet herhangi bir şema yapısı içermiyorsa XML belgesinden hiçbir veri yüklenmez. Eğer XML belgesi DiffGram biçimindeyse IgnoreSchema seçeneği işlevsel olarak DiffGram seçeneği gibi çalışır. InferSchema: Belgedeki şema dikkate alınmayıp yükleme yapılmadan önce okunan verilere bağlı bir DataSet şeması oluşturulur. DiffGram: Bu seçenekte DiffGram formatındaki belge okunur ve veriler var olan şemaya eklenir. DiffGram, yeni ve mevcut kayıtlardan tekil tanımlayıcı (unique identifier) değerleri eşleşenleri birleştirir. Fragment: Belgeyle ilişkili kanalın sonuna kadar XML parçalarını (fragment) okur. DataSet şemasıyla uyuşan parçaları DataSet in uygun tablosuna ekler uyuşmayanları da dikkate almaz. Papatya Yayıncılık Eğitim

247 .NET Framework te XML Programlama XmlDataDocument Sınıfı Bilindiği gibi DataSet nesnesi verileri DataTable nesnelerinde SQL ifadelerle sorgulanabilecek şekilde ilişkisel formatta saklar. Verileri hiyerarşik formatta saklamak için de XmlDocument nesnesi kullanılır. Bu formattaki veriler de XPath ifadeleriyle sorgulanır. Yani, şu ana kadar ele alınan ADO.NET nesneleri verileri ilişkisel veya hiyerarşik olmak üzere yalnızca tek bir görünümde sunar. ADO.NET platformunda verileri hem ilişkisel hem de hiyerarşik olarak görünmesine sağlamak için XmlDataDocument sınıfı geliştirildi. XmlDocument sınıfından türemiş olan XmlDataDocument sınıfı hem ADO.NET in XML ile ilgili nesnelerinden gelen hiyerarşik veriyi, hem de DataSet nesnesinden beslenen ilişkisel veriyi desteklemek üzere tasarlanmıştır. XmlDataDocument sınıfının en önemli özelliği ilişkili olduğu XmlDocument veya DataSet ile senkronize olmasıdır. Yani bu iki farklı görünüm tarafından yapılan değişiklikleri eş zamanlı olarak algılar. XmlDataDocument sınıfı XmlDocument sınıfının üyelerini desteklediği gibi ek olarak aşağıdaki üyelere sahiptir: DataSet : XmlDataDocument in içeriğini sağlayan DataSet nesnesini temsil eder. GetElementFromRow(): DataRow türünde parametre alır. DataSet tarafındaki DataRow un XML nesnesi tarafında ilişkili olduğu XmlElement ini döndürür. GetRowFromElement(): XmlElement türünde parametre alır. XML nesnesi tarafındaki XmlElement in DataSet tarafında ilişkili olduğu DataRow u döndürür. Load() : Stream, string, TextReader veya XmlReader türlerinde parametre alarak XmlDataDocument nesnesinin yüklenmesini sağlar. Aşağıdaki satırlarda veri tabanındaki Musteri tablosu DataAdapter aracılığıyla DataSet nesnesine aktarılmış ve buna bağlı bir XmlDataDocument nesnesi tanımlanmıştır. DataSet ods = new DataSet(); oda.fill(ods, "Musteri"); // XmlDataDocument nesnesi oluşturalım. XmlDataDocument odoc= new XmlDataDocument(oDs); DataSet nesnesini kullanarak XmlDataDocument in temsil ettiği verileri okuyalım. foreach (DataRow odr in odoc.dataset.tables[0].rows) Console.WriteLine(oDr["AdSoyad"].ToString()); Zeynep Ilhan Cem Can Metin Ak Bölüm 29

248 716 C# Programlama Dili Aynı şekilde XML verilerine uygulanan XPath sorgu ifadelerini de kullanarak XmlDataDocument in temsil ettiği verileri sorgulayabiliriz. XmlNodeList ondlst; // AdSoyad elementlerinin listesini alalım. ondlst = odoc.documentelement.selectnodes("//adsoyad"); foreach (XmlNode ond in ondlst) Console.WriteLine(oNd.InnerText); Zeynep Ilhan Cem Can Metin Ak XmlDataDocument nesnesinin ilişkili olduğu DataSet nesnesinde oluşan değişiklikleri algıladığı belirtilmişti. Aşağıdaki örnekte DataSet üzerinde değişiklik yapılmış ve değişiklikten sonra XmlDataDocument nesnesinin sonucu bir disk dosyasına yazdırılmıştır. oda.fill(ods, "Musteri"); ods.tables["musteri"].columns["musteriid"].columnmapping = MappingType.Attribute; XmlDataDocument odoc= new XmlDataDocument(oDs); // Zeynep İlhan kaydını değiştirelim. ods.tables[0].rows[0]["adsoyad"] = "Zeynep Özilhan"; // DataSet içerisindeki 3. kaydı silelim. ods.tables[0].rows.remove(ods.tables[0].rows[2]); // XML belgesini yazdıralım. odoc.save(@"c:\musteri.xml"); <NewDataSet> <Musteri MusteriId="1"> <AdSoyad>Zeynep Özilhan</AdSoyad> <KayitTarih> T22:47:00-08:00</KayitTarih> </Musteri> <Musteri MusteriId="2"> <AdSoyad>Cem Can</AdSoyad> <KayitTarih> T22:47:00-08:00</KayitTarih> </Musteri> </NewDataSet> İlişkili Tablolar ve XML ADO.NET ortamında bellek üzerindeki veri tablolarını birbirleriyle ilişkilendirebilir ve veritabanındaki ilişkili tabloları ilişki bilgilerini koruyacak şekilde DataSet nesnesine yükleyebiliriz. Bununla birlikte ADO.NET, DataSet içindeki ilişki bilgileri XML belgelerine yazma ve okuma esnasında da korunmasını sağlar. Tablolar arasındaki ilişkiler DbDataRelation sınıfı tarafından yönetilir. Papatya Yayıncılık Eğitim

249 .NET Framework te XML Programlama 717 string CnnStr SqlConnection ocnn = new SqlConnection(CnnStr); // Kategori tablosunu isteyelim. SqlCommand ocmd1 = new SqlCommand("SELECT KategoriId,KategoriAd FROM Kategori", ocnn); SqlDataAdapter oda1 = new SqlDataAdapter(oCmd1); // Urun tablosunu isteyelim. SqlCommand ocmd2 = new SqlCommand("SELECT UrunId,KategoriId,UrunAd FROM Urun", ocnn); SqlDataAdapter oda2 = new SqlDataAdapter(oCmd2); DataSet ods = new DataSet("KategoriUrunler"); oda1.fill(ods, "Kategori"); oda2.fill(ods, "Urun"); // Urun ve Kategori tabloları arasında KategoriId kolonu üzerinde ilişki kuralım. DataRelation kturrel = ods.relations.add("kturler", ods.tables["kategori"].columns["kategoriid"], ods.tables["urun"].columns["kategoriid"]); ods.writexml(@"c:\urun.xml"); [Urun.Xml] <KategoriUrunler> <Kategori> <KategoriId>1</KategoriId> <KategoriAd>Kategori1 </KategoriAd> </Kategori> <Kategori> <KategoriId>2</KategoriId> <KategoriAd>Kategori2 </KategoriAd> </Kategori> <Urun> <UrunId>1</UrunId> <KategoriId>1</KategoriId> <UrunAd>Urun1 </UrunAd> </Urun> <Urun> <UrunId>2</UrunId> <KategoriId>1</KategoriId> <UrunAd>Urun2 </UrunAd> </Urun> <Urun> <UrunId>3</UrunId> <KategoriId>2</KategoriId> <UrunAd>Urun3 </UrunAd> </Urun> <Urun> <UrunId>4</UrunId> <KategoriId>1</KategoriId> <UrunAd>Urun4 </UrunAd> </Urun> </KategoriUrunler> Bölüm 29

250 718 C# Programlama Dili XML belgesinin içeriğinden görüldüğü gibi ana tablo olan Kategori ve alt tablo olan Urun tablosu birbirinden bağımsız yazılmıştır. Belge içerisindeki tabloların ilişkilerini iç içe göstermek için DbDataRelation sınıfının Nested özelliği true olarak düzenlenir. DataRelation kturrel = ods.relations.add("kturler", ods.tables["kategori"].columns["kategoriid"], ods.tables["urun"].columns["kategoriid"]); kturrel.nested = true; <KategoriUrunler> <Kategori> <KategoriId>1</KategoriId> <KategoriAd>Kategori1 </KategoriAd> <Urun> <UrunId>1</UrunId> <KategoriId>1</KategoriId> <UrunAd>Urun1 </UrunAd> </Urun> <Urun> <UrunId>2</UrunId> <KategoriId>1</KategoriId> <UrunAd>Urun2 </UrunAd> </Urun> <Urun> <UrunId>4</UrunId> <KategoriId>1</KategoriId> <UrunAd>Urun4 </UrunAd> </Urun> </Kategori> <Kategori> <KategoriId>2</KategoriId> <KategoriAd>Kategori2 </KategoriAd> <Urun> <UrunId>3</UrunId> <KategoriId>2</KategoriId> <UrunAd>Urun3 </UrunAd> </Urun> </Kategori> </KategoriUrunler> Papatya Yayıncılık Eğitim

251 .NET Framework te XML Programlama 719 Bu durumda her kategori elementinin altında o sınıfa ait ürünlerin kayıtlar yazılır NET Serileştirme İşlemleri Serileştirme, bir nesnenin o anki durumunu/içeriğini belirli bir formata dönüştürüp geçici veya kalıcı bir kaynak üzerinde depolama işlemidir. Bu kaynak bir disk olabildiği gibi bellek veya ağ üzerindeki bir kanal da olabilir..net Framewok ün birçok sınıfında gizli olarak kullanılan bu işlem daha çok.net Framework ün en önemli bileşenlerinden olan Remoting mimarisinde etkilidir. Bu mimaride veriler serileştirilerek farklı uygulamalar arasında taşınır. Serileştirilmiş veriyi geri elde etme yani orijinal nesne haline çevirme işlemine de ters-serileştirme (deserialization) denilir. Bir sınıfın serileştirmeyi desteklemesi için Serializable özniteliğini uygulamış olması gerekir. Ayrıca sınıfı ISerializable arabiriminden türeterek sınıfın serileştirilmesiyle ilgili ayrıntılı ayarlar yapılabilir. Önceki örneklerde DataSet nesnesini WriteXml() yordamını kullanarak, güncel içeriğini bir XML belgesine saklayabiliyor olmamız veya ReadXml() aracılığıyla XML içeriği DataSet içerisine aktarabiliyor olunmasının temelinde DataSet nesnesinin ISerializable ve IXmlSerializable arabirimlerinden türemiş olması yatar..net Framework üç tür serileştirme yöntemi sunar: İkili Serileştirme (Binary Serialization) XML (XML Serialization) SOAP Serileştirme (SOAP Serialization) İkili serileştirme nesnenin son durumunu bit bit serileştirip bu bilgiyi diskte bir dosyaya saklamak veya.net Remoting mimarisi aracığıyla birbirinden bağımsız uygulamalar arasında taşımak için tercih edilir. Göreceli olarak daha hızlıdır. İkili serileştirmeden System.Runtime.Serialization.Formatters.Binary isim-uzayı altında bulunan BinaryFormatter sınıfı sorumludur. Bu sınıfın Serialize() yordamı kullanılarak nes- Bölüm 29

252 720 C# Programlama Dili neler ikili formata dönüştürülür; aynı şekilde Deserialize() kullanılarak ikili içerik uygun nesne yapısına dönüştürülür. Aşağıdaki örnekte basit bir sınıf oluşturulmuş ve bu sınıf türündeki bir nesnenin ikili formata dönüştürülmesi gösterilmiştir. [Serializable] class Musteri { public int MusteriId; public string AdSoyad; public DateTime DogumTarih; // Serileştirilecek Musteri nesnesini oluşturalım. Musteri om = new Musteri(); om.musteriid = 1; om.adsoyad = "Ali Çetin"; om.dogumtarih = new DateTime(1978,02,28); // Serileştirmenin yapılacağı bir kanal oluşturalım. // Burada örnek olarak dosya kanalı kullanacağız. FileStream ofs = new FileStream(@"C:\Musteri.bin", FileMode.Create); // İkili serileştirme yapacak nesneyi oluşturalım. BinaryFormatter obf = new BinaryFormatter(); obf.serialize(ofs, om); Bu durumda Musteri nesnesi ikili tabanda serileştirilir ve oluşan çıktı C:\Musteri.bin dosyasına yazdılır. Bu dosyadaki içerik ikili formatta olduğu için okunabilir bir yapıda değildir. Bu dosyayı ters serileştirme işleminden geçirip dosyadaki verileri gerçek bir Musteri nesnesine dönüştürelim. Bunun için BinaryFormatter sınıfının Deserialize() yordamı kullanılır. // Serileştirilecek Musteri nesnesini oluşturalım. Musteri om = null; // Nesneyi kendisinden ters-serileştirecek kanalı tanımlayalım. FileStream ofs = new FileStream(@"C:\Musteri.bin", FileMode.Open); // İkili ters-serileştirme yapacak nesneyi oluşturalım. BinaryFormatter obf = new BinaryFormatter(); // kanaldaki içeriği om nesnesine ters-serileştirme yapalım. om = (Musteri)oBf.Deserialize(oFs); Console.Write(oM.MusteriId + ", "+ om.adsoyad +", "+ om.dogumtarih); 1, Ali Çetin, :00:00 İkili serileştirmede sınıf içerisinde serileştirme işlemine dahil edilmesi istenilmeyen üyeler NonSerialized özniteliğiyle belirtilir. ADO.NET in en önemli bileşeni olan DataSet nesnesinin verileri XML formatında sakladığını biliyoruz. Özellikle farklı konumlardaki uygulamalar arasında yüksek miktarda- Papatya Yayıncılık Eğitim

253 .NET Framework te XML Programlama 721 ki DataSet nesnelerinin taşınması veri iletişim süresini uzatabilmektedir. DataSet nesnesi ikili serileştirme işleminden geçirilse bile temelde veriyi yine XML formatında sakladığı için veri miktarında ciddi bir küçülme olmamaktadır. Bu sorunu aşmak için ADO.NET 2.0 ile birlikte DataSet sınıfına RemotingFormat isimli özellik eklendi. Bu özellik SerializationFormat.Binary veya Serialization-Format.Xml değerlerinden birini alır. Böylece Binary seçeneği seçilerek gerçek anlamda ikili serileştirme işlemi yapılmış olur. XML serileştirme uygulamadaki bir nesnenin güncel durum bilgisini XML formatına dönüştürme amaçlı kullanılır. XML, açık standart bir format olduğu ve işlenmesi kolay olduğu için genellikle farklı uygulamalar arasında veri paylaşımında tercih edilir. Örneğin uygulamadaki bir DataSet nesnesini serileştirip İnternet üzerinden başka bir uygulamaya gönderebiliriz. Karşıdaki uygulama aldığı XML içeriği ters serileştirme işleminden geçirip DataSet nesnesinin son durumunu elde etmiş olur. XML serileştirmenin çıktısı doğası gereği ikili çıktıya göre daha anlaşılır bir formata sahiptir. XML serileştirmenin bazı kısıtlamaları bulunur: Sadece public nitelikli özellik ve alan türündeki üyeler serileştirilebilir. Yordam, indisleyici, yalnızca-okunur özellik ve private nitelikli üyeler serileştirilemez. Yalnızca-okunur koleksiyon türündeki üyeler serileştirilebilir. Sadece verinin kendisini serileştirir; veri türleri bilgilerini serileştirmez. Bu yüzden ters serileştirme işleminde dönüştürülen nesnenin aynı türde olması garantilenmez. XML tabanlı serileştirmede rol oynayan sınıflar System.Xml.Serialization kütüphanesi altında bulunur. XML tabanlı serileştirme işleminden sorumlu olan XmlSerializer sınıfıdır. Bu sınıfın Serialize() yordamı nesnenin güncel içeriğini XML formatına dönüştürür; Deserialize() yordamı, Serialize() ın tersini gerçekleştirir. Yani XML verisinden uygun nesneyi oluşturur. Bu sınıfın diğer önemli üyesi olan CanDeserialize() yordamı XML verisinin ters serileştirme işlemi için uygun olup olmadığını denetler. XML serileştirme işleminde serileştirilecek nesnenin türü XmlSerializer sınıfının yapıcı yordamına parametre olarak geçilir. // Serileştirilecek müşteri nesnesini oluşturalım. Musteri om = new Musteri(); om.musteriid = 1; om.adsoyad = "Ali Çetin"; om.dogumtarih = new DateTime(1978, 02, 28); // Serileştirmenin yapılacağı bir kanal oluşturalım. // Burada örnek olarak dosya kanalı (file stream) kullanacağız. FileStream ofs = new FileStream(@"C:\Musteri.xml", FileMode.Create); // XML serileştirme yapacak nesneyi oluşturalım. // XmlSerializer'ın yapıcı yordamına Musteri sınıfının türü gönderilir. XmlSerializer oxs = new XmlSerializer(typeof(Musteri)); oxs.serialize(ofs, om); Bölüm 29

254 722 C# Programlama Dili Bu işlemden sonra Musteri nesnesinin o anki durumu XML formatında diske yazılır. <?xml version="1.0"?> <Musteri xmlns:xsi=" xmlns:xsd=" <MusteriId>1</MusteriId> <AdSoyad>Ali Çetin</AdSoyad> <DogumTarih> T00:00:00</DogumTarih> </Musteri> Görüldüğü gibi belge içerisinde alanların veri türleriyle ilgili tanımlama bulunmamaktadır. Şimdi bu belgedeki verileri sözkonusu Musteri sınıfına uygun bir nesneye geri yükleme yapalım. // Yeni bir Musteri nesnesi oluşturalım. Musteri om = null; // Ters-serileştirme işleminde kullanılacak serializer nesnesi. XmlSerializer oxs = new XmlSerializer(typeof(Musteri)); XmlReader oxr = new XmlTextReader(@"C:\Musteri.xml"); // XML verisinin ters-serileştirme durumunu denetleyelim. if (oxs.candeserialize(oxr)) { // XML verisini ters-serileştirme yapalım. om = (Musteri)oXs.Deserialize(oXr); Console.Write(oM.MusteriId + ", " + om.adsoyad + ", " + om.dogumtarih); 1, Ali Çetin, :00:00 System.Xml.Serialization isim-uzayı XML formatında serileştirilecek nesneyle ilgili ayrıntılı ayarlama yapmak için çeşitli öznitelikler içerir. Örneğin nesneye ait bir özelliği XML belgesinde öznitelik olarak tanımlamak için XmlAttribute niteliği, serileştirme işlemine dahil edilmeyecek alanlar için XmlIgnore niteliği, alanın XML belgesindeki elemen yapısını düzenlemek için XmlElement niteliği kullanılır. Musteri sınıfını aşağıdaki gibi düzenleyelim. public class Musteri { [XmlAttribute("MusteriKodu")] public int MusteriId; [XmlElement("MusteriAdiSoyadi")] public string AdSoyad; [XmlIgnore] public DateTime DogumTarih; Bu durumda örnekte kullandığımız nesnenin XML çıktısı şu şekilde olacaktır: <?xml version="1.0"?> <Musteri xmlns:xsi=" xmlns:xsd=" MusteriKodu="1"> <MusteriAdiSoyadi>Ali Çetin</MusteriAdiSoyadi> </Musteri> Papatya Yayıncılık Eğitim

255 .NET Framework te XML Programlama 723 SOAP serileştirme XML serileştirme işleminin bir türü olup SOAP formatında serileştirme yapmak için kullanılır. Basit nesne erişim protokolü olarak tanımlanan SOAP (Simple Object Access Protocol), uygulamalar arasında HTTP üzerinden veri iletimini sağlamak amacıyla geliştirilmiş XML tabanlı bir iletişim protokolüdür. Bu protokol, dağıtık uygulamalarda ve Web servislerinin haberleşmesinde kullanılılır..net Framework te nasıl ki ikili serileştirme BinaryFormatter isimli formatter nesnesi tarafından gerçekleştiriliyorsa SOAP serileştirme de SoapFormatter isimli formatter nesnesi tarafından gerçekleştirilir. SoapFormatter sınıfı System.Runtime.Serialization.Formatters.Soap.dll kütüphanesi altında bulunmaktadır. SOAP serileştirme için projemize bu kütüphaneyi referans olarak eklemeyi unutmamalıyız. SoapFormatter sınıfının da serileştirme işlemini yapan Serialize() ve tersserileştirme işlemini yapan Deserialize() yordamı bulunur. [Serializable] public class Musteri { public int MusteriId; public string AdSoyad; public DateTime DogumTarih; // Serileştirilecek müşteri nesnesini oluşturalım. Musteri om = new Musteri(); om.musteriid = 1; om.adsoyad = "Ali Çetin"; om.dogumtarih = new DateTime(1978, 02, 28); // Serileştirmenin yapılacağı bir kanal oluşturalım. FileStream ofs = new FileStream(@"C:\Musteri.xml", FileMode.Create); SoapFormatter osf = new SoapFormatter(); osf.serialize(ofs, om); Bu şekilde serileştirilmiş nesne aşağıdaki içeriğe dönüştürülür: <SOAP-ENV:Envelope xmlns:xsi=" xmlns:xsd=" xmlns:soap- ENC=" xmlns:soap- ENV=" xmlns:clr=" SOAP- ENV:encodingStyle=" <SOAP-ENV:Body> <a1:program_x002b_musteri id="ref-1" xmlns:a1=" 0Version%3D %2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull"> <MusteriId>1</MusteriId> <AdSoyad id="ref-3">ali Çetin</AdSoyad> <DogumTarih> T00:00: :00</DogumTarih> </a1:program_x002b_musteri> </SOAP-ENV:Body> </SOAP-ENV:Envelope> Serileştirilmiş SOAP formatındaki bu veriyi uygulamadaki Musteri nesnesine dönüştürelim. Bölüm 29

256 724 C# Programlama Dili Musteri om = null; FileStream ofs = new FileStream(@"C:\Musteri.xml", FileMode.Open); SoapFormatter osf = new SoapFormatter(); // SOAP verisini deserialize edelim. om = (Musteri)oSf.Deserialize(oFs); Console.Write(oM.MusteriId + ", " + om.adsoyad + ", " + om.dogumtarih); 1, Ali Çetin, :00:00 Serileştirmeyle ilgili olarak yine bizim tarafımızdan yazılmış başka bir nesneyle ilişkili bir nesneyi kullanalım. Musteri sınıfına AdresBilgi isimli başka bir sınıfın türünden olan Adres isimli bir özellik ekleyelim. [Serializable] public class Musteri { public int MusteriId; public string AdSoyad; public DateTime DogumTarih; public AdresBilgi Adres; [Serializable] public class AdresBilgi { public string CaddeSokak; public string Sehir; // Adres nesnesi AdresBilgi oadres = new AdresBilgi(); oadres.caddesokak = "A Cd. B Sokak"; oadres.sehir = "İstanbul"; // Musteri nesnesi Musteri om = new Musteri(); om.musteriid = 1; om.adsoyad = "Ali Çetin"; om.dogumtarih = new DateTime(1978, 02, 28); om.adres = oadres; Bu verilere göre XML ve SOAP serileştirme işleminde bulunalım. // Kanal nesnesi FileStream ofs = new FileStream(@"C:\Musteri.xml", FileMode.Create); XmlSerializer oxs = new XmlSerializer(typeof(Musteri)); oxs.serialize(ofs, om); <?xml version="1.0"?> <Musteri xmlns:xsi=" xmlns:xsd=" <MusteriId>1</MusteriId> <AdSoyad>Ali Çetin</AdSoyad> <DogumTarih> T00:00:00</DogumTarih> <Adres> <CaddeSokak>A Cd. B Sokak</CaddeSokak> <Sehir>İstanbul</Sehir> </Adres> </Musteri> Papatya Yayıncılık Eğitim

257 .NET Framework te XML Programlama 725 Adres düğümü Musteri düğümünün bir alt elementi olarak tanımlanmıştır. SOAP tabanlı serileştirmeye bakalım. // Kanal nesnesi FileStream ofs = new FileStream(@"C:\Musteri.xml", FileMode.Create); SoapFormatter osf = new SoapFormatter(); osf.serialize(ofs, om); <SOAP-ENV:Envelope xmlns:xsi=" xmlns:xsd=" xmlns:soap- ENC=" xmlns:soap- ENV=" xmlns:clr=" SOAP- ENV:encodingStyle=" <SOAP-ENV:Body> <a1:program_x002b_musteri id="ref-1" xmlns:a1=" 0Version%3D %2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull"> <MusteriId>1</MusteriId> <AdSoyad id="ref-3">ali Çetin</AdSoyad> <DogumTarih> T00:00: :00</DogumTarih> <Adres href="#ref-4"/> </a1:program_x002b_musteri> <a1:program_x002b_adresbilgi id="ref-4" xmlns:a1=" %2C%20Version%3D %2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnu ll"> <CaddeSokak id="ref-5">a Cd. B Sokak</CaddeSokak> <Sehir id="ref-6">istanbul</sehir> </a1:program_x002b_adresbilgi> </SOAP-ENV:Body> </SOAP-ENV:Envelope> Sonuçta görüldüğü gibi Adres alanı için ayrı bir veri türü tanımlaması yapılmış ve Adres alanından o veri türüne referans verilmiştir Özet.NET Framework içerisinde XML türü veri ve belgeleriyle çalışmak için çeşitli sınıflar bulunmaktadır. Bu sınıflar, System.Xml.dll kütüphanesinde toplanmıştır. XML belgelerini işlemek için genellikle XmlReader ve XmlWriter taban sınıfları kullanılır. Soyut niteliğindeki bu iki sınıf XML veriyi okumak ve yazmak için gerekli yordamları içerir. Serialization, bir nesnenin o anki durumunu (içeriğini) belirli bir formata dönüştürüp geçici veya kalıcı bir kaynak üzerinde depolama işlemidir. Bu kaynak bir disk olabildiği gibi bellek veya network üzerindeki bir kanal da olabilir..net Framewok ün birçok sınıfında gizli olarak kullanılan bu işlem daha çok.net Framework ün en önemli bileşenlerinden olan Remoting mimarisinde etkilidir. Bu mimaride veriler serileştirilerek farklı uygulamalar arasında taşınır. Serileştirilmiş veriyi geri elde etme yani orijinal nesne haline çevirme işlemini de ters-serileştirme denilir. Bölüm 29

258 726 C# Programlama Dili Sorular 29.1).NET Framework te XML verilerin programlanması için hangi sınıflar kullanılır? 29..2) DOM ve SAX çözümleyici modellerini açıklayınız? 29.3) Serileştirme işlemi nedir,.net Framework, kaç çeşit serileştirme destekler? 29.4) SOAP nedir? Hangi amaçla kullanılır? Papatya Yayıncılık Eğitim

259 30. C# 3.0 Yenilikleri C# 3.0 ile birlikte programlama dili tarafına bazı yenilikler eklendi. Bu yeniliklerin daha çok LINQ teknolojisiyle ilişkili olduğu ve onun sorgularını desteklemek amacıyla sunulduğu görülmektedir. Bu bölümde.net 3.0 ile birlikte gelmiş yenilikler incelenmektedir Bilinçsizce Türlendirilmiş Değişkenler (Implicitly Typed Variables).NET 3.0 ile birlikte değişkenlerin türlerini belirtmeden tanımlama imkanı sağlandı; var anahtar sözcüğü kullanılarak türü verilmeden bir değişken tanımlanabilir. Derleyici, değişkenin ne tür olacağına ona verilen ilk değerden anlar. Dolayısıyla böylesi değişkenlere mutlaka ilk değeri verilmelidir. var v1 = "Microsoft"; var v2 = 2007; var v3 = false; var v4 = new DataSet(); var v5 = new int[] { 1, 2, 3 ; Console.WriteLine(v1.GetType()); Console.WriteLine(v2.GetType()); Console.WriteLine(v3.GetType()); Console.WriteLine(v4.GetType()); Console.WriteLine(v5.GetType()); System.String System.Int32 System.Boolean System.Data.DataSet System.Int32[] Böylesi değişkenlerin türleri derleme anında belirlenir; herhangi bir performans sorunu olmaz. Verilen bu basit örnek ILDASM aracıyla incelendiğinde aşağıdaki MSIL kodu görülür.

260 728 C# Programlama Dili.locals init ([0] string v1, [1] int32 v2, [2] bool v3, [3] class [System.Data]System.Data.DataSet v4, [4] int32[] v5) Bu tür değişkenlerin ilk değerinin null dan farklı olması gerekir. Aşağıdaki değişken tanımları geçersiz sayılır: var x; // HATALI, ilk değeri yok. var y = { 1, 2, 3 ; // HATALI, koleksiyon nesnesi oluşturulmamış. var z = null; // HATALI, ilk değer olarak null değeri girilmiş Anonim/İsimsiz Veri Türleri (Anonymous Types).NET 2.0 daki isimsiz yordamlar gibi.net 3.0 ile birlikte isimsiz değişken türü de tanımlama imkanı elde edilmiş oldu. Bu türlerden bir değişken tanımlamak için aynı şekilde var sözcüğü kullanılır. Bunun nasıl olacağını bir örnek üzerinde gösterelim. var Kisi1 = new { KisiId = 1, AdSoyad = "Ali Korkmaz" ; var Kisi2 = new { KisiId = 2, AdSoyad = "Ayşe Korkmaz" ; // Aynı türden bu iki değişkeni birbirine eşitleyelim. Kisi1 = Kisi2; Console.WriteLine(Kisi1.KisiId +"» "+ Kisi1.AdSoyad); 2» Ayşe Korkmaz Kisi1 ve Kisi2 satırlarında görüldüğü gibi değişken ve değişkenin türü aynı anda oluşturulmuş oldu. Çalışma zamanında bu değişken türlerinin isimsiz türler olduğu algılanır ve ona göre işlem yapılır. Böylesi değişken türleri için derleyici türün yapısına uygun bir veri türü oluşturur. Programın MSIL kodu incelendiğinde tamsayı türünde KisiId ve sözce türünde AdSoyad özelliklerine sahip bir sınıfın oluşturulduğu ve Kisi1 ile Kisi2 nin bu sınıf türünden tanımlandığı görülür. Papatya Yayıncılık Eğitim

261 C# 3.0 Yenilikleri Nesne ve Koleksiyon İlklendirme (Object and Collection Initializers) Bilindiği gibi bir sınıftan nesne yaratırken o sınıfın yapıcı yordamı kullanılır. Farklı sayıda veya türde parametre kullanılması için yapıcıların yeniden yüklenmesi sağlanır..net 3.0 ile birlikte nesnenin özelliklerini tek bir yapıcı üzerinden tanımlayabileceğiz. Böylece girilecek özelliklerin sırası veya sayısına göre birden fazla yapıcı yordam oluşturulmamış olacak. public class Kisi { public int KisiId; public string AdSoyad; public AdresBilgi Adres; public class AdresBilgi { public string CaddeSokak; public string Sehir; Kisi Kisi1 = new Kisi {KisiId=1; Console.WriteLine(Kisi1.KisiId + "» " + Kisi1.AdSoyad); var Kisi2 = new Kisi { KisiId = 1, AdSoyad="Ali Korkmaz"; Console.WriteLine(Kisi2.KisiId + "» " + Kisi2.AdSoyad); AdresBilgi Kisi3Adres = new AdresBilgi { CaddeSokak = "X Cd. Y Sk.", Sehir = "İzmir" ; Kisi Kisi3 = new Kisi { KisiId = 1, AdSoyad = "Ali Korkmaz", Adres = Kisi3Adres ; Console.WriteLine(Kisi3.KisiId + "» "+ Kisi3.AdSoyad +"» "+ Kisi3.Adres.CaddeSokak +"» "+ Kisi3.Adres.Sehir); Bölüm 30

262 730 C# Programlama Dili 1» 1» Ali Korkmaz 1» Ali Korkmaz» X Cd. Y Sk.» İzmir Görüldüğü gibi Kisi isimli sınıftan aynı yapıcı yordam üzerinden parametre yapıları farklılaştırılarak üç farklı nesne oluşturuldu. Aynı mantıkla jenerik koleksiyon türündeki nesnelere başlangıç değerleri de bu şekilde verilebilir. List<int> sayilar = new List<int> { 0, 1, 2, 3, 4, 5, 6 ; Genişletme Yordamları (Extension Methods).NET 3.0 ile birlikte var olan bir veri türü yeni yordamlarla genişletilme olanağı elde edildi. Bu veri türü.net Framework ün hazır gelen derlenmiş bir sınıfı olabileceği gibi kendi yazdığımız bir sınıf ta olabilir. Genişletme yordamı yazmanın ilk kuralı, bu yordamların mutlaka statik bir sınıf içerisinde tanımlanmasıdır. İkinci kural ise genişletme yordamının ilişkili olduğu veri türü, yordamın ilk parametresinde this anahtar sözcüğüyle belirtilmesidir. Dolayısıyla genişletme yordamları mutlaka bir parametre almak zorundadır..net Framework ün System.Int32 veri türü için bir genişletme yordamı yazalım. Bu yordam çağrıldığı değerin karesini döndürecek. public static class GenisletmeYordamı { public static int KareAl(this int sayi) { return sayi * sayi; public class Program { public static void Main(string[] args) { int x = 9; int kare = x.kareal(); Console.WriteLine("9'ün karesi : {0", kare); Console.ReadLine(); 9'ün karesi : 81 Bu yöntemin kullanışlı yanı veri türü yapısını bozmadan ona yeni yordamlar kazandırıyor olmasıdır. Visual Studio IntelliSense özelliği bu yordamlar için de geçerlidir. Yanda görüldüğü gibi KareAl() tamsayı türünün dahili bir yordamı olarak görülür. Papatya Yayıncılık Eğitim

263 C# 3.0 Yenilikleri 731 Kendi yazdığımız bir veri türü için genişletme yordamı yazalım. Aşağıdaki örnekte KisiTur isimli yeni bir veri türü tanımlanmış ardından bunun için Yazdir() isminde bir genişletme yordamı yazılmıştır. public class KisiTur { public Int32 KisiId; public string AdSoyad; public static class GenisletmeYordamı { public static void Yazdir(this KisiTur Kisi) { Console.WriteLine("{0 nolu müşterinin adı : {1", Kisi.KisiId, Kisi.AdSoyad); public class Program { public static void Main(string[] args) { KisiTur okisi = new KisiTur(); okisi.kisiid = 1; okisi.adsoyad = "Ali Korkmaz"; // KisiTur için yazılmış olan genişletilmiş yordamı çağıralım. okisi.yazdir(); Console.ReadLine(); 1 nolu müşterinin adı : Ali Korkmaz Genişletme yordamları birden fazla parametre de alabilir. Aynı şekilde ilk parametre türü yordamın ilişkili olduğu veri türüyle aynı olmasına dikkat edilmelidir. Diğer parametreler bilinen normal yöntemle tanımlanır. public static class GenisletmeYordamı { public static int Toplama(this int sayi1,int sayi2) { return sayi1 + sayi2; public class Program { public static void Main(string[] args) { int sayi = 10; int sonuc = sayi.toplama(15); Console.WriteLine("İşlemin sonucu : {0", sonuc); İşlemin sonucu : 25 Genişletme yordamları diğer yordamlar gibi yeniden yüklenebilir. Yani bir genişletme yordamının aynı isimde ama parametre yapısı farklı olan türevleri yazılabilir. Bölüm 30

264 732 C# Programlama Dili public static class GenisletmeYordamı { public static int Toplama(this int sayi1, int sayi2) { Console.WriteLine("İkinci parametre Integer türünde"); return sayi1 + sayi2; public static double Toplama(this int sayi1, double sayi2) { Console.WriteLine("İkinci parametre Double türünde"); return sayi1 + sayi2; public class Program { public static void Main(string[] args) { int sayi = 10; Console.WriteLine("İşlemin sonucu : {0", sayi.toplama(15)); Console.WriteLine("İşlemin sonucu : {0", sayi.toplama(15.4)); İkinci parametre Integer türünde İşlemin sonucu : 25 İkinci parametre Double türünde İşlemin sonucu : 25,4 Genişletme yordamları sınıfların yanısıra arabirim (interface) türündeki yapılar için de yazılabilir. Böylece arabirimden türemiş sınıflar da otomatik olarak söz konusu genişletme yordamını kullanmış olur. Bu şekilde kaynak koduna erişebildiğimiz veya erişemediğimiz herhangi bir veri türüne, türün yapısını bozmadan dışarıdan yeni yordamlar ekleyebiliyoruz. Eğer object türü için bir yordam yazılırsa doğal olarak tüm veri türlerinden bu yordama erişilmiş olur Lambda İfadeleri (Lambda Expressions) Lambda ifadeleri, karmaşık algoritmaların ve uzun satırların daha kısa deyimlerle ifade edilmesini sağlar..net 3.0 ile birlikte Framework tabanlı dillerde kullanılmaya başlanan Lambda ifadeleri Pyhton ve Lisp gibi programlama dilleriyle uğraşmış kişilerin yabancı olmadığı bir konudur..net platformunda Lambda ifadelerinin çıkış amacı genişletme yordamlarıyla birlikte LINQ sorgularını yazmak olsa da uygulamada farklı alanlarda kullanılması programcıya büyük kolaylık sağlamaktadır. Lambda ifadelerinin gereksinimini anlamak için delege/temsilci (delegate) yapısından yola çıkarak bu noktaya nasıl gelindiğine bakacağız. Delege yapısını ele almamızın nedeni LINQ deyimlerinin temsilci nesnelerinin parametre olarak çağrıldığı alanlarda kullanılıyor olmasıdır. Bilindiği gibi.net teknolojisinin ilk sürümünden beri temsilci yapılarına destek verilmektedir. Nasıl ki değişkenlerin türleri varsa yordamların da yapılarına uygun Papatya Yayıncılık Eğitim

265 C# 3.0 Yenilikleri 733 türleri bulunur. Bu türleri temsil eden yapılara temsilci denilir. Teknik bir tanım yapılacak olunursa temsilciler yordamların bellekteki konumuna işaret eden işaretçilerdir. Uygulamada bir yordamın konumuna ihtiyaç duyulursa onu temsil eden temsilciyi çağırmak yeterli olur. Aşağıdaki örneğimizde tamsayı türünde iç ve dış parametrelere sahip MyDelegate isminde temsilcinin kullanımı gösterilmiştir. public class Program { public delegate int MyDelegate(int Deger); // Tanımlama public static void Main(string[] args) { MyDelegate odlg = new MyDelegate(KareAl); // Örnekleme // Veya // MyDelegate odlg = KareAl; int Kare = odlg(5); // Çağırma Console.WriteLine("5'in karesi :{0", Kare); static int KareAl(int Sayi) { return Sayi * Sayi; 5'in karesi :25 Bu örnekte ismi belli olan bir yordam kullanıldı. Aynı örneği aşağıdaki gibi anonim yordam (anonymous method) yapısını tercih ederek daha az kod satırı yazabiliriz. // Temsilcinin temsil edeceği yordam isimsiz olarak tanımlanmıştır. MyDelegate odlg = delegate(int Sayi) { return Sayi * Sayi; ; int Kare = odlg(5); C# 3.0 ile birlikte bu tür işlemlerin sözdizimini kısaltmak için LINQ ifadeleri geliştirildi. LINQ ifadeleri => operatörüyle yazılır. Örnekteki temsilciyi LINQ aracılığıyla aşağıdaki şekillerde ifade edebiliriz. MyDelegate odlg = (int sayi) => { return sayi * sayi; ; // return ifadesini kaldıralım. MyDelegate odlg = (int sayi) => sayi * sayi; // Türü dönüşümünde sorun çıkmayacaksa aşağıdaki gibi de yazabiliriz. MyDelegate odlg = sayi => sayi * sayi; Aynı şekilde birden fazla parametre de yazılabilir. // İki integer türünde parametre alan bir temsilci. public delegate int DlgToplam(int Sayi1, int Sayi2); DlgToplam odlg = (x,y) =>x+y; int Sonuc = odlg(7, 4); Bölüm 30

266 734 C# Programlama Dili Lambda ifadeleri, LINQ sorguları yazılırken kısa sözdizimiyle sorgunun okunurluğunu arttırır. Bu ifadelerin kolaylık sağladığı diğer alan da koleksiyon nesneleridir. Koleksiyon nesnelerinin temsilci türünde parametre alan yordamlarında Lambda ifadeleri kullanılabilir. Örneğin koleksiyon tabanlı kaynaklarda arama yapmak için kullandığımız Find(), FindAll() ve FindIndex() bu türden yordamlardır. Aşağıdaki örnekte koleksiyonda bulunan 5 haneli kayıtların listesi alınmaktadır. List<string> kisiler = new List<string>() { "Ali", "Büşra", "Zeynep" ; // Uzunluğu 5 olan kayıtları sorgulayalım. var besliler = kisiler.findall(ad=>ad.length==5); Eğer Lambda ifadeleri kullanılmamış olunsa idi bu basit örnek için aşağıdaki gibi harici bir yordam yazılacaktı. var besliler = kisiler.findall(beslibul); static bool BesliBul(string ad) { return ad.length == 5; Lambda ifadelerini kullandığımız durumlarda derleyici ifadeye uygun yordam o- luşturur. En son yaptığımızın örneği Reflector aracılığıyla kodunu incelediğimizde temsili bir yordamın oluşturulduğunu görürüz Parçalı Yordam.NET 3.0 ın sunduğu yeni özelliklerinden biri de parçalı yordam (partial method) yapılarıdır. Bu yapı aracılığıyla bir yordamın tanımını ve içeriğini farklı dosyalarda saklayabileceğiz. Bu yordamlar parçalı sınıflar gibi partial sözcüğüyle tanımlanır. Papatya Yayıncılık Eğitim

267 C# 3.0 Yenilikleri 735 Parçalı yordamlarla ilgili birkaç zorunlu madde bulunmaktadır: Parçalı yordamlar, ancak parçalı sınıflar içerisinde tanımlanabilir ve içeriği o- luşturulabilir. Parçalı yordamlar, sadece parametre döndüremeyen (void) yordamlar olabilir. Parçalı yordamlar yalnızca private nitelikli olabilir. [Class1.cs] partial class Musteri { string adsoyad; public string AdSoyad { get { return adsoyad; set { adsoyad = value; MusteriKaydet(value); partial void MusteriKaydet(string name); [Class2.cs] using System; partial class Musteri { partial void MusteriKaydet(string name){ Console.WriteLine("Müşteri bilgisi, veritabanına kayıt edildi."); Ana program içerisinde Musteri sınıfından bir örnek oluşturup parçalı yordamın yansımasını görelim. public static void Main(string[] args) { Musteri oms = new Musteri(); oms.adsoyad = "Ahmet Kaymaz"; Müşteri bilgisi, veritabanına kayıt edildi Yeni Nesil Veri Programlama Modeli (ADO.NET 3.5) Microsoft.NET Framework 3.0 ile birlikte ADO.NET in yeni bir uyarlamasını yayınlamadı..net Framework 3.5 ile birlikte yeni eklentiler kazandırılmış ADO.NET 3.5 sürümü yayınlandı. Microsoft, ADO.NET 3.5 sürümleriyle birlikte veritabanı uygulama mimarisinde büyük kolaylık sağlayan Object Relational Mapping (O/R Mapping) yöntemini destekleyecek önemli adımlar attı. Bu amaçla ADO.NET Entity Framework aracı geliştirildi. Bölüm 30

268 736 C# Programlama Dili Günümüzde özellikle veritabanına dayalı uygulamalar katmanlı mimari (multi-tier architecture) üzerine kurulur. Bu kavram dahilinde uygulamalar veri (data), iş (business logic) ve sunum (presentation) olarak üç temel katmandan oluşur. Tasarım sürecinde ilk sırada bulunan veri katmanı oluşturulurken çeşitli veri modelleri kullanılır. Verileri mantıksal düzeyde düzenlemek için kullanılan kavramlar, yapılar ve işlemler bütününe veri modeli denir. 70 lı yıllardan bu yana Veritabanı Yönetim Sistemlerinin (Database Management System DBMS [VTYS]) değişmesiyle birlikte çeşitli veri modelleri geliştirildi: Sıradüzensel veri modeli (Hierarchical data model) Ağ veri modeli (Network data model) İlişkisel veri modeli (Relational data model) Nesneye yönelik veri modeli (Object oriented data model) Her VTYS nin kendine özgü veri modeli bulunur. Veri modeli kullanılarak veritabanının kavramsal ve dış şemaları oluşturulur. Burada bu modellerle ilgili kavramları tanımlayacağız ve VTYS den bağımsız tasarım yapmamızı sağlayan ER modelini ayrıntılı ele elacağız Varlık-İlişki Modellemesi Günümüzde bir veritabanı uygulamasının başarılı olmasını sağlayacak ilk parametre iyi bir veritabanı tasarımına sahip olmasıdır. Bir uygulama geliştirileceği zaman öncelikle bu uygulamanın ne tür bir uygulama olduğu ve ne tür verileri içereceği belirlenir. Veritabanı tasarımı, bu soruların yanıtları doğrultusunda şekillenir. İlişkisel veritabanı sistemlerinde veritabanı tasarımının ilk adımı veri modellemedir. Veri modelleme, veritabanı verilerinin ve bunların arasındaki ilişkinin organizasyon içindeki herkesin rahatlıkla anlayabileceği metin ve şekillerle ifade edilmesidir. Bilindiği gibi organizasyon içindeki kullanıcı, tasarımcı ve programcı gibi teknik veya teknik olmayan kişilerin veriye bakış açıları ve veriyi tanımlamaları farklılık arzeder. Hazırlanacak olan veritabanı tasarımında bu kişilerin hepsinin rolü olduğu için bu kişiler arasında ortak bir dil oluşturmak gerekir. Bu dilin temeli hiç şüphesiz gerçek dünya varlıklarıdır. Veritabanını kullanacak olan kullanıcı, tasarlayacak olan tasarımcı ve kodlayacak programcı ihtiyacını ancak gerçek varlıklar (nesneler) üzerinden daha kolay anlatabilir. Fakat gerçek dünyada varlıklar daha karmaşık özellik ve yeteneklere sahip olduğu için bu varlıklar üzerinden ifade edilmiş yapıyı, veri modellemesi aracılığıyla daha anlaşılır hale getirmek gerekir. Buradaki amaç, organizasyon içindeki herkesin gördüğü zaman kolaylıkla anlayabileceği ve veritabanı yapısını gözünde canlandırabileceği bir veri modeli çıkarmak ve bu model üzerinden organizasyon ihtiyaçlarını doğru şekilde karşılayacak fiziksel bir veritabanı tasarımına zemin hazırlamaktır. Papatya Yayıncılık Eğitim

269 C# 3.0 Yenilikleri 737 Veritabanı modellemenin üç türü vardır. Bu türler aynı zamanda veritabanı model veya tasarımın aşamalarını da bildirir. Kavramsal tasarım (Conceptual design): Veritabanının hangi verileri içereceği ve bu veriler arasındaki ilişkilerin belirlendiği aşamadır. Bu aşamanın en ö- nemli özelliği veritabanı nesneleri herhangi bir VTYS şemasına bağlı kalınmadan modellenir. Bu aşamada organizasyon içindeki herkes aktif olabilir. Kavramsal modelleme mantıksal modellemenin çözümlenmesini ve tasarımını kolaylaştırmak için atılmış bir adımdır. Mantıksal tasarım (Logical design): VTYS nin tanımlandığı aşamadır. Kavramsal tasarımda modellenen yapı, karar verilmiş olan VTYS ye uygun şemaya dönüştürülür. Veritabanında kullanılacak veri türleri, tablo üzerindeki anahlarlar ve tablo satırlarının birbirleriyle ilişkileri bu aşamada belirlenir. Bu aşamada daha çok tasarımcı ve programcı rol oynar. Fiziksel tasarım (Physical design): Verilerin hangi disk veya başka fiziksel depolama sistemlerinde nasıl tutulacağı, ne tür işlemci yapılarının kullanılacağı, aygıtların verilere hangi yol ve kurallar dahilinde ulaşacağı ve bunların doğrultusunda VTYS yazılımın hangi işletim sistemi üzerinde koşacağı belirlenir. Daha çok veritabanı programcısının aktif rol oynadığı bu aşamada oraya çıkan modelin daha önce organizasyon elemanlarıyla birlikte karar verilmiş olan kavramsal ve mantıksal modelle olan uyumluluğu test edilir. Kısaca, kavramsal ve mantıksal tasarım aşamasında, veritabanı şemasına karar verildiğini, fiziksel tasarım a- şamasında da veritabanının fiziksel yapısına karar verildiğini söyleyebiliriz. Bu aşamalardan sonraki süreç, uygulama programının yazılarak kullanıcıların belirlenmiş yetkiler ölçüsünde veriye ulaşmasıdır. Kendisi soyut bir yapı olan veri modeli, ANSI-SPARC mimarisine göre kavramsal (conceptual), dış (external) ve iç (internal) olmak üzere üç seviyeli soyutlama derecesine sahiptir. Bu seviyeler, veri ortamının organizasyon içindeki kişiler tarafından görünen görünümü temsil eder. Dış seviye, veritabanının kullanıcıya olan görüntüsü ifade eder. Her kullanıcının özgül bakışı ve bilgisi doğrultusunda birçok dış görünüm oluşturulabilir. Bu görünümler kullanıcı programları veya doğrudan SQL cümleleriyle oluşturulur. Daha çok veritabanı sahiplerinin hazırladığı ve veritabanının üst düzeyde soyutlanmış hali olan kavramsal seviye, veritabanının kuş bakışı görüntüsünü temsil eder. Bu seviyede veritabanında hangi verilerin saklandığı ve bu veriler arasındaki ilişki görünür. Kavramsal modelde veritabanı yazılım ve donanımla ilgili bilgi bulunmaz yani VTYS den bağımsız bir görüntü sunar. İç seviye ise VTYS ye bağımlı olup veritabanının VTYS ye göre hazırlanmış şemasını belirtir. Veritabanının en alt düzeyde soyutlanmış olan bu seviyede verilerin veritabanının nasıl saklandığı ifade edilir. Aynı zamanda fiziksel seviye olarak ta bilinir. Kitabın konusu gereği veritabanı tasarımı konusunda bu kadar giriş yapmamız yeterli olacaktır. Asıl konumuz kavramsal tasarım aşamasında en çok kullanılan yöntem olan Varlık-İlişki (Entity-Relationship, ER) modelidir. ER modeli, kavramsal veri modelini herhangi bir VTYS şemasına bağlı kalmadan ana hatlarıyla ER Bölüm 30

270 738 C# Programlama Dili diyagramı üzerinde şematik olarak gösterme tekniğidir. Bu teknik, organizasyon içerisindeki teknik veya teknik olmayan kişiler arasında verinin farklı görünümlerini ortak bir kalıba dönüştürür ve veritabanı uygulamalarının daha kolay geliştirilmesini sağlar. ER diyagramları için genel olarak UML gösterimleri kullanılır. ER yönteminin çıkış amacı tasarımcıların oluşturduğu nesne modelini ve kavramını anlamayan ilişkisel veritabanı yönetim sistemlerine kolaylıkla uyarlayabilmektir. ER modeli, teknik olarak varlıklar (entities) ve ilişkiler (relationships) koleksiyonundan oluşur; temelinde bu bölümün de anahtar kelimesi olan varlık bileşeni bulunur. Varlık ifadesi, Yazılım Mühendisliği literatüründe gerçek dünyadaki anlamıyla kullanılır. Yani soyut veya somut olarak var olan ve benzerlerinden ayırt edilebilen herşeye varlık denilir. Örneğin gerçek dünyada insanoğlu, araba ve devlet nesneleri varlık olarak nitelendirildiği gibi nesne tabanlı programlamada sınıflar, veritabanı yönetim sisteminde de tablolar varlık kümesi olarak nitelendirilir. Her varlık, kendisini diğer varlıklardan ayırt eden öznitelik olarak bilinen karakteristiklere sahiptir. Veritabanındaki tablolar, satır ve sütunlardan oluşur. Tablo satırlarının herbiri bir tek varlığı temsil eder. Varlığın herbir niteliği bir sütunda temsil edilir. Örneğin bir e-ticaret projesinde Musteri bir varlık, onun AdSoyad bilgisi, bu varlığa ait bir nitelik olarak tanımlanabilir. AdSoyad bilgisinin Murat Şensoy olarak belirlenmesi bu niteliğin değeridir (data value). Özniteliğin alabileceği veya aldığı değerler etki alanı (domain) olarak tanımlanır. VTYS ler varlık kümesi ve onların arasındaki etkileşimden oluşur. Varlıklar arasındaki etkileşim varlık ilişkisi olarak tanımlanır. Varlıklar arasında üç tür ilişki kurulur: Birden çoğa ilişki (one-to-many relationship): Bir tablonun bir kaydına karşılık ikinci tabloda birden fazla kaydın olmasıdır. Siparis ve Musteri tabloları a- rasındaki ilişkidir. Bir sipariş, sadece bir müşteriye ait olabilir fakat bir müşterinin birden fazla siparişi olabilir. Bu ilişki 1:M ile temsil edilir. Çoktan çoğa ilişki (many-to-many relationship): Bir tablonun birden fazla kaydı, diğer tablonun birden fazla kaydıyla ilişkilidir. M:N ile temsil edilen bu ilişki genellikle üçüncü bir tablo üzerinde oluşturulur. Ogrenci ve Kurs tablolarını örnek olarak verebiliriz. Bir öğrenci birden fazla kursa katılabilir aynı şekilde bir kursa birden fazla öğrenci katılabilir. Bu ilişki OgrenciKurs tablosunda tutulur. Birden bire ilişki (one-to-one relationship): Bir müşterinin bir şifresi var ve her bir şifre bir müşteriye aittir. Bu durum, bir tablo ilişkisinden ziyade ikinci tablonun birinci tablonun uzantısı olmasıdır. Tasarım aşamasında varlık ve onlara ait öznitelikler belirlendikten sonra varlıklar arasındaki ilişkiler belirlenir ve varlık ilişki diyagramı (entity relationship diagramming) çıkarılır. Bu diyagramda veritabanı varlıkları, varlıkların türleri, varlıklar arası ilişkiler ve ilişki kuralları gösterilir. ER diyagramı hazırlayabilmek için Microsoft Visio, Sybase Power Designer gibi araçlar kullanılır. Aşağıdaki çizelgede üç tablonun yapısı ve ilişkileri gösterilmiştir: Papatya Yayıncılık Eğitim

271 C# 3.0 Yenilikleri 739 Sonuç olarak ER tekniğinin veritabanı oluşturulmadan önce tasarımı kolaylaştırmak amacıyla veritabanının hangi varlıklardan oluştuğunu ve bu varlıkların nasıl ilişkilendirildiğini çeşitli şemalarla belirtme yöntemi olduğunu söyleyebiliriz. ER modelinin en önemli özelliği bu mantıksal tasarımı VTYS şemasına bağlı kalınmadan yapmasıdır. VTYS tarafında, tasarımcılar tarafından oluşturulmuş olan nesne modelini VTYS de saklamak için aracı olarak ER modelini kullanıyoruz. Bu ek işlem, şu anki VTYS lerin nesne yönelimli (Object Oriented) olmamasından kaynaklanmaktadır. Aynı sorun kullanıcılarla veritabanı arasında iletişim sağlayacak veritabanı uygulamaları yazılırken de yaşanır. Özellikle nesne yönelimli bir programlama dili kullanıldığı zaman class, inheritance, property, method gibi OOP varlıkları VTYS tarafından desteklenmediği için iş katmanında OOP ve ilişkisel veritabanı özellikleri iç içe kullanılır. Uygulamaların iş katmanındaki bu iş yükünü azaltmak amacıyla O/R Mapping isimli nesne-ilişki eşleştirme yöntemi kullanılır Nesne-İlişki Haritalama Günümüzde en çok kullanılan veri modeli ilişkisel veri modelidir. Aynı şekilde uygulamaların iş katmanı yazılırken en çok nesne odaklı programlama modeli tercih edilir. Uzun yıllardır kullanılan bu iki yöntem, uygulama geliştiriciler için ciddi kolaylıklar sağlamıştır. Bununla birlikte bu iki yöntemin kullanıldığı kurumsal uygulamaların en önemli sorunu, bu uygulamaların tasarım ve geliştirme süreçlerinin yönetimi zor ve zaman alıcı bir hal almasıdır. Özellikle iş katmanında veritabanı sorgulama işlemlerinde yoğun bir şekilde SQL dilinin kullanılıyor olması ve uygulama kodlarının veritabanı şemasına bağlı kalması, uygulama geliştiricilerinin veritabanı tarafında çok zamanını almaktadır. Veritabanında yeni bir kolon ekleme, kolon değiştirme gibi tablonun şemasının değişmesi durumunda uygulamanın iş katmanındaki veri erişim kodlarının yeniden gözden geçirilmesi gerekir. Bir veritabanı uygulamasında, kodun üçte birinin veri erişimiyle ilgili olduğunu göz önünde bulundurduğumuzda veri erişim katmanında her modül için sorgulama, ekleme, güncelleme, silme gibi işlemleri yeniden kodlamak birçok yazılım mühendisi için sıkıcı ve zaman alıcı olabilmektedir. Bölüm 30

272 740 C# Programlama Dili Ayrıca iş katmanında nesne odaklı programlama tercih edildiği halde veriye nesnesel olmayan yöntemlerle erişilir. Yani veritabanında veri tutmaya yarayan tabloları, uygulama geliştirme tarafında gerçek varlıkları temsil etmek için kullandığımız nesnelerle ifade edememekteyiz. Aynı şekilde uygulama tarafında oluşturulmuş bir nesneyi de veritabanı tarafında uygun bir şekilde temsil edememekteyiz. Bu işlemleri yapabilmek için diğer veri modeli olan ve gerçek dünyayı daha iyi modelleyebilecek nesneye yönelik veri modeli nin gelişmiş olması lazım. Şu ana kadar bu modeli desteklemek amacıyla Nesne Yönelimli VTYS (Object Oriented DBMS - OODBMS) üzerinde çalışmalar yapılmışsa da ciddi sonuçlar elde edilemedi. Bu nedenle sözkonusu sorunları aşmak için yapılabilecek şey yöntemin iyileştirilmesi olacaktır. Bu amaçla ilişkisel veritabanı ile nesneler arasında bağlantı kurma ve eşleştirme yapma yöntemi olarak bilinen Nesne/İlişkisel Eşleme (O/R Mapping-ORM) modeli kullanılır. ORM yöntemi, nesne odaklı programlama modeli ile ilişkisel veritabanı modeli arasında ilişki kurup ilişkisel veritabanındaki öğelerin, nesne odaklı dildeki nesnelere nasıl karşılık geleceğini yönetir. Tabloları sınıflara (class), satırları nesnelere (instance), kolonları bu nesnelerin özniteliklerine (attribute) bağlar; oluşturulan sınıf yordamları tablo seviyesinde, nesne yordamları da satır seviyesinde işlemler yapmak için kullanılır. Böylece verileri sorgularken, güncellerken, eklerken ve silerken nesnesel ifadeler kullanılmasına olanak tanır. VTYS tarafındaki öğelere karşılık gelen uygulama nesnelerine alan nesnesi (domain object) denilir. Bu kalıcı sınıflar, uygulama tarafında, veri erişim katmanı (persistence layer) olarak isimlendirilen alanda yaşar. Bir anlamda uygulamayı, veritabanından soyutlamış olan ORM yaklaşımı, veri işleme katmanının daha hızlı oluşturulmasını, veritabanı işlemlerinde daha az SQL kodu yazılmasını, bununla birlikte verilerin en az SQL kadar güçlü bir yöntemle sorgulanmasını ve otomatik ön bellekleme yapılmasını sağlar. Ayrıca veritabanı şemasında yapılan bir değişiklikte (veri erişim katmanındaki sınıflar doğrudan veritabanına bağlı olmadığı için) veri erişim kodlarını gözden geçirmek yerine daha önce oluşturulmuş olan xml tabanlı nesne-tablo eşleştirme dosyasının düzenlenmesi yeterli olacaktır. O/R Mapping yönteminde kullanılmak üzere birçok araç geliştirilmiştir; bunların en popüleri ilk olarak Java platformu için çıkarılmış ama daha sonra.net sürümü de geliştirilmiş olan açık kaynak NHibernate ürünüdür. Bunun dışında başta LLBLGen ürünü olmak üzere.net platformunda kullanılacak birçok O/R Mapping aracı bulunmaktadır. Günümüzde birçok yazılımevi, uygulamalara ait ORM kodu üreten framework ler geliştirmiştir. Bu araçlar kullanılarak sorgulama cümleleri elle yazılmak yerine otomatik olarak üretilir ve veri erişim katmanı kısa sürede oluşturulmuş olur. Dolayısıyla yazılım mühendisinin veri erişim ve sorgulama için ayıracağı kodlama zamanını iş katmanındaki algoritmaya ayırması, o alana odaklanması ve veri erişim katmanını düşünmemesi sağlanmış olur. Papatya Yayıncılık Eğitim

273 C# 3.0 Yenilikleri 741 Uygulama Persistent Objects NHibernate app.config web.config XML Mapping Veritabanı Bir diğer konu da uygulamalar içerisinde veritabanı işlemleriyle ilgili kodların çalıştırılma yönteminin ne olacağıdır. Bu yöntemin ne olacağı yazılım uzmanları arasında birçok tartışma konusu olmuştur..net Framework ün ilk sürümlerinde iş katmanında dinamik SQL yerine SQL yordamlarının (stored procedure) kullanılması önerilmiştir. Bu yöntemin daha fazla güvenlik ve hız kazandırdığı dile getirildi. Ancak günümüzde bu yöntemin güvenli olduğu kabul edilse de performans ve kullanım konusunda fikir birliği sağlanamamıştır. Stored procedure yerine dinamik SQL kullanılmasını önerenlerin dayanağı, stored procedure lerin versiyonlama, taşınma ve kurulumun kolay olmaması ve büyük ölçekli kurumsal uygulamalarda iş kurallarının genişlemesiyle birlikte yönetimi ve bakımı zorlaşacak çok sayıda procedure yazmak zorunda kalınmasıdır. Aslında asıl sorun iş katmanı ve veri katmanı sınırlarının içiçe geçmesi ve ilk paragraflarda bahsedildiği gibi uygulama geliştiricinin veri katmanında çok zaman harcamasıdır. Veritabanının doğası gereği görevi veri arama, tarama olduğu için kod güvenliği, matematiksel işlemler, koşula dayalı işlemler gibi ek faaliyetlerle uğraşmaması gerekir. Bununla birlikte farklı türden veritabanı üzerinde çalışma ihtimali olan bir ürün için iş kurallarını stored procedure üzerinde oluşturmak uyarlama sürecini zorlaştıracak ve zaman kaybına neden olacaktır. Stored procedure lere alternatif olarak dinamik SQL yapısının kullanılması da uygulamayı, veritabanı şemasına bağımlı hale getirecektir. Bu yüzden O/R Mapping yöntemini kullanarak iş katmanını SQL kodlarından temizleyip bu katmanın sadece iş mantığı algoritmasını içermesini ve uygulama geliştiricinin de sadece o katmana odaklanmasını, T-SQL ile boğuşmak yerine sadece OOP destekli dil ile işlemlerini yürütmesi sağlanmalıdır. ORM yöntemini tercih ettiğimizde aşağıdaki gibi bir T-SQL cümlesi veya stored procedure yerine; UPDATE Musteri SET AdSoyad='Murat Şensoy' WHERE MusteriId = 10 aşağıdaki gibi bir kod yazmış olacağız. Musteri.AdSoyad = "Murat Şensoy" orm.save(musteri) Bölüm 30

274 742 C# Programlama Dili Görüldüğü gibi CRUD (create, read, update, delete) işlemlerini OOP kurallarına göre yazmış oluyoruz. Böylece iş katmanını kodlayan yazılımcının yöntem alışkanlığı bozulmamış olmaktadır. O/R Mapping modelinin geliştirme sürecinde kolaylık sağlamasına karşılık çalışma zamanında klasik yöntemle kıyaslandığında performans konusunda yeterince iyi olduğunu söyleyemeyiz. Bunun nedenlerini şu şekilde sıralayabiliriz: ORM, uygulama mimarisine veri erişim isminde yeni bir katman ekler. Veri katmanı, veritabanına doğrudan erişemez. SQL dilinin kompleks sorgu oluşturma ve çözmedeki gücünden yararlanılmaz. ORM, uygulamanın veritabanı bağımsız olmasını sağladığı için o anda kullanılan VTYS nin yeteneklerinden yararlanılamaz. Sonuç olarak O/R Mapping yönteminin zorunlu bir yöntem olmadığını, ancak karmaşık iş katmanına sahip büyük ölçekli kurumsal uygulamalarda veri erişim katmanını daha az kodla ve kısa sürede üretmeye yardımcı olduğu ve sonuçta yazılım mühendisinin üzerindeki yükü hafiflettiği için tercih nedeni olabileceğini söyleyebiliriz. Bu yöntemi kullanıp kullanmamak birazcık ta uygulama içerisinde klasik veri sorgulama ifadelerini (select, insert, update, delete) SQL ile manual mi oluşturacağız yoksa bunlar otomatik mi oluşturulmalı? sorusuna vereceğimiz yanıta bağlıdır ADO.NET Entity Framework Microsoft, ADO.NET 2.0 ile birlikte sunduğu ObjectSpaces (OS.Net) ürünüyle O/R Mapping çözümüne ilk adımını atmış oldu. ADO.NET 3.5 ile birlikte ObjectSpaces ürününü yeniden düzenleyerek bu bölümde işleyeceğimiz ADO.NET Entity Framework isimli eklentiyi geliştirerek O/R Mapping alanındaki adımlarını daha da belirginleştirdi. Entity Framework eklentisi, sadece bir O/R mapping aracı veya bir kod üreteci olmanın ötesinde bunları da içinde barındıran ve uygulama içerisindeki varlıkları ve ilişkilerini sorgulamamızı sağlayan servisler bütünüdür. Entity Framework ün servisleri aşağıdaki şekilde gösterilmiştir. ADO.NET Entity Framework ün bir parçası olarak Framework ortamında ER modelinin oluşturulması için Varlık Veri Papatya Yayıncılık Eğitim

275 C# 3.0 Yenilikleri 743 Modeli (Entity Data Model-EDM) isimli kavram sunulmaktadır. EDM yi veritabanı şemasını kavramsal ve mantıksal boyutta gösteren ve ilgili ORM haritasını oluşturan bir araç olarak değerlendirebiliriz. EDM, varlıklarla ilgili şemayı XML belgelerinde (CSDL, SSDL, MSL) saklar. Bu üç dosya Entity Framework ün üç temel boyutu olan kavramsal (conceptual), mantıksal (logical) ve haritalama (mapping) modelleriyle ilgili bilgileri içerir. CSDL (Conceptual Schema Definition Language): Varlıkların ve onlara ait ilişkilerin tanımlandığı dosya olup uygulamanın çekirdek veri modelini teşkil eder. Uygulama katmanında varlıklar, CSDL şeması referans alınarak oluşturulur. Bu şema kavramsal şema formatına sahiptir. SSDL (Store Schema Definition Language): Veri kaynağı olarak kullanılacak veritabanına ait üst-veri (metadata) bilgisini içerir. Mantıksal model için oluşturulan bu dosya, varlıklarla veri kaynağı arasındaki iletişimi kurar. MSL (Mapping Specification Language): CSDL dosyasındaki varlıkları SSDL dosyasında belirtilmiş tablolarla eşleştirir. Entity Client, SQLClient ve OracleClient gibi ADO.NET in bir yönetilebilir veri sağlayıcısı olup EDM tarafından tanımlanmış olan veriye erişmeyi sağlar. Diğer sağlayıcılar gibi bunun da EntityCommand, EntityConnection ve EntityTransaction bileşenleri bulunur. Object Services bileşeni, veri nesneleri üzerinde CRUD (Create, Read, Update, Delete) işlemleri için gerekli sorguları oluşturur. Bu sorgulama servisi Entity SQL ve LINQ to Entities türü sorgulamaları destekler. Entity SQL (esql) ismindeki sorgulama dili T-SQL dilinden türemiş olup EDM modelindeki varlıkları ve aralarındaki ilişkileri sorgulamak için kullanılır. LINQ to Entities bileşeni de daha sonra ayrıntılı işleyeceğimiz LINQ teknolojisinin varlıkları sorgulamak için sunduğu bir sağlayıcıdır. Bölüm 30

276 744 C# Programlama Dili Özet C# 3.0 ile birlikte programlama dili tarafına bazı yenilikler eklendi. Bu yenilikler incelendiğinde, bunların daha çok LINQ teknolojisiyle ilişkili olduğu ve LINQ tabanlı sorguları desteklemek amacıyla sunulduğu görülür. Yenilikler arasında var anahtar sözcüğünü kullanarak türünü vermeden bir değişken tanımlama, isimsiz veri türleri oluşturma, yapıcı yordamı yeniden yüklemeksizin nesne veya koleksiyonları farklı sayıda parametreyle ilklendirme, genişletme yordamları ve parçalı yordam gibi yöntemler bulunmaktadır. Bunların dışında karmaşık algoritmaları, uzun satırları daha kısa deyimlerle ifade etmemizi sağlayan Lambda ifadelerine destek verildi. Böylesi yeniliklerle birlikte sunulan ve Microsoft un Nesne-İlişki Haritalama (O/R Mapping) konusunda sağlam adım attığını gösteren ADO.NET Entity Framework bileşeni daha çok ses getirdi. Nesne-İlişki Haritalama çok katmanlı uygulamalarda daha hızlı ve kolayca ilişkisel veritabanı işlemlerini sağlayan bir yöntemidir. Bu yöntem nesne tabanlı olan programlama katmanı ile ilişkisel veritabanı katmanı arasında işlemler için anlaşılır bir kodlama sunar. Böylece kullanıcının OOP tabanlı alışkanlıklarını değiştirmeden uygulama içerisinden veritabanını yönetme imkanı bulmaktadır. Microsoft, ilk olarak ObjectSpaces ürünüyle bu sürece dahil oldu kısa bir aradan sonra bu ürünü biraz daha olgunlaştırıp ADO.NET Entity Framework ismiyle ADO.NET 3.5 üzerinde bir eklenti olarak sunuldu Sorular 30.1) C# 3.0 ile birlikte gelmiş yenilikler nelerdir? 30.2) String türü için mevcut değerin içindeki sesli harfleri dizi olarak döndüren bir genişletme yordamı yazınız? 30.3) Veri Modeli nedir, tasarım seviyelerini açıklayınız? 30.4) ER Modelleme nedir? Örnek vererek açıklayınız. 30.5) O/R Mapping nedir? Ne amaçla kullanılır? 30.6) ADO.NET Entity Framework bileşenleri nelerdir? Papatya Yayıncılık Eğitim

277 31. LINQ Sorgulama Yöntemi LINQ *, dil ile bütünleşik sorgulama anlamına gelmektedir. Bu kavram.net Framework 3.5 ve VS.NET 2008 ile birlikte ortaya çıkmış ve C# 3.0 dili tarafından da desteklenmektedir. LINQ kavramı dizi ve sıralanabilir sınıf (enumerable class) gibi CLR tabanlı nesneleri veya XML belge ve ilişkisel veritabanı gibi dış kaynaktaki veriler üzerinde SQL benzeri sorgulama yapılmasını sağlar. En önemli özelliği bu sorgulama sürecini tamamen nesnesel ifadelerle yapabiliyor olmasıdır. Bu yönüyle LINQ, nesne dünyası ile ilişkisel veritabanı dünyası arasında köprü kurmaktadır. Programcılar, programa SQL kodu gömmek yerine LINQ kullanarak uygulama içerisinden verileri nesnesel ifadelerle sorgularlar Dil İle Bütünleşik Sorgulama LINQ tarafından kullanılan temel sınıf ve arabirimler System.Core.dll kütüphanesinde bulunan System.Linq sınıfı altında bulunur. LINQ aracılığıyla IEnumerable<T> arabirimlerini uygulamış her türlü nesnenin içerdiği veri sorgulanabilir. Sorgulama SQL dili yazımına benzer operatörlerle gerçekleştirilir. LINQ, yerel bellekteki nesne koleksiyonları, SQL Sunucu veritabanları, ADO.NET DataSet nesneleri ve XML belgeleri için özel sağlayıcılar sunmaktadır: LINQ to Object: Bu sağlayıcı bellekte bulunan IEnumerable<T> arabirimini destekleyen koleksiyonların sorgulanmasını sağlar. Bununla kullanıcı dizileri bir ilişkisel veritabanı gibi sorgulanabilir. Bu sorguların operatörleri olarak System.Linq.Enumerable sınıfının statik yordamları kullanılır. LINQ to DataSet: ADO.NET DataSet nesnelerinin ilişkisel veritabanı gibi sorgulanmasını sağlar. LINQ to SQL (DLINQ): İlişkisel veri tabanlarını IQueryable<T> arabirimini uygulamış nesneler üzerinden nesnesel yöntemlerle sorgulanmasını sağlayan kütüphanelerdir. LINQ teknolojisi ile veritabanı standart sorgulama dili olan SQL arasındaki iletişim Veritabanı İşaretleme Dili (DataBase Markup Language-DBML) aracılığıyla gerçekleştirilir. LINQ to XML (XLINQ): XML belgelerini sorgulama ve düzenlemek için kullanılır. XLINQ işlemleri için System.Xml.Linq kütüphanesi kullanılır. * LINQ (Language Integrated Query): Dil İle Bütünleşik Sorgulama

278 746 C# Programlama Dili MSDN Magazine den alınmış olan aşağıdaki diyagram LINQ mimarisini açıkça göstermektedir: LINQ Sorgu Sözdizimi ve Yürütülmesi SQL tabanlı sorgulamalar gibi LINQ sorgu ifadeleri de select, from, where ve orderby gibi yan tümcelerden oluşur. Bu tümceler, sorgulanacak kaynağı, kaynak üzerinde çalıştırılacak koşulu, koşula uyan kayıtların döndürülecek alanları ve alan sıralama biçimini belirtir. Aşağıdaki tabloda ön fikir vermesi açısından basit bir LINQ örneği gösterilmiştir. VS.NET 2008 te bir konsol projesi oluşturup aşağıdaki gibi bir kodlama yapalım. Bu kodlamada Kisiler dizisinde uzunluğu 5 karakter olan elemanlar sorgulanmıştır. using System; using System.Collections.Generic; using System.Linq; class Program { static void Main(string[] args) { // Kişiler dizisi. string[] kisiler = {"Ahmet", "Zeynep", "Ayşe", "Haluk"; // Listede uzunluğu 5 olan isimleri sorgulayalım. IEnumerable<string> sorgu = from k in kisiler where k.length == 5 orderby k // Verileri büyük harf yapalım. select k.toupper(); foreach (string kisi in sorgu) Console.WriteLine(kisi); Papatya Yayıncılık Eğitm

279 LINQ Sorgulama Yöntemi 747 AHMET HALUK Kodlardan da görüleceği üzere dizideki veri sorgulanırken alışılagelen sorgulamadan alıştığımız select from... where... ifadesine benzer bir yazım biçimi kullanılıyor. Şayet LINQ çözümü olmasaydı, böyle bir işlem için klasik foreach döngüsü içerisinde if ifadesiyle her kaydın kritere uyup uymadığı sınanacaktı. LINQ nun, veritabanı geliştiricileri için tanıdık gelen bu yazım biçimi iş katmanı geliştiricileri için biraz farklı olabilir. Neyse ki VS.NET IDE sinin IntelliSense desteği programcıların imdadına yetişmektedir. LINQ sorgulamalarında buradaki yan tümcelere benzer birçok operatör kullanılmaktadır..net Framework 3.5, LINQ yöntemine dayalı sorgulama işlemlerinde birkaç sorgu yazım biçimi destekler: Sorgulama yazım biçimi (Query syntax) Genişletilmiş yordam ve Lambda ifadeleri (Extension methods and Lambda expressions) Sayılabilir türler (Enumerable type) Genel delege (Generic delegate) Anonim yordamlar (Anonymous methods) Genişletme yordamları (extension methods) ve Lambda ifadeleri (Lambda expressions) LINQ sorgulamalarında en çok tercih edilen yöntemlerdir. Genişletme yordamlarının amacı genişletilemeyen CLR tabanlı türlere yeni işlevler kazandırmaktır. Lambda ifadeleri de temsilci işlemlerinin daha kısa sözcüklerle ifade edilmesini sağlarlar. Yani, LINQ sorgulamalarında koşul seçme deyimlerinin daha kısa ifade edilmesinde kullanılır. Lambda ifadeleri => operatörüyle oluşturulur. Where() ve Select() gibi birçok yordam System.Linq.Enumerable sınıfının genişletme yordamlarıdır. Bunlar kullanılarak sorgulara daha nesnesel bir ifade kazandırılmış olunur. Bir diğer konu ise LINQ sorgusunun döndürdüğü sonucun uygun türdeki bir nesneye eşitlenmesidir. Sorgudaki select operatörü her defasında farklı türde bir nesne döndürebilir. Dolayısıyla sorgunun döndüreceği sonucu karşılayacak herhangi yerel bir tür bulunmadığı zaman Framework 3.0 ile birlikte gelmiş ve daha önce ayrıntılı olarak ele alınan isimsiz/anonim türler kullanılır; bunlar var anahtar sözcüğüyle tanımlanır. İsimsiz türün kullanılması durumunda derleyici sonuca uygun türü kendisi oluşturur. Önceki örnekte sonucun string/sözce türünde olduğu bilindiği için sonuç IEnumerable<string> türünde tanımlandı. Eğer sonucun türü öngörülemiyorsa var ile tanımlanmış bir değişkene atanması işlem yükünü hafifletecektir. Bölüm 31

280 748 C# Programlama Dili Örneğimizi aşağıdaki gibi var ile yapıp derleyici tarafından oluşturulmuş sonucun türünü okuyalım. var sorgu = from k in kisiler where k.length == 5 select k.toupper(); Console.WriteLine("Sonuç Türü : {0",sorgu.GetType()); Console.WriteLine(); foreach (var kisi in sorgu) Console.WriteLine(kisi); Sonuç Türü : System.Linq.Enumerable+<SelectIterator>d d`2[system.string,system. String] AHMET HALUK Burada string türünde bir değer döndürülmüş oldu. Konunun daha iyi anlaşılması için aşağıdaki gibi iki üyesi bulunan isimsiz bir tür döndürelim. var sorgu = from k in kisiler where k.length == 5 select new { Uzunluk = k.length, Deger = k.toupper() ; Console.WriteLine("Sonuç Türü : {0",sorgu.GetType()); Console.WriteLine(); foreach (var kisi in sorgu) Console.WriteLine(kisi); Sonuç Türü : System.Linq.Enumerable+<SelectIterator>d d`2[system.string,<>f An onymoustype0`2[system.int32,system.string]] { Uzunluk = 5, Deger = AHMET { Uzunluk = 5, Deger = HALUK Bu durumda derleyici sonucu iki üyesi bulunan bir nesneye aktaracaktır. Papatya Yayıncılık Eğitm

281 LINQ Sorgulama Yöntemi 749 Bu şekilde isimsiz bir tür yerine daha önce oluşturulan isimli bir türü de kullanabiliriz. select ifadesinin döndüreceği sonuca uygun bir sınıf yazalım. public class UzunlukDeger { public int Uzunluk; public string Deger; var sorgu = from k in kisiler where k.length == 5 select new UzunlukDeger { Uzunluk = k.length, Deger = k.toupper() ; Console.WriteLine("Sonuç Türü : {0", sorgu.gettype()); Sonuç Türü: System.Linq.Enumerable+<SelectIterator>d d`2[system.string, Program+UzunlukDeger] LINQ konusuyla ilgili diğer madde de Func isimli genel temsilcilerin kullanılmasıdır. Bu temsilcinin parametre sayıları farklı birkaç türevi bulunur. public delegate TR Func<TR>(); public delegate TR Func<T0, TR>(T0 a0); public delegate TR Func<T0, T1, TR>(T0 a0, T1 a1); public delegate TR Func<T0, T1, T2, TR>(T0 a0, T1 a1, T2 a2); public delegate TR Func<T0, T1, T2, T3, TR>(T0 a0, T1 a1, T2 a2,t3 a3); Buradaki T0, T1, T2 ve T3 türü parametreler fonksiyon argümanlarının türlerini, TR parametresi de fonksiyonun döndüreceği sonucun türünü belirtir. Böylesi genel temsilcilerin kullanılması, özellikle Lambda ifadelerinde ayrıca temsilci tanımlama işinden sıyrılınmış olunur. Aşağıdaki satırda Musteri türünde parametre alan ve geriye bool türünde değer döndüren filtre isimli bir temsilci (delegate) tanımlanmıştır. Func<Musteri, bool> filtre = m => m.sehir == "Istanbul"; Bu temsilciyi örnek olarak şu şekilde kullanabiliriz. IEnumerable<Musteri> IstanbulluOlanlar = Musteri.Where(filtre); Bu ayrıtta anlatılan LINQ sorgu sözdizimleri aşağıda aynı örnek üzerinde gösterilmiştir. // Klasik sorgulama sözdizimi kullanımı. var qssorgu = from k in kisiler where k.length == 5 select k.toupper(); // Genişletme yordamları ve Lambda ifadeleri. var emsorgu = kisiler.where(k => k.length == 5).Select(k => k.toupper()); Bölüm 31

282 750 C# Programlama Dili // IEnumerable<T> kullanımı. var enumsorgu = Enumerable.Where(kisiler, k => k.length == 5).Select(k => k.toupper()); // Geneldelege Func<string, bool> where = (k => k.length == 5); Func<string, string> select = k => k.toupper(); var gdsorgu = kisiler.where(where).select(select); // Anonim yordam kullanımı. var amsorgu = kisiler.where(delegate(string k) { return k.length == 5; ).Select(delegate(string k) { return k.toupper(); ); AHMET HALUK Örnekte kullanılan yazım biçimlerinin hepsi aynı sonucu döndürür. Genişletme yordamlarının tercih edilmesi daha kolay sorgu ifadeleri yazılmasını sağlar. Kavramsal sözdizimi (comprehension syntax) olarak ta bilinen klasik sorgulama yazım biçimi de tercih edilebilir. LINQ nun çalışmasıyla ilgili olarak dikkat edilmesi gereken önemli bir nokta da sorguların yalnızca çağrıldığı anda yürütülmesidir. Yani örneklerde olduğu gibi kendisine foreach döngüsüyle eriştiğimiz noktada sorgu yürütülür. Örnekte kriter olarak kullanılan karakter uzunluk değerini dinamik hale getirip yerel bir değişkene bağlayalım; ve sorgu ifadesinden sonra, foreach döngüsünden önce bu yerel değişkeninin değerini değiştirelim. // Kişiler dizisi. string[] kisiler = {"Ahmet", "Zeynep","Ayşe", "Haluk", "Abdullah"; byte x = 5; IEnumerable<string> sorgu = from k in kisiler where k.length == x select k.toupper(); // Sorgu henüz çalışmadı. x = 6; // x değişkeninin değerini değiştirelim. foreach (string kisi in sorgu) Console.WriteLine(kisi); ZEYNEP Görüldüğü gibi sorgu sonucunda 5 değil 6 karakterli değerler geldi. Yani sorguya ait kriter ifadesi x yerel değişkeninin ilk değeri değil son değerinden etkilenmiş oldu. Bu çalışma yöntemine ertelenmiş yürütme (differed execution) denilir. Bu yöntemin amacı, aynı LINQ sorgusunun farklı zamanlarda çalıştırılması durumunda güncel sonuçları döndürmesidir. Bazı durumlarda bu durum istenmeyebilir; yani sorgunun daha sonra değil de tanımlandığı anda yürütülmesi istenebilir. Bu amaçla Papatya Yayıncılık Eğitm

283 LINQ Sorgulama Yöntemi 751 bu işlemin tersi olarak sorgunun o anda çalışması ve sonucunun korunması için.net Framework ToArray<T>(), ToDictionary<TSource, TKey>() ve ToList<T>() gibi genişletme yordamları sunmaktadır. Bu tür çalıştırmaya da derhal yürütme (immediate execution) denilir. Önceki örnekte kriter alanı üzerinde değişiklik yapılmıştı; bu sefer farklı bir örnek olması açısından kaynak üzerinde değişiklik yapalım. Sorgu ifadesinden hemen sonra kaynak dizinin sorgu kriterine uyan ilk elemanı değiştirelim. // Kişiler dizisi. string[] kisiler = { "Ahmet", "Zeynep", "Ayşe", "Haluk", "Abdullah"; // Immediate Execution IEnumerable<string> sorgu = (from k in kisiler where k.length == 5 select k.toupper()).toarray<string>(); // Sorgu artık çalıştı ve sonuç olarak AHMET ve HALUK döner. // Bu noktada kaynaktaki bir veriyi değiştirelim. kisiler[0] = "Mehmet";// Ahmet kaydını Mehmet olarak değiştirdik. // LINQ sorgu sonucu bu değişiklikten etkilenmeyecektir. foreach (string kisi in sorgu) Console.WriteLine(kisi); AHMET HALUK Aynı örneği Lambda ifadeleriyle yazalım. // Hemen/anında yürütme. IEnumerable<string> sorgu = kisiler.where(k => k.length == 5).Select(k => k.toupper()).toarray(); LINQ Operatörleri Bu ayrıtta ele alınacak olan LINQ sorgu operatörlerinin bazıları bellek nesneleri, bazıları da ilişkisel veritabanı kaynağı üzerinde örneklendirilecektir. İlişkisel veritabanı örnekleri DLINQ ayrıtında ele alınacaktır. Sorgulama işlemlerinin daha kolay anlaşılması için aşağıdaki Müsteri ve Siparis tabloları üzerinde örneklerimizi yapalım: Bölüm 31

284 752 C# Programlama Dili DataSet ve DataTable gibi bağlantısız nesneler üzerinde LINQ sorgulamaları doğrudan yapılamadığı için bu tabloları temsilen Generic.List türünde koleksiyon nesneleri kullanılacaktır. using System; using System.Collections.Generic;//List using System.Linq; class Program { static List<Musteri> MusteriList; static List<Siparis> SiparisList; static void Main(string[] args) { // LINQ sorgulamaları bu alanda yazılacak. // Müşteri listesine örnek kayıt girecek yordam. static void MusteriListeOlustur() { MusteriList = new List<Musteri>(); // Listeye iki kayıt ekleyelim. MusteriList.Add( new Musteri { MusteriId = 1, AdSoyad = "Ali Korkmaz" ); MusteriList.Add( new Musteri { MusteriId = 2, AdSoyad = "Ayşe Korkmaz" ); // Sipariş listesine örnek kayıt girecek yordam. static void SiparisListeOlustur() { SiparisList = new List<Siparis>(); SiparisList.Add(new Siparis { SiparisId = 1, MusteriId = 1, UrunId = 10, Adet = 2, Tarih = Convert.ToDateTime("01/01/2007") ); SiparisList.Add(new Siparis { SiparisId = 2, MusteriId = 1, UrunId = 12, Adet = 3, Tarih = Convert.ToDateTime("01/01/2007") ); SiparisList.Add(new Siparis { SiparisId = 3, MusteriId = 2, UrunId = 10, Adet = 1, Tarih = Convert.ToDateTime("12/01/2007") ); // Musteri sınıfı. public class Musteri { public int MusteriId; public string AdSoyad; // Siparis sınıfı. public class Siparis { public int SiparisId; public int MusteriId; public int UrunId; public int Adet; public DateTime Tarih; Papatya Yayıncılık Eğitm

285 LINQ Sorgulama Yöntemi 753 LINQ sorgulamaları için kullanılabilecek operatörler aşağıdaki gibi gruplanabilir: Kısıtlama Operatörleri : Where Seçme Operatörleri : Select, SelectMany Bölümleme Operatörleri : Take, Skip, TakeWhile, SkipWhile Birleştirme Operatörleri : Join, GroupJoin Ulama/ekleme Operatörleri : Concat Sıralama Operatörleri : OrderBy, OrderByDescending, ThenBy ThenByDescending, Reverse Gruplama Operatörleri : GroupBy Küme Operatörleri : Distinct, Union, Intersect, Except Dönüştürme Operatörleri : ToArray, ToList, ToDictionary, OfType Eşitlik Operatörleri : SequenceEqual Eleman Operatörleri : DefaultIfEmpty, ElementAt, ElementAtOrDefault First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault Üretim Operatörleri : Empty, Range, Repeat Ölçüm Operatörleri : Any, All Gruplama Fonksiyonu Operatörleri : Aggregate, Count, LongCount, Sum, Min Max, Average Kısıtlama Operatörleri (Restriction Operators) Where : Sorgulama yapılacak nesne üzerinde koşul tanımlamak için kullanılır Seçme Operatörleri (Projection Operators) Select: Kaynaktaki belirli alanları seçer. SelectMany: Kaynak üzerinde çoklu seçim yapmak için kullanılır. LINQ sorgulamalarında en çok kullanılan operatörler bunlar olduğu için bu konuda biraz ayrıntılı örnekler yapacağız. Öncelikle MusteriList listesinin içindeki elemanları herhangi bir koşul tanımlamadan sorgulayalım. static void Main(string[] args) { // Listeyi oluşturalım. MusteriListeOlustur(); var Musteriler = from M in MusteriList select M; foreach (var oms in Musteriler) Console.WriteLine("{0\t{1",oMs.MusteriId, oms.adsoyad); 1 Ali Korkmaz 2 Ayşe Korkmaz Bölüm 31

286 754 C# Programlama Dili Listede MusteriId kolonu 1 den büyük olan satırların AdSoyad bilgilerini alalım. Burada koşul sözkonusu olduğu için where operatörünü kullanacağız. static void Main(string[] args) { MusteriListeOlustur(); var Musteriler = from M in MusteriList where M.MusteriId >1 // Filtre öğeleri. select M.AdSoyad; // Döndürülecek öğeler. foreach (var oms in Musteriler) Console.WriteLine(oMs); Ayşe Korkmaz Bu örnekte tanımladığımız bir veri türü (Musteri) üzerinde çalıştık. LINQ için tercih edilebilecek sözdizimlerini anlatırken string/sözce tabanlı basit bir veri türü örnek olarak gösterilmişti. Burada muhtemel LINQ sözdizimlerinin Musteri türü için de örneklendirilmesi programcının sorgu yazma biçimini yönlendirmesi açısından yararlı olacaktır. // Query expression(klasik sorgu sözdizimi) kullanımı. var qsmusteriler = from M in MusteriList where M.MusteriId > 1 select M.AdSoyad; // Extension Methods ve Lambda expressions(genişletme yordamları ve Lambda ifadeleri). var emmusteriler = MusteriList.Where(M => M.MusteriId > 1).Select(M => M.AdSoyad); // IEnumerable<T> kullanımı. var enummusteriler= Enumerable.Where(MusteriList, M => M.MusteriId > 1).Select(M => M.AdSoyad); // Generic delegate(genel temsilci). Func<Musteri, bool> where = (M => M.MusteriId > 1); Func<Musteri, string> select = M => M.AdSoyad; var gdmusteriler = MusteriList.Where(where).Select(select); // Anonymous method(isimsiz yordam) kullanımı. var ammusteriler = MusteriList.Where(delegate(Musteri M) { return M.MusteriId > 1; ).Select(delegate(Musteri M) { return M.AdSoyad; ); Ayşe Korkmaz Sorgulamalarda from ve where operatörleri birden fazla kullanılarak farklı nesneler aynı anda sorgulanabilir veya birden fazla koşul tanımlanabilir. Musteri ve Siparis tablosu MusteriId kolonu üzerinde birleştirilip müşterilerin hangi tarihte satış yaptıklarını listeleyelim. Bunun için her kayıt dizisi için from ifadesi kullanılır. Papatya Yayıncılık Eğitm

287 LINQ Sorgulama Yöntemi 755 static void Main(string[] args) { MusteriListeOlustur(); SiparisListeOlustur(); var Musteriler = from M in MusteriList from S in SiparisList where M.MusteriId == S.MusteriId select new { M, S ; foreach (var oms in Musteriler) Console.WriteLine("{0\t{1", oms.m.adsoyad, oms.s.tarih); Ali Korkmaz :00:00 Ali Korkmaz :00:00 Ayşe Korkmaz :00:00 Heriki tabloyu MusteriId kolonu üzerinden ilişkilendirip geriye heriki tabloyu döndüren bir sorgu yazılmış olundu. Bu nedenle sorgu sonucu ekrana yazdırılırken kolonlara tablo isimleri verilerek erişildi. Sorgu, yalnızca kullanılacak kolonları döndürecek şekilde değiştirilirse, sonuçlar ekrana yazdırılırken tablo isimlerinin kullanılmasına gerek kalmaz. var Musteriler = from M in MusteriList from S in SiparisList where M.MusteriId == S.MusteriId select new { M.AdSoyad, S.Tarih ; foreach (var oms in Musteriler) Console.WriteLine("{0\t{1", oms.adsoyad, oms.tarih); SelectMany() yordamı Select() yordamından farklı olarak birden çoğa ilişki (one-to-many relationship) işlemlerinde kullanılarak çoklu seçim yapar. Select() hiyerarşik türde sonuç döndürürken, SelectMany() tek-düze (flat) türden kayıt dizisi döndürür. Yani seri bir kaynaktan okunan elemanın kendisi de bir dizi ise Select() yordamı o elemanı dizi olarak döndürürken, SelectMany() yordamı o dizinin her elemanını ayrıştırarak tek tek döndürür. Bu yordamların örnek kullanımları aşağıdaki gibidir. [Select()] public static IEnumerable<TResult> Select<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, TResult> selector ) [SelectMany()] public static IEnumerable<TResult> SelectMany<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector ) Bölüm 31

288 756 C# Programlama Dili Heriki yordam da TSource isimli sıralı kaynağı ve TSource türünde bir seçici temsilciyi giriş olarak alır. Select() yordamı geriye TResult u döndürür; SelectMany() yordamı ise TResult a ait bir IEnumerable döndürür. Dolayısıyla SelectMany(), kaynaktaki her kayıt için birden fazla satır döndürür. Bu farkı bir örnek üzerinde gösterelim. Musteriler dizisindeki AdSoyad kolonundaki değerleri boşluk karakterine göre ayrıştıralım. Böylece her kayda ait iki elemanlı bir dizi oluşmuş olur. Oluşan tüm kayıtları Select() yordamını kullanarak okumak için hem kaynağın kendisini hem de kaynaktaki her kayıdı döngüye alarak okumalıyız. var AdSoyadlar = MusteriList.Select(M => M.AdSoyad.Split(' ')); foreach (string[] AdSoyad in AdSoyadlar) foreach (string Sozcuk in AdSoyad) Console.WriteLine(Sozcuk); Aynı işlemi SelectMany() yordamıyla gerçekleştirelim. var AdSoyadlar = MusteriList.SelectMany(M => M.AdSoyad.Split(' ')); foreach (string Sozcuk in AdSoyadlar) Console.WriteLine(Sozcuk); SelectMany() yordamının yaptığı bu iş Select() ile yapılacak olunursa aşağıdaki gibi bir kavramsal sorgu yazılabilir: var AdSoyadlar = from omusteri in MusteriList from Sozcuk in omusteri.adsoyad.split() select Sozcuk; Burada yapılan işlem MusteriList kaynağındaki her satır içerdeki from ifadesinin çalıştırılmasıdır. Bu tür iterasyon işlemlerinde iç iterasyon değerleriyle birlikte harici değerler de okunabilir. Örneğimizi sonuçta listelenen değerin hangi AdSoyad bilgisinden geldiğini de yazacak şekilde değiştirelim. var AdSoyadlar = from omusteri in MusteriList from Sozcuk in omusteri.adsoyad.split() select omusteri.adsoyad +"» " + Sozcuk; foreach (string Sozcuk in AdSoyadlar) Console.WriteLine(Sozcuk); Ali Korkmaz» Ali Ali Korkmaz» Korkmaz Ayşe Korkmaz» Ayşe Ayşe Korkmaz» Korkmaz SelectMany() yeniden yüklenmiş (overloaded) bir yordamdır. En son örneğin SelectMany() yordamıyla yapılabilmesi için bunun collectionselector ve resultselector isimli parametreler aldığı türevleri kullanılır. Papatya Yayıncılık Eğitm

289 LINQ Sorgulama Yöntemi 757 IEnumerable<TResult> SelectMany<TSource, TCollection, TResult> (this IEnumerable<TSource> source, Func<TSource, int, IEnumerable<TCollection>> collectionselector, Func<TSource, TCollection, TResult> resultselector); collectionselector parametresi, kaynaktaki her elemana uygulanacak fonksiyonu, resultselector parametresi de sorgulama esnasında oluşan ara kayıtdizisinin her elemanına uygulanacak fonksiyonu belirtir. Örneğimizi SelectMany() yordamıyla aşağıdaki gibi yazabiliriz. var AdSoyadlar = MusteriList.SelectMany(M => M.AdSoyad.Split(' '),// collectionselector (M, Sozcuk) => (M.AdSoyad +"» "+ Sozcuk));// resultselector foreach (string Sozcuk in AdSoyadlar) Console.WriteLine(Sozcuk); Ali Korkmaz» Ali Ali Korkmaz» Korkmaz Ayşe Korkmaz» Ayşe Ayşe Korkmaz» Korkmaz Bu bölümdeki diğer önemli operatör, where() fonksiyonudur. Bu fonksiyon source ve predicate isminde iki parametre alır ve select() fonksiyonu gibi geriye IEnumerable türünde değer döndürür. İlk parametre, IEnumerable türünde olup filtrenin uygulanacağı kaynağı, ikinci parametre, temsilci türünde olup kaynak üzerinde uygulanacak filtre fonksiyonunu belirtir. Filtre fonksiyonu geriye bool türünde değer döndürür. Bu bilgileri, gerektiğinde where() yordamını daha yetkin kullanmak için verdik. Konunu başında verdiğimiz ilk örnekteki kisiler dizisinin bir elemanını null yapıp diziyi sorgulayalım. string[] kisiler = { "Ahmet", "Zeynep", null, "Haluk"; var sorgu = from k in kisiler where k.length == 5 select k.toupper(); foreach (string kisi in sorgu) Console.WriteLine(kisi); Bu küçük uygulamayı çalıştırdığımızda aşağıdaki hata mesajıyla karşılaşırız. Bölüm 31

290 758 C# Programlama Dili Buradaki sorun dizi elemanlarının iterasyonla uzunluklarının 5 hane olup olmadığı sınanırken üçüncü elemanın null olmasından kaynaklanmaktadır. Çünkü null kayıtlar için Length özelliği kullanılamaz. Bu yüzden where operatörü içerisinde okunan kayıdın öncelikle null olup olmadığı sınanmalıdır. var sorgu = from k in kisiler where k!=null && k.length == 5 select k.toupper(); Farklı örnekler vermek açısından bu işlem aşağıdaki gibi de yazılabilir. var sorgu = from k in kisiler where (k==null? false : k.length==5) select k.toupper(); Aynı işlemi Lambda ifadeleriyle yazalım. var sorgu = kisiler.where( k=>{ if(k==null) return false; else if (k.length==5) return true; return false; ).Select(k=> k.toupper()); LINQ sorgulamalarında programcı tarafından yazılan genişletme yordamları da sorgulamanın bir parçası olarak kullanılabilir. Bu ayrıtta kullanılan örneği Linq.Enumerable.Where() yordamı yerine kendi sonradan yazılan where yordamı kullanılacak şekilde değiştirelim. class Program { static void Main(string[] args) { string[] kisiler = { "Ahmet", "Zeynep", "Ayşe", "Haluk" ; var sorgu = kisiler.bizimwhere(k => k.length == 5).Select(k => k.toupper()); foreach (string kisi in sorgu) Console.WriteLine(kisi); Console.ReadLine(); static class Ornek { // System.Linq.Enumerable.Where() ile aynı formatta. public static IEnumerable<string> BizimWhere( this IEnumerable<string> kaynak, Func<string, bool> kriterislem) { foreach (var kayit in kaynak) { Papatya Yayıncılık Eğitm

291 LINQ Sorgulama Yöntemi 759 kontrol ediliyor.",kayit); // Okunan kayıt, kriterlere uyuyorsa dönecek listeye ekle. if (kriterislem(kayit)) yield return kayit; "Ahmet" kontrol ediliyor. AHMET "Zeynep" kontrol ediliyor. "Ayşe" kontrol ediliyor. "Haluk" kontrol ediliyor. HALUK Bölümleme Operatörleri (Partitioning Operators) Skip(): Parametre olarak aldığı değer kadar satır atlanılmasını sağlar. SkipWhile(): Parametre olarak fonksiyon türünde parametre alır. Bu fonksiyon her elemana uygulanacak olan koşul denetleme fonksiyonudur. Fonksiyonun kendisi için true değeri döndürdüğü kayıtlar atlanılır. Take(): Parametre olarak aldığı değer kadar satır okur. Skip() ile atlanılan satırdan itibaren kaç satırın alınacağını belirtir. Skip() ve Take() operatörleri sayfalama işlemlerinde önemli kolaylık sağlar. TakeWhile: Parametre olarak fonksiyon türünde parametre alır. Bu fonksiyon, her elemana uygulanacak olan koşul denetleme fonksiyonudur. Fonksiyonun kendisi için true değeri döndürdüğü kayıtlar sonuç listesine eklenir. // İlk 2 kaydı alalım. var Musteriler =MusteriList.Take(2); foreach (var oms in Musteriler) Console.WriteLine("{0\t{1", oms.musteriid, oms.adsoyad); 1 Ali Korkmaz 2 Ayşe Korkmaz Sipariş listesindeki iki ve üçüncü kayıtlara ait müşteri bilgilerini okuyalım. Bu durumda sipariş listesinde ilk kaydı atlayıp ardından gelen iki kaydı okuyacağız. Var Musteriler = (from M in MusteriList from S in SiparisList where M.MusteriId == S.MusteriId select M).Skip(1) // İlk kayıdı atla..take(2); // Atlanılan kayıttan sonraki 2 kaydı al. foreach (var oms in Musteriler) Console.WriteLine("{0\t{1", oms.musteriid, oms.adsoyad); 1 Ali Korkmaz 2 Ayşe Korkmaz Bölüm 31

292 760 C# Programlama Dili SkipWhile() yordamını kullanarak ismi Ali ile başlayan kayıtları atlayalım. var Musteriler = MusteriList.SkipWhile(M=>M.AdSoyad.StartsWith("Ali")); 2 Ayşe Korkmaz Aynı şekilde ad ve soyad Korkmaz ifadesini içeren müşterilerden ilk ikisini alalım. var Musteriler = MusteriList.TakeWhile(M=>M.AdSoyad.Contains("Korkmaz")).Take(2); 1 Ali Korkmaz 2 Ayşe Korkmaz Birleştirme Operatörleri (Join Operators) Join: İki sıralı liste arasında INNER JOIN işlemi yaptırır. GroupJoin: İki sıralı liste arasında grupsal join işlemi yaptırır. Sol tarafa karşılık sağ tarafa ait bilgileri gruplamamızı sağlar. Aşağıdaki tabloda Join ve GroupJoin yordamlarının kullanım biçimleri gösterilmiştir: public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>( this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerkeyselector, Func<TInner, TKey> innerkeyselector, Func<TOuter, TInner, TResult> resultselector ) public static IEnumerable<TResult> GroupJoin<TOuter, TInner, TKey, TResult>( this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerkeyselector, Func<TInner, TKey> innerkeyselector, Func<TOuter, IEnumerable<TInner>, TResult> resultselector ) Bu yordamların aldığı paremetreler şunlardır: Outer: IEnumerable türünde olup birleştirilecek ilk listeyi belirtir. Inner: IEnumerable türünde olup birleştirilecek ikinci listeyi belirtir. outerkeyselector: İlk kayıt dizisinin her satırından birleştirici anahtarı seçecek olan fonksiyonu belirtir. innerkeyselector: İkinci kayıt dizisinin her satırından birleştirici anahtarı seçecek olan fonksiyonu belirtir. resultselector: Heriki kayıt dizisinin eşleşen satırlarından ilgili alanları seçecek olan fonksiyonu belirtir. Papatya Yayıncılık Eğitm

293 LINQ Sorgulama Yöntemi 761 GroupJoin() yordamının Join() den farkı tabloda da görüldüğü gibi resultselector bölümünde ikinci parametre olarak aldığı değerin IEnumerable türünde bir nesne olmasıdır. Func<TOuter, IEnumerable<TInner>, TResult> resultselector Bu koleksiyon nesnesi ikinci kayıt dizisi türüne (TInner) sahiptir. Öncelikle müşterilere ait siparişleri listeleyerek klasik birleştirme işlemini örneklendirelim. Bu örneği daha önce iki adet from kullanarak yapmıştık. Bu tür örnekler için join operatörleri kullanılması işlemlere hız kazandıracaktır. Birleştirme işlemlerinin farklı türlerini örneklendirmek için müşteri listesine siparişi olmayan yeni bir kayıt ekleyelim. MusteriList.Add(new Musteri { MusteriId=3, AdSoyad="Mert Şensoy" ); Bilindiği gibi T-SQL dilinde MusteriList ve SiparisList kayıt kümeleri aşağıdaki gibi JOIN ifadesiyle birleştirilebilir. SELECT M.AdSoyad, S.Tarih, S.Adet FROM MusteriList AS M INNER JOIN SiparisList AS S ON M.MusteriId = S.MusteriId Böylece heriki listedeki MusteriId kolonu üzerinden eşleşen kayıtları listelemiş oluruz. Aynı sorgulamayı LINQ aracılığıyla aşağıdaki biçimde yazabiliriz. var Sonuc = from M in MusteriList join S in SiparisList on M.MusteriId equals S.MusteriId select new { M.AdSoyad, S.Tarih, S.Adet ; foreach (var bilgi in Sonuc) Console.WriteLine(bilgi); { AdSoyad = Ali Korkmaz, Tarih = :00:00, Adet = 2 { AdSoyad = Ali Korkmaz, Tarih = :00:00, Adet = 3 { AdSoyad = Ayşe Korkmaz, Tarih = :00:00, Adet = 1 Birleştirme örneğini Lambda ifadeleriyle aşağıdaki gibi de yazabiliriz: var Sonuc = MusteriList.Join( // Dış liste SiparisList, // İç liste M => M.MusteriId, // Dış anahtar seçici S => S.MusteriId, // İç anahtar seçici (M, S) => new { Musteriler=M, Siparisler=S // Sonuç seçici ); Şekilde görüldüğü gibi sadece sipariş bilgisi olan müşteriler listelenmiştir. Yani inner join işlemi uygulanmıştır. Bu iki liste arasında left join işleminin nasıl yapılacağı GroupJoin operatöründen sonra örneklendirilecektir. Bölüm 31

294 762 C# Programlama Dili GroupJoin operatörü iki adet sıralı liste arasında gruplanmış bir birleştirme gerçekleştirir. Bir listeye GroupJoin operatörü uygulandığı zaman o listenin her elemanı karşısına ona karşılık gelen ikinci listenin elemanları tek bir hiyerarşik yapıda listelenir. Bu cümleyi daha açık anlamak için önceki örneği GroupJoin ile yapıp ortaya çıkan sonuçları karşılaştıralım. GroupJoin işlemini klasik sorgulama formatında yazmak için join... into ifadesi kullanılır. // Query expression var Sonuc = from M in MusteriList join S in SiparisList on M.MusteriId equals S.MusteriId into SipGrup select new { Musteriler = M, Siparisler = SipGrup ; // Lambda expression var Sonuc = MusteriList.GroupJoin( SiparisList, M => M.MusteriId, S => S.MusteriId, (M,S) => new { Musteriler = M, Siparisler = S ); Papatya Yayıncılık Eğitm

295 LINQ Sorgulama Yöntemi 763 Görüldüğü gibi birinci kayıt kümesindeki her elemanın karşısına ikinci kayıt kümesinde o elemanın ilişkili kayıtlar tek bir hücrede sunulmuştur. Böyle bir durumda her müşteri için sipariş ayrıntılarına erişmek için döngü kurulmalıdır. foreach (var bilgi in Sonuc) { // Müşterinin adını yazalım. Console.WriteLine(bilgi.Musteriler.AdSoyad); // Müşterinin yaptığı siparişlerin tarihlerini listeleyelim. foreach (var sp in bilgi.siparisler) Console.WriteLine(" " + sp.tarih); Ali Korkmaz Ayşe Korkmaz Mert Şensoy Birleştirme operatörünü kullanarak left join işlemi yapılabilmesi için into ifadesiyle ikinci dizi bir ara listeye aktarılır ve o listeden de DefaultIfEmpty() yordamı aracılığıyla değerleri boş olan satırlar elde edilir. DefaultIfEmpty() yordamı boş liste (sequence) için varsayılan bir element döndürür. T-SQL de left join işlemi aşağıdaki kod ile ifade edilir: SELECT * FROM MusteriList AS M LEFT JOIN SiparisList AS S ON M.MusteriId = S.MusteriId Bu işlemi LINQ ifadeleriyle aşağıdaki gibi yapabiliriz. // Query expression var Sonuc = from M in MusteriList join S in SiparisList on M.MusteriId equals S.MusteriId into SipGrup from Satir in SipGrup.DefaultIfEmpty() select new { M.AdSoyad, // Karşı tabloda müşteriye ait sipariş yok ise. SiparisId = (Satir == null? "Siparişi Yok!" : Satir.SiparisId.ToString()) ; foreach (var bilgi in Sonuc) { Console.WriteLine(bilgi.ToString()); { AdSoyad = Ali Korkmaz, SiparisId = 1 { AdSoyad = Ali Korkmaz, SiparisId = 2 { AdSoyad = Ayşe Korkmaz, SiparisId = 3 { AdSoyad = Mert Şensoy, SiparisId = Siparişi Yok! Bölüm 31

296 764 C# Programlama Dili Müşteri ve sipariş listeleri arasında left join işlemi kurulduğu zaman aşağıdaki görüntü elde edilir. Bu görüntüden asıl left join işleminin group join ile değil bu şekilde yapılması gerektiğini de öğrenmekteyiz. Left join işlemi Lambda ifadeleriyle aşağıdaki gibi yazılabilir: // Lambda ifadesi. var Sonuc = MusteriList.GroupJoin( SiparisList, M => M.MusteriId, S => S.MusteriId, (M, SipGrup) =>new{m = M,SipGrup = SipGrup ) // GroupJoin.SelectMany( TempTb => TempTb.SipGrup.DefaultIfEmpty(), (TempTb, Satir) => new { AdSoyad = TempTb.M.AdSoyad, SiparisId = (Satir == null)? "Siparişi Yok!" : Satir.SiparisId.ToString() ); // SelectMany Aynı şekilde right join işlemini yapmak için listelerin yerini değiştirmek yeterli olacaktır. Yani, LINQ sorgusunda MusteriList ile SiparisList dizilerinin yeri değiştirilir. Papatya Yayıncılık Eğitm

297 LINQ Sorgulama Yöntemi Ulama Operatörleri (Concatenation Operators) Concat(): Aynı veya farklı sıralı listedeki birden fazla veriyi yan yana birleştirir. Ayrıca iki farklı listeyi birleştirmek için de kullanılır. Concat operatöründe herhangi bir aykırı durum oluşmaması için birleştirilen verilerin aynı tür olmasına dikkat edilmeli. Concat operatörünün Union operatöründen farkı T-SQL deki Union All ifadesine denk gelmesidir. Yani birleştirdiği kaynaklardaki tekrarlı alanları teke indirgemez; olduğu gibi alır. Müşteri ve sipariş listesindeki ID bilgilerini birleştirelim. var Sonuc = SiparisList.Select(S => "Sipariş Id = "+ S.SiparisId).Concat(MusteriList.Select(M => "Müşteri Id = "+ M.MusteriId)); foreach (var bilgi in Sonuc) { Console.WriteLine(bilgi ); Sipariş Id = 1 Sipariş Id = 2 Sipariş Id = 3 Müşteri Id = 1 Müşteri Id = 2 Müşteri Id = 3 İki sıralı listeyi aynı şekilde birleştirmenin diğer bir yolu da SelectMany operatörünü kullanmaktır. var Sonuc = new[] { MusteriList.Select(M=> "Müşteri Id = "+ M.MusteriId ), SiparisList.Select(S=> "Sipariş Id = "+ S.SiparisId ).SelectMany(S=>S); foreach (var bilgi in Sonuc) { Console.WriteLine(bilgi ); Sipariş Id = 1 Sipariş Id = 2 Sipariş Id = 3 Müşteri Id = 1 Müşteri Id = 2 Müşteri Id = Sıralama Operatörleri (Ordering Operators) Bu operatörler sorgu sonucundaki kayıtları belirli alana göre sıralamayı sağlar. OrderBy, OrderByDescending: Verileri alfabetik olarak küçükten büyüğe veya büyükten küçüğe sıralar. ThenBy, ThenByDescending: Birden fazla kritere göre sıralama yapılacağı zaman bu yordamlar kullanılır; Lambda ifadelerinde kullanılır. Klasik sorgulamada birden fazla kritere göre sıralama yapılacaksa OrderBy operatöründe bu kriterler virgülle girilir. Reverse: Listedeki kayıtların orijinal sıranı tersine çevirir. Bölüm 31

298 766 C# Programlama Dili Müşterileri isimlerine göre büyükten küçüğe sıralayalım. 'Klasik sorgulama ifadesi. var Sonuc = from M in MusteriList orderby M.AdSoyad descending select M.AdSoyad; 'Lambda ifadesi var Sonuc = MusteriList.OrderByDescending(M => M.AdSoyad).Select(M => M.AdSoyad); foreach (var bilgi in Sonuc) { Console.WriteLine(bilgi); Mert Şensoy Ayşe Korkmaz Ali Korkmaz Müşterileri isimlerine göre büyükten küçüğe, kimlik (id) bilgilerine göre de küçükten büyüğe sıralayalım. Burada çoklu kritere göre sıralama yapılacağı için Lambda ifadelerinde ThenBy operatörü kullanılır. 'Klasik sorgulama ifadesi. var Sonuc = from M in MusteriList orderby M.AdSoyad descending, M.MusteriId select M.AdSoyad; 'Lambda ifadesi. var Sonuc = MusteriList.OrderByDescending(M => M.AdSoyad).ThenBy(M => M.MusteriId).Select(M => M.AdSoyad); Müşteri listesindeki kayıtların sırasını tersine çevirelim. 'Klasik sorgulama ifadesi. var Sonuc = (from M in MusteriList select M.AdSoyad).Reverse(); 'Lambda ifadesi. var Sonuc = MusteriList.Select(M => M.AdSoyad).Reverse(); Mert Şensoy Ayşe Korkmaz Ali Korkmaz Gruplama Operatörleri (Grouping Operators) GroupBy: Listeyi belirli bir kritere göre gruplamak için kullanılır. Gruplama işlemi verileri belirli kolon veya kolonlara göre özetlemek için yapılır. Gruplama işlemini basit bir örnek üzerinde gösterelim. Aşağıdaki örnekte dizideki rakamlar 5 e bölünmesi sonucu çıkan kalana göre gruplanmıştır. LINQ sorgularında Papatya Yayıncılık Eğitm

299 LINQ Sorgulama Yöntemi 767 gruplama işleminde kullanılan özetleme kriterine Key anahtar sözcüğüyle erişilir. Örneğimizdeki özetleme kriteri bölüm sonucu kalandır. // Kaynak dizi. int[] sayilar = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 ; // Sayıları 5'e bölümünde kalanlarına göre gruplayalım. var gruplanmissayilar = from s in sayilar group s by s % 5 into g orderby g.key select new { Kalan = g.key, SayiGrup= g ; foreach (var g in gruplanmissayilar) { Console.WriteLine("5'e bölümünden {0 kalanını verenler :", g.kalan); foreach (var s in g.sayigrup) { Console.WriteLine(s); 5'e bölümünden 0 kalanını verenler : 5 0 5'e bölümünden 1 kalanını verenler : 1 6 5'e bölümünden 2 kalanını verenler : 7 2 5'e bölümünden 3 kalanını verenler : 3 8 5'e bölümünden 4 kalanını verenler : 4 9 Aynı mantıkla örneklerimizde kullandığımız sipariş listesini müşteri numaralarına göre gruplayalım. var Sonuc= from S in SiparisList group S by S.MusteriId into SipGrup select new { MusteriNo = SipGrup.Key, Siparisler = SipGrup ; foreach (var g in Sonuc) { Console.WriteLine("{0 nolu müşteri siparişleri", g.musterino); foreach (var s in g.siparisler) { Console.WriteLine(" Sipariş No : {0", s.siparisid); 1 nolu müşteri siparişleri Sipariş No : 1 Sipariş No : 2 2 nolu müşteri siparişleri Sipariş No : 3 Group By ifadesi kullanılarak özetleme işlemi yapıldığında derleyici bu ifadeyi GroupBy() isimli genişletme yordamına çevirir. GroupBy(), geriye Bölüm 31

300 768 C# Programlama Dili System.Linq.IGrouping türünde değer döndürür. Özetlenmiş olan veriyi temsil eden IGrouping arabirimi IEnumerable arabiriminden türemiştir. Dolayısıyla gruplanmış olan veri üzerinde de LINQ operatörlerini çalıştırabiliriz. IGrouping arabiriminin tek bir üyesi bulunur. O da döndürülen kayıt kümesinin özetleme seviyesini belirten Key özelliğidir. Aynı örneği Lambda ifadeleriyle yazdığımızda GroupBy() yordamının kullanımını daha kolay görmüş oluruz. var Sonuc = SiparisList.GroupBy(S => S.MusteriId).Select( SipGrup => new { MusteriNo = SipGrup.Key, Siparisler = SipGrup ); //Select Örneği biraz daha geliştirip müşterinin siparişlerini yıllara göre özetleyelim. Örneğin anlamlı olması için listedeki 2 nolu siparişin tarihini 01/01/2008 olarak değiştirelim. Böylece 1 nolu müşterinin hem 2007 hem 2008 yılı siparişi olmuş olur. var Sonuc = from S in SiparisList group S by S.MusteriId into SipGrup select new { //1 MusteriNo = SipGrup.Key, YilGrup = from Sg in SipGrup group Sg by Sg.Tarih.Year into YilGrup select new {//2 Yil = YilGrup.Key, Siparisler = YilGrup //2 ;//1 foreach (var s in Sonuc) { Console.WriteLine("{0 nolu müşteri siparişleri", s.musterino); foreach (var y in s.yilgrup) { Console.WriteLine("\t{0 yılı siparişleri", y.yil); foreach (var sp in y.siparisler) { Console.WriteLine("\t\tSipariş No : {0", sp.siparisid ); Papatya Yayıncılık Eğitm

301 LINQ Sorgulama Yöntemi nolu müşteri siparişleri 2007 yılı siparişleri Sipariş No : yılı siparişleri Sipariş No : 2 2 nolu müşteri siparişleri 2007 yılı siparişleri Sipariş No : 3 Yıl bazında özetleme yapan bu örneği Lambda ifadeleriyle yazalım. var Sonuc = SiparisList.GroupBy(S => S.MusteriId).Select(//1 SipGrup => New { //2 MusteriNo = SipGrup.Key, YilGrup = SipGrup.GroupBy(Sg => Sg.Tarih.Year).Select(//3 YilGrup => new { Yil = YilGrup.Key, Siparisler = YilGrup )//3 //2 );// Küme Operatörleri (Set Operators) Distinct (Tekrarsız): Kaynak listede tekrarlanan kayıtlar teke indirgenir; yani sonuç kümesine bir kez yazılır. Except (Farklı Olan): İki kayıt listesini parametre olarak alıp birinci listede olup ikinci listede olmayan kayıtları döndürür. Bölüm 31

302 770 C# Programlama Dili Intersect (Kesişim): İki listeyi karşılaştırıp iki listede de bulunan kayıtları döndürür. Union (Birleşim): İki sıralı listeyi birleştirir. Concat operatöründen farklı olarak heriki listede olan kayıtları bir kere yazar. İki örnek liste üzerinde küme operatörlerinin sonucu nasıl oluşturduğunu görelim. int[] Lst1= {10, 20, 10, 40, 50; int[] Lst2= {40, 50, 60, 70, 80; var Sonuc1 = Lst1.Distinct(); Console.WriteLine("Lst1.Distinct"); foreach (var s in Sonuc1) { Console.WriteLine(s); var Sonuc2 = Lst1.Except(Lst2); Console.WriteLine("Lst1.Except(Lst2)"); foreach (var s in Sonuc2) { Console.WriteLine(s); var Sonuc3 = Lst1.Intersect(Lst2); Console.WriteLine("Lst1.Intersect(Lst2)"); foreach (var s in Sonuc3) { Console.WriteLine(s); var Sonuc4 = Lst1.Union(Lst2); Console.WriteLine("Lst1.Union(Lst2)"); foreach (var s in Sonuc4) { Console.WriteLine(s); var Sonuc5 = Lst1.Concat(Lst2); Console.WriteLine("Lst1.Concat(Lst2)"); foreach (var s in Sonuc5) { Console.WriteLine(s); Lst1.Distinct Lst1.Except(Lst2) Lst1.Intersect(Lst2) Lst1.Union(Lst2) Papatya Yayıncılık Eğitm

303 LINQ Sorgulama Yöntemi 771 Lst1.Concat(Lst2) Klasik örneğimize dönüp şimdiye kadar sipariş vermiş müşterilerin bilgilerini listeleyelim. Sipariş listesinde bir müşteriye ait birden fazla kayıt olabileceği için tekrarlanan kayıtları teke indirgememiz lazım. var Sonuc = (from M in MusteriList join S in SiparisList on M.MusteriId equals S.MusteriId select M.AdSoyad).Distinct(); foreach (var s in Sonuc) { Console.WriteLine(s); Ali Korkmaz Ayşe Korkmaz Conversion Operators (Dönüştürme Operatörleri) AsEnumerable: Derleyicinin mevcut listeyi IEnumerable<T> türünde görmesini sağlar. Örneğin bir SQL tablosuna uygulandığı zaman derleyici, o nesneyi artık SQL deki tablo türünde değil IEnumerable<T> türünde değerlendirir. Cast: Uygulandığı listedeki elemanı parametre olarak aldığı türe dönüştürür. Dönüştürülemeyen elemana rastlandığı zaman hata oluşur. OfType: Listeden parametre olarak verilmiş türe dönüşebilenleri seçer. ToArray: Sıralı listeden bir dizi oluşturur. ToDictionary: Sıralı listeden bir Dictionary nesnesi oluşturur. ToList: Sıralı listeden bir List nesnesi oluşturur. ToLookup: Sıralı listeden bir Lookup nesnesi oluşturur. Aşağıdaki örnekte basit bir dönüştürme işlemi yapılmıştır. ArrayList Arr= new ArrayList(); Arr.AddRange(new string[] {"A","B","C"); IEnumerable<string> Lst = Arr.Cast<string>(); ToXXX dönüştürme yordamları LINQ sorgusunun o anda çalışmasını sağlar. Şu ana kadar sipariş vermiş müşterileri sıralı bir şekilde bir diziye aktaralım. Bölüm 31

304 772 C# Programlama Dili Array Arr = (from M in MusteriList join S in SiparisList on M.MusteriId equals S.MusteriId select M.AdSoyad).Distinct().ToArray(); foreach (string s in Arr) { Console.WriteLine(s); Ali Korkmaz Ayşe Korkmaz OfType operatörü Cast den farklı olarak listedeki elemanlardan, belirlenmiş türe sadece dönüştürülebilenleri geri döndürür. Dönüştürülemeyenler için herhangi bir hata üretmez. object[] Lst = { null, 1.0, "ahmet", 3, 4.0f, 5, ".net", 7.0 ; var Sayilar = Lst.OfType<double>(); Console.WriteLine("Ondalıklı elemanlar :"); foreach (var s in Sayilar) { Console.WriteLine(s); Ondalıklı elemanlar : Eşitlik Operatörleri (Equality Operators) SequenceEqual: Bu operatör iki listenin birebir aynı olup olmadığını kontrol eder. Aşağıda bu operatörün kullanımı için örnek verilmiştir: Musteri om1 = new Musteri { MusteriId = 1, AdSoyad = "Ahmet" ; Musteri om2 = new Musteri { MusteriId = 2, AdSoyad = "Ali" ; // Musteri türünde iki liste oluşturalım. List<Musteri> mlist1 = new List<Musteri> { om1, om2 ; List<Musteri> mlist2 = new List<Musteri> { om1, om2 ; // İki listenin eşit olup olmadığını denetleyelim. bool esitmi = mlist1.sequenceequal(mlist2); Console.WriteLine("Bu iki liste {0.", esitmi? "eşit" : "eşit değil"); Bu iki list eşit Eleman Operatörleri (Element Operators) DefaultIfEmpty: Listedeki boş sıralar için varsayılan bir eleman listesi oluşturur. DefaultIfEmpty operatörü Group Join ile birlikte Left Join işlemini yapmak için kullanılabilir. ElementAt: Listenin parametre olarak aldığı indisteki elemanını döndürür. Papatya Yayıncılık Eğitm

305 LINQ Sorgulama Yöntemi 773 ElementAtOrDefault: ElementAt() yordamından farklı olarak parametre olarak aldığı indiste kayıt yoksa hata üretmek yerine geriye varsayılan değere sahip bir element döndürür. First: Listedeki ilk elemanı döndürür. FirstOrDefault: Listenin ilk elemanı yoksa, yani listede hiç eleman yoksa varsayılan değerde bir eleman döndürür. Last: Listenin son elemanının döndürür. LastOrDefault: Listenin son elemanı yoksa varsayılan değerde bir eleman döndürür. Single: Parametre olarak aldığı koşul ifadesine uyan tek kaydı döndürür. Herhangi bir koşul girilmezse listedeki tek kaydı döndürür. Koşula uyan birden fazla kayıt varsa hata mesajı oluşur. SingleOrDefault: Parametre olarak aldığı koşul ifadesine uyan kayıt yoksa varsayılan türden tek kayıt döndürür. Koşula uyan birden fazla kayıt varsa hata mesajı oluşur. Görüldüğü gibi her operatörün XXXOrDefault türevi bulunmaktadır; bu genel olarak uygun kayıt bulamadığı zaman hata üretilmesin diye kullanılır. Ayrıca bu operatörlerin koşul parametresi alan türevleri de bulunmaktadır. Örneğin, yaşı 30 dan büyük ilk müşterinin kaydına erişmek için First() yordamına 30 dan büyük koşulu parametre olarak geçilir. int[] Sayilar = { 1, 2, 3, 4, 5; Console.WriteLine("First()"); Console.WriteLine("\tİlk Eleman : {0",Sayilar.First()); Console.WriteLine("\tİlk Çift Eleman : {0", Sayilar.First(s => s % 2 == 0)); Console.WriteLine("\nLast()"); Console.WriteLine("\tSon Eleman : {0", Sayilar.Last()); Console.WriteLine("\tSon Çift Eleman : {0", Sayilar.Last(s => s % 2 == 0)); Console.WriteLine("\nFirst()"); Try { // 5'ten büyük elemanı isteyelim. Sayilar.First(s => s > 5); catch { Console.WriteLine("\t5'ten büyük eleman bulunmamaktadır."); Console.WriteLine("\nFirstOrDefault()"); // 5'ten büyük elemanı isteyelim. Console.Write("\t5'ten büyük ilk eleman : "); Console.WriteLine(Sayilar.FirstOrDefault(s => s > 5)); First() Last() İlk Eleman : 1 İlk Çift Eleman : 2 Son Eleman : 5 Son Çift Eleman : 4 First() 5'ten büyük eleman bulunmamaktadır. FirstOrDefault() 5'ten büyük ilk eleman : 0 Bölüm 31

306 774 C# Programlama Dili Örnekte 5 ten büyük ilk elemanı isterken listede bu kritere uyan herhangi bir kayıt olmadığı için try-catch bloğu kullanıldı. Blok dışında kullanılmış olunsa idi Sequence contains no matching element hatası ile karşılaşılırdı. FirstOrDefault() yordamında böyle bir sorun bulunmamaktadır; bu yordam koşula uygun kayıdı bulamadığı zaman geriye listenin türü olan tamsayının varsayılan değerini (0) döndürür. Aynı durum Single() yordamı için de geçerlidir. Single() yordamına ikiye bölünme koşulunu girdiğimizde kaynak listede bu koşula uyan birden fazla kayıt olduğu için sistem hata verecektir. Console.WriteLine("\nSingle()"); Console.WriteLine("\t3'e bölünen sayı : {0", Sayilar.Single(s => s % 3 == 0)); // Koşula uyan birden fazla kayıt varsa Single() yordamı hata verir. try { Sayilar.Single(s => s % 2 == 0); catch { Console.WriteLine("\t2'ye bölünen birden fazla kayıt var."); Console.WriteLine("\nSingleOrDefault()"); // 6'ya bölünen elemanı isteyelim. Console.Write("\t6'ya bölünen sayı : : "); // Uygun kayıt olmadığı default değer döndürülür. Console.WriteLine(Sayilar.SingleOrDefault(s => s % 6 == 0)); // Koşula uyan birden fazla kayıt varsa SingleOrDefault() hata üretir. try { Sayilar.SingleOrDefault(s => s % 2 == 0); Catch { Console.WriteLine("\t2'ye bölünen birden fazla kayıt var."); Single() 3'e bölünen sayı : 3 2'ye bölünen birden fazla kayıt var. SingleOrDefault() 6'ya bölünen sayı : : 0 2'ye bölünen birden fazla kayıt var. Müşteri listesinde ismi Ali ile başlayan kayıtlardan ilkini okuyalım. Musteri IlkMusteri = MusteriList.Where(M => M.AdSoyad.Contains("Ali")).First(); Console.WriteLine(IlkMusteri.AdSoyad); Ali Korkmaz Aynı örneği, ismi Burçin ile başlayan kayıtlar için yapalım. Listede Burçin kayıdı olmadığı için First() yordamı hata üretecektir. Kayıt bulunamadığı zaman kaynak listenin varsayılan türünden bir kayıt döndüren FirstOrDefault() yorda- Papatya Yayıncılık Eğitm

307 LINQ Sorgulama Yöntemi 775 mı kullanılabilir; kaynak listenin türü referans türden (nesne türü) olduğu için geriye null değerinde bir Musteri nesnesi döner. Bu durumda o nesnenin AdSoyad özelliğini yazdırmaya çalıştığımız için Object reference not set to an instance of an object. hatası alırız. Bu hatayla karşılaşmamak için örneğimizde kullandığımız try-catch bloğu tercih edebilir veya DefaultIfEmpty() yordamını kullanabiliriz. Eğer koşula uyan herhangi bir kayıt bulunamazsa AdSoyad özelliği Kayıt Bulunamadı olan bir müşteri nesnesi döndürelim. Musteri IlkMusteri = MusteriList.Where(M => M.AdSoyad.Contains("Burçin")).DefaultIfEmpty( new Musteri {MusteriId=0,AdSoyad="Kayıt Bulunamadı." ).First(); Console.WriteLine(IlkMusteri.AdSoyad); Kayıt bulunamadı Üretim Operatörleri (Generation Operators) Empty: Parametre olarak aldığı türden boş bir kayıt döndürür. Range: start ve count isminde iki parametre alıp verilmiş olan aralıkta ardışık elemanlardan oluşan sayısal bir liste oluşturur. Repeat: element ve count isminde iki parametre alır. İlk parametrenin temsil ettiği elemanı ikinci parametredeki değer kadar tekrarladığı bir liste oluşturur. // 1-6 arasındaki sayıların kareleri. int[] kareler = Enumerable.Range(1, 6).Select(x => x * x).toarray(); Console.WriteLine("1-6 arasındaki sayıların kareleri"); foreach (var s in kareler) Console.WriteLine(s); // 5 tane "Ali" kaydı içeren bir sıralı liste. string[] isimler = Enumerable.Repeat("Ali", 5).ToArray(); Console.WriteLine("\n5 tane 'Ali' kaydı içeren sıralı liste"); foreach (var i in isimler) Console.WriteLine(i); // Empty operatörü IEnumerable<Musteri> boskayit = Enumerable.Empty<Musteri>(); 1-6 arasındaki sayıların kareleri tane 'Ali' kaydı içeren sıralı liste Ali Ali Ali Ali Ali Bölüm 31

308 776 C# Programlama Dili Ölçüm Operatörleri (Quantifiers Operators) Any: Kaynak listede herhangi bir elemanın verilen koşula uyup uymadığını ölçer. All: Any opeiratöründen farklı olarak listedeki tüm elemanların verilmiş olan koşula uyup uymadığını bildirir. Contains: Kaynak listenin parametre olarak verilmiş elemanı içerip içermediğini bildirir. Bu operatörler parametre aldıkları koşula uygun olarak geriye bool türünde değer döndürür. bool sonuc = MusteriList.Any(M=>M.AdSoyad.Contains("Ali")); Console.WriteLine("'Ali'yi içeren herhangi bir müşteri var mı: {0",sonuc); 'Ali'yi içeren herhangi bir müşteri var mı: True ADO.NET in ilk örneklerinde kullandığımız Urun tablosunu düşünelim. Bütün ürünlerinin stok değerinin 4 ten büyük olduğu kategorileri listeleyelim. Bu kritere 1 nolu kategori uymayacaktır. Çünkü bu kategorinin altındaki Urun4 ürününün stok değeri 4 ten büyük değil. Uruns.GroupBy(u => u.kategoriid). Where(g => g.all(p => p.adet > 4)). Select(g => g) Gruplama Fonksiyonu Operatörleri (Aggregate Operators) Aggregate: Sıralı bir liste üzerinde gruplama sağlayacak bir fonksiyonu uygular. Average: Listedeki sayısal değerlerin aritmetik ortalamasını alır. Count, LongCount: Listedeki elemanların sayısını döndürür. Count operatörü tamsayı, LongCount operatörü uzun tamsayı değer döndürür. Max ve Min: İlki listedeki en büyük ikincisi en küçük değeri döndürür. Sum: Listede belirlenmiş bir alandaki değerlerin toplamını verir. int[] sayilar = { 5, 14, 6, 5, 53, -5; // Basit Count yordamı. int sayi = sayilar.count(); Papatya Yayıncılık Eğitm

309 LINQ Sorgulama Yöntemi 777 Console.WriteLine("Eleman sayısı: {0",sayi); // Distinct Count yordamı. sayi = sayilar.distinct().count(); Console.WriteLine("Tekrarsız eleman sayısı: {0", sayi); // Koşullu Count yordamı. sayi = sayilar.count(s => s % 2 == 0); Console.WriteLine("Çift rakamların sayısı: {0", sayi); // Basit Max yordamı. sayi = sayilar.max(); Console.WriteLine("\nEn büyük sayı: {0", sayi); // Koşullu Max yordamı. sayi = sayilar.max(s => s % 10); Console.WriteLine("10'a bölümünden kalanı en büyük olan sayı: {0", sayi); sayi = sayilar.where(s => s % 2 == 0).Max(); Console.WriteLine("En büyük çift sayı: {0", sayi); // Basit Min yordamı. sayi = sayilar.min(); Console.WriteLine("\nEn küçük sayı: {0", sayi); // Koşullu Min yordamı. sayi = sayilar.min(s => s % 10); Console.WriteLine("10'a bölümünden kalanı en küçük olan sayı: {0", sayi); sayi = sayilar.where(s => s % 2 == 0).Min(); Console.WriteLine("En küçük çift sayı: {0", sayi); // Basit Sum yordamı. sayi = sayilar.sum(); Console.WriteLine("\nElemanların toplamı: {0", sayi); // Koşullu Sum yordamı. sayi = sayilar.where(s => (s % 2 == 1 s % 2 == -1 )).Sum(); Console.WriteLine("Tek sayıların toplamı: {0", sayi); // Basit Average yordamı. double ort = sayilar.average(); Console.WriteLine("\nAritmetik ortalama: {0", ort); // Koşullu Average yordamı. ort = sayilar.where(s => s > 5).Average(); Console.WriteLine("5'ten büyük sayıların ortalaması: {0", ort); Eleman sayısı: 6 Tekrarsız eleman sayısı: 5 Çift rakamların sayısı: 2 En büyük sayı: 53 10'a bölümünden kalanı en büyük olan sayı: 6 En büyük çift sayı: 14 En küçük sayı: -5 10'a bölümünden kalanı en küçük olan sayı: -5 En küçük çift sayı: 6 Elemanların toplamı: 78 Tek sayıların toplamı: 58 Aritmetik ortalama: 13 5'ten büyük sayıların ortalaması: 24, Bölüm 31

310 778 C# Programlama Dili Müşteri ve sipariş listelerini birleştirip her müşterinin kaç adet sipariş verdiğini ve kaç adet ürün aldığını sorgulayalım. // Klasik sorgulama ifadesi. from M in MusteriList join S in SiparisList on M.MusteriId equals S.MusteriId into Grup select new { M.AdSoyad,SipAdet=Grup.Count(), UrunAdet=Grup.Sum(G=> G.Adet) // Lambda ifadesi. MusteriList.GroupJoin( SiparisList, M => M.MusteriId, S => S.MusteriId, (M, Grup) => new { AdSoyad = M.AdSoyad, SipAdet = Grup.Count(), UrunAdet = Grup.Sum(G => G.Adet) ); Bu yordamlar koşula uyan kayıt bulunamadığı zaman hata üretirler. Dolayısıyla bunların try-catch bloğunda kullanılması daha doğru olacaktır. LINQ ortamında Sum ve Count gibi gruplama fonksiyonları yazabilir ve LINQ sorgularında bunlar kullanılabilir. Aşağıda, listedeki elemanları kümülatif çarpan bir yordamın nasıl oluşturulduğu gösterilmiştir. using System; using System.Collections.Generic; static class Program { static void Main(string[] args) { int[] sayilar = { 2, 3, 4, -2 ; Console.WriteLine("Elemanların çarpımı: {0",sayilar.Carpma()); Console.ReadLine(); public static int Carpma(this IEnumerable<int> kaynak) { int crp = 1; foreach (int k in kaynak) crp = k*crp; return crp; Elemanların çarpımı: -48 LINQ sorgularında bu tür iç iterasyon işlemleri yapmak için Aggregate() yordamı da kullanılabilir. Aggregate() yordamı çekirdek bir sayıyı referans alıp belirlenmiş olan bir fonksiyonu çalıştırır ve onun ürettiği sonuçları birleştirir. Papatya Yayıncılık Eğitm

311 LINQ Sorgulama Yöntemi 779 public static TResult Aggregate<TSource, TAccumulate, TResult>( this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func, Func<TAccumulate, TResult> resultselector); En son örneğimiz olan kümülatif çarpma işlemini Aggregate() yordamıyla aşağıdaki gibi basitçe yazabiliriz. int[] sayilar = { 2, 3, 4, -2 ; int sonuc = sayilar.aggregate((x, y) => x * y); Console.WriteLine("Elemanların çarpımı: {0", sonuc); Elemanların çarpımı: -48 Bu örnekte Aggregate() in yaptığı iş iki elemanı çarpıp çıkan sonucu bir sonraki elemanla çarpmasıdır. 2 * 3 = 6 6 * 4 = * -2 = -48 Lambda ifadesi kullanılmadan aşağıdaki gibi iki parametre alan bir yordamı da çağırabiliriz. static void Main(string[] args) { int[] sayilar = { 2, 3, 4, -2 ; int sonuc = sayilar.aggregate(carp); Console.WriteLine("Elemanların çarpımı: {0", sonuc); static int Carp(int x, int y) { return x * y; Aggregate() yordamını kullanarak bu bölümde işlediğimiz grupsal fonksiyoların görevini yerine getirebiliriz. Aşağıdaki tabloda bu işlemlerin nasıl yazılacağı gösterilmiştir. int[] sayilar = { 1, 2, 3, 5, 7, 11 ; int sum = sayilar.aggregate(0, (x, y) => x + y); int product = sayilar.aggregate(1, (x, y) => x * y); int count = sayilar.aggregate(0, (x, y) => x + 1); int max = sayilar.aggregate(int.minvalue, (x, y) => Math.Max(x, y)); int min = sayilar.aggregate(int.maxvalue, (x, y) => Math.Min(x, y)); İleri LINQ Örnekleri LINQ operatörleriyle dizi, koleksiyon ve veritabanı gibi IEnumerable ve IQueryable arabirimlerini destekleyen veri kaynakları sorgulanabilir. Bu ayrıtta LINQ teknolojisinin sağlamış olduğu kolaylığı daha açık görmek için farklı örnekler veri- Bölüm 31

312 780 C# Programlama Dili lecektir. İlk örneğimizde System.String sınıfının yordamlarını sorgulayacağız. Aşağıda bu sınıfın yordamları ve onların kaç kez yeniden yüklendiği sorgulamaktadır. var liste = from m in typeof(string).getmethods() orderby m.name where m.declaringtype == typeof(string) group m by m.name into g orderby g.count() select new { Yordam = g.key, OverloadAdet = g.count() ; Console.WriteLine("String sınıfının {0 yordamı bulunur.", liste.count()); Console.WriteLine(""); foreach (var l in liste) { Console.WriteLine(l); String sınıfının 45 yordamı bulunur. { Yordam = Clone, OverloadAdet = 1 { Yordam = Contains, OverloadAdet = 1 { Yordam = Copy, OverloadAdet = 1 { Yordam = CopyTo, OverloadAdet = 1 { Yordam = get_chars, OverloadAdet = 1 { Yordam = get_length, OverloadAdet = 1 { Yordam = GetEnumerator, OverloadAdet = 1 { Yordam = GetHashCode, OverloadAdet = 1 { Yordam = GetTypeCode, OverloadAdet = 1 { Yordam = Insert, OverloadAdet = 1 { Yordam = Intern, OverloadAdet = 1 { Yordam = IsInterned, OverloadAdet = 1 { Yordam = IsNullOrEmpty, OverloadAdet = 1 { Yordam = op_equality, OverloadAdet = 1 { Yordam = op_inequality, OverloadAdet = 1 { Yordam = ToLowerInvariant, OverloadAdet = 1 { Yordam = ToUpperInvariant, OverloadAdet = 1 { Yordam = TrimEnd, OverloadAdet = 1 { Yordam = TrimStart, OverloadAdet = 1 { Yordam = CompareOrdinal, OverloadAdet = 2 { Yordam = CompareTo, OverloadAdet = 2 { Yordam = IsNormalized, OverloadAdet = 2 { Yordam = Join, OverloadAdet = 2 { Yordam = Normalize, OverloadAdet = 2 { Yordam = PadLeft, OverloadAdet = 2 { Yordam = PadRight, OverloadAdet = 2 { Yordam = Remove, OverloadAdet = 2 { Yordam = Replace, OverloadAdet = 2 { Yordam = Substring, OverloadAdet = 2 { Yordam = ToCharArray, OverloadAdet = 2 { Yordam = ToLower, OverloadAdet = 2 { Yordam = ToString, OverloadAdet = 2 { Yordam = ToUpper, OverloadAdet = 2 { Yordam = Trim, OverloadAdet = 2 { Yordam = EndsWith, OverloadAdet = 3 { Yordam = IndexOfAny, OverloadAdet = 3 { Yordam = LastIndexOfAny, OverloadAdet = 3 Papatya Yayıncılık Eğitm

313 LINQ Sorgulama Yöntemi 781 { Yordam = StartsWith, OverloadAdet = 3 { Yordam = Equals, OverloadAdet = 5 { Yordam = Format, OverloadAdet = 5 { Yordam = Split, OverloadAdet = 6 { Yordam = Compare, OverloadAdet = 8 { Yordam = Concat, OverloadAdet = 9 { Yordam = IndexOf, OverloadAdet = 9 { Yordam = LastIndexOf, OverloadAdet = 9 İkinci örnekte System.IO kütüphanesi kullanılarak bir klasördeki dosyalar sorgulanacaktır. Diskteki klasörleri yönetmek için kullanılan DirectoryInfo sınıfının GetFiles() ve GetDirectories() yordamları dizi türünde değer döndürdükleri için dönen listeyi LINQ genişletme yordamlarıyla sorgulayabiliriz. Aşağıdaki örnekte C:\ klasörünün altında kaç farklı dosya türünün bulunduğu sorgulanmıştır. DirectoryInfo odi = new DirectoryInfo(@"C:\"); var liste = from f in odi.getfiles() orderby f.name where f.extension!="" group f by f.extension into g orderby g.count() descending select new { Uzanti = g.key, DosyaSayisi = g.count(), ToplumBuyukluk=g.Sum(f=>f.Length)/1024 +" KB" ; Console.WriteLine(@"'C:\' klasörünün altında {0 dosya bulunmaktadır.", liste.count()); Console.WriteLine(""); foreach (var l in liste) { Console.WriteLine(l); 'C:\' klasörünün altında 12 dosya bulunmaktadır. { Uzanti =.sqm, DosyaSayisi = 4, ToplumBuyukluk = 1 KB { Uzanti =.sys, DosyaSayisi = 2, ToplumBuyukluk = KB { Uzanti =.SYS, DosyaSayisi = 2, ToplumBuyukluk = 0 KB { Uzanti =.Log, DosyaSayisi = 1, ToplumBuyukluk = 4 KB { Uzanti =.bat, DosyaSayisi = 1, ToplumBuyukluk = 100 KB { Uzanti =.inf, DosyaSayisi = 1, ToplumBuyukluk = 0 KB { Uzanti =.ini, DosyaSayisi = 1, ToplumBuyukluk = 0 KB { Uzanti =.com, DosyaSayisi = 1, ToplumBuyukluk = 101 KB { Uzanti =.MDF, DosyaSayisi = 1, ToplumBuyukluk = KB { Uzanti =.LDF, DosyaSayisi = 1, ToplumBuyukluk = 1024 KB { Uzanti =.COM, DosyaSayisi = 1, ToplumBuyukluk = 46 KB { Uzanti =.EXE, DosyaSayisi = 1, ToplumBuyukluk = 41 KB LINQ sorgu yapısını kullanabileceğimiz bir başka alan da Reflection işlemleridir. Bütünleştirilmiş kodları (assembly) ve üyelerini LINQ aracılığıyla kolaylıkla sorgulayabiliriz. Aşağıdaki basit örnekte üzerinde çalıştığımız projede hangi sınıfların ve yordamların kullanıldığı sorgulanmıştır. Bu örnek projeye System.Reflection kütüphanesini eklemeyi unutmayalım. Bölüm 31

314 782 C# Programlama Dili Assembly asm = Assembly.LoadFrom(@"C:\Kitap\Ornek\bin\AsmOrnek.exe"); var liste = from t in asm.gettypes() where t.ispublic from m in t.getmethods() select new { t, m ; Console.WriteLine(@"Bu Assemblynin içinde {0 public yordam bulunmaktadır.", liste.count()); Console.WriteLine(""); foreach (var l in liste) { Console.WriteLine(l); Bu Assemblynin içinde 13 public yordam bulunmaktadır. { t = Program, m = Void Main(System.String[]) { t = Program, m = System.Type GetType() { t = Program, m = System.String ToString() { t = Program, m = Boolean Equals(System.Object) { t = Program, m = Int32 GetHashCode() { t = Musteri, m = System.Type GetType() { t = Musteri, m = System.String ToString() { t = Musteri, m = Boolean Equals(System.Object) { t = Musteri, m = Int32 GetHashCode() { t = Siparis, m = System.Type GetType() { t = Siparis, m = System.String ToString() { t = Siparis, m = Boolean Equals(System.Object) { t = Siparis, m = Int32 GetHashCode() Son bir örnek olarak programın çalıştığı sistemdeki olay geçmişlerini (log) sorgulayalım. Aşağıdaki satırlarda SQL Sunucunun yayınladığı son 10 geçmişi listelemekteyiz. Örneğimizde System.Diagnostics kütüphanesini kullanacağız. EventLog oel = new EventLog("Application"); var sonuc = from EventLogEntry el in oel.entries where el.source == "MSSQLSERVER" orderby el.timegenerated descending select new { Tarih = el.timegenerated, Mesaj = el.message ; foreach (var s in sonuc.take(10)) { Console.WriteLine(s); LINQ To Dataset ADO.NET ortamının bağlantısız katmanında veri kaynağından alınmış verileri bellekte tutmak için kullanılan DataSet, DataTable ve DataView nesnelerindeki verileri sorgulamanın farklı yöntemleri bulunur. En popüleri DataTable nesnesinin Select() yordamıdır. Bu, veri tablosundaki kayıtları belirli kriter ve sıralamaya Papatya Yayıncılık Eğitm

315 LINQ Sorgulama Yöntemi 783 göre listeler. İkinci bir yöntem olarak DataView nesnesinin RowFilter özelliği kullanılabilir. Başka bir yöntem ise DataView nesnesinin Find() veya FindRows() yordamlarının kullanılmasıdır. Bu klasik yöntemler nesnesel sorgu ifadeleri yerine SQL ifadelerini kullanır. Ayrıca karmaşık koşul sorgularında yetersiz kalmaktadır. Şu ana kadar verdiğimiz örneklerden de görebileceğimiz gibi bu tür nesneleri sorgulamanın en esnek yolu LINQ yöntemini kullanmaktır. System.Data.DataTableExtensions.dll kütüphanesi bağlantısız nesneleri LINQ ortamında sorgulamak için çeşitli genişletme yordamları içermektedir. DataTableExtensions sınıfının bahsi geçen nesnelere kazandırdığı genişletme yordamları şunlardır: Şekilde de görüldüğü gibi DataTable nesnesi için AsEnumerable() ve AsDataView() yordamları geliştirilmiştir. İlki, DataTable üzerinde LINQ sorgularını çalıştırmak için kullanılır. Sorgu sonucunda oluşan kayıt dizisini bazı nesnelere bağlamak veya kayıtlar üzerinde değişiklik yapmak için AsDataView() yordamı kullanılarak sorgu sonucu DataView nesnesine dönüştürülür. Aynı şekilde sorgu sonucunu başka bir DataTable nesnesine aktarmak için CopyToDataTable() yordamı kullanılır. Projeye DataTableExtensions kütüphanesini referans olarak ekleyip basit bir örnek yapalım. ADO.NET bölümünde örnek olarak kullandığımız Musteri tablosu üzerinde çalışacağız; bu tablodaki kayıtları aşağıdaki sorguyla listeleyebiliriz. Bölüm 31

316 784 C# Programlama Dili DataTable odt = ods.tables["musteri"]; var Sonuc = from m in odt.asenumerable() select m; foreach (var s in Sonuc) Console.WriteLine(s["AdSoyad"]); Ali Korkmaz Ayşe Korkmaz Mert Şensoy Sorgu ifadesinde DataTable nesnesindeki kayıt dizisinin kolonlarına erişmek DataTable satırlarının veri türü olan DataRow nesnesinin indisleyicisi veya DataRowExtensions sınıfının DataRow için kullanıma sunduğu Field() genişletme yordamı kullanılır. Yeniden yüklenmiş olan Field() yordamına parametre olarak kolonun adı veya indisi girilir. Ayrıca girilen kolon üzerinde işlem yapmak için kolonun döndüreceği değerin türü de belirtilir. Bu türün doğru girilmesi gerekir; aksi taktirde derleme zamanında hataya neden olur. Örneğimizi kayıtları alfabetik olarak büyükten küçüğe sıralayacak değiştirelim. // Indexer kullanımı. var Sonuc = from m in odt.asenumerable() orderby m["adsoyad"] descending select m; foreach (var s in Sonuc) Console.WriteLine(s["AdSoyad"]); // Field() yordamı kullanımı. var Sonuc = from m in odt.asenumerable() orderby m.field<string>("adsoyad") descending select m; foreach (var s in Sonuc) Console.WriteLine(s.Field<string>("AdSoyad")); Mert Şensoy Ayşe Korkmaz Ali Korkmaz Sorgu sonucu oluşan kayıt dizisinden belirli kolonları seçelim. var Sonuc = from m in odt.asenumerable() orderby m.field<string>("adsoyad") descending select new{ MusteriId=m.Field<int>("MusteriId"), AdSoyad = m.field<string>("adsoyad") ; foreach (var s in Sonuc) Console.WriteLine(s.MusteriId +"» "+ s.adsoyad); 3» Mert Şensoy 2» Ayşe Korkmaz 1» Ali Korkmaz Papatya Yayıncılık Eğitm

317 LINQ Sorgulama Yöntemi 785 DataSet içindeki bir alanı okumak için DataRow nesnesinin indeksleyici üyesini kullandığımızda, geri dönen değer object türünde olacağı için bunu doğru veri türüne dönüştürmek gerekir. Fakat Field() yordamında okunan alanın veri türü de girildiği için dönüştürme işlemi yapılmasına gerek kalınmaz. // Indexer kullanımı. var Sonuc = from m in odt.asenumerable() where (string)m["adsoyad"] == "Ali Korkmaz" select m; // Field() yordamı kullanımı. var Sonuc = from m in odt.asenumerable() where m.field<string>("adsoyad")=="ali Korkmaz" select m; Field() yordamının avantajlı olduğu diğer durum da null türündeki kayıtlardır. Bilindiği gibi DataSet içindeki null değerini taşıyan kayıtlar, CLR ın nullable veri türüyle değil bu kayıtlar için geliştirilmiş olan DBNull veri türüyle ilişkilendirilir. Yani kayıtlar üzerinde filtre işlemi uygulanmak istendiğinde aşağıdaki gibi yazılır: odt["adsoyad"]==null yazmak yerine odt["adsoyad"] == DBNull.Value Ayrıca indeksleyici yönteminde önceki örnekte bahsettiğimiz dönüştürme işlemi yaptığımızda null kayıtlar için hata oluşur. SQL Sunucu tarafındaki tabloya AdSoyad kolonu null olan bir kayıt ekleyip önceki örneğimizi çalıştırdığımızda aşağıdaki hata mesajıyla karşılaşırız. Bu sorunu aşmak için Where ifadesinde kaydın null olup olmadığı kontrol edilip ona göre koşul yönlendirmesi yapılır veya indexer yöntemi yerine Field() yordamı kullanılır. Önceki örneğimizi AdSoyad bilgisi sadece null olan kayıtları sorgulayacağımız şekilde değiştirelim. var Sonuc = from m in odt.asenumerable() where m.field<string>("adsoyad") == null orderby m.field<string>("adsoyad") descending select new{ MusteriId=m.Field<int>("MusteriId"), AdSoyad = m.field<string>("adsoyad") ; Bölüm 31

318 786 C# Programlama Dili Özet.NET Framework platformunda veri erişim katmanını kullanmak ve veritabanını nesnesel yöntemlerle sorgulamak için LINQ teknolojisi geliştirilmiştir. Veri erişim katmanında mevcut veritabanını temsil eden ana sınıf, System.Data.Linq.DataContext sınıfından türemiştir. DataContext sınıfı kullanılarak veritabanı oluşturma, düzenleme, silme ve sorgulama işlemleri kolaylıkla yapılabilir. LINQ (Language Integrated Query-Dil ile bütünleşik sorgulama),.net Framework ortamında farklı kaynaklardan alınan verilerin nesnesel ifadelerle sorgulanmasını sağlar. Bu kaynaklara bağlı olarak LINQ to Objects, LINQ to DataSet, LINQ to SQL ve LINQ to XML sağlayıcıları geliştirilmiştir. Temelde aynı LINQ operatörlerini kullanan bu sağlayıcılar ilişkili oldukları kaynakları SQL diline benzer ifadelerle sorgulanmasına yardımcı olurlar Sorular 31.1) LINQ teknolojisi nedir? Hangi amaçla kullanılır? 31.2) LINQ hangi türdeki nesneleri sorgulayabilir? 31.3) LINQ sorgularında Lambda ifadeleri nasıl kullanılır? 31.4) LINQ işlemlerinde table join işlemini bir örnekle açıklayınız? 31.5) DataTable nesnesini LINQ ile sorgulayan bir örnek hazırlayınız? Papatya Yayıncılık Eğitm

319 32. SQL ve XML için LINQ Kullanımı DLINQ kavramı, ilişkisel veritabanındaki öğeleri nesnesel ifadelerle sorgulamayı temsil eder. Bir uygulama içerisinde bu tür sorgulamaları yapabilmek için öncelikle veritabanı üyelerini o uygulama içerisinde temsil edecek varlıkların oluşturulması gerekir. O/R Mapping konusundan hatırlanacağı gibi uygulamada veri erişim katmanı (Data Access Layer-DAL) oluşturulur ve veritabanı öğelerini simgeleyecek varlık sınıfları (entity classes) bu katmanda yazılır. Uygulamadaki diğer katmanlar, veritabanına doğrudan erişmek yerine bu erişim katmanını kullanır. Her varlık sınıfı veritabanındaki bir tablo veya view nesnesine denk gelir. Uygulama, bu sınıfların içindeki yordam ve öznitelikler aracılığıyla veriye erişir, veriyi işler. Uygulama tarafındaki nesne modeli ile ilişkisel veritabanı tarafındaki model arasındaki temel üyelerin ilişkisi aşağıdaki tabloda özetlenmiştir. LINQ to SQL Object Model Entity class Class member Association Method Relational Data Model Table Column Foreign-key relationship Stored Procedure veya Function.NET Framework te varlık sınıflarını otomatik oluşturmak için iki araç kullanılır.

320 788 C# Programlama Dili Code Generation Tool (SqlMetal.exe) VS.NET içindeki Object Relational Designer aracı (O/R Designer) Bu araçlar: Veritabanından belirlediğimiz dile uygun sınıf dosyalarını üretebilir. Bu dosyalar, veri erişim katmanın kaynak kodlarını içerir. Veritabanından.dbml dosyasını oluşturabilir. DBML (database markup language) dosyası, veritabanının şeması referans alınarak oluşturulmuş XML tabanlı bir dosyadır. Bu dosyada veritabanındaki üyelerle ilgili bilgiler bulunur..dbml dosyasından kaynak kodu oluşturabilir. Veri erişim katmanı oluşturulurken bu üç yöntemden herhangi biri takip edilebilir. Bu bölümde SQL Server tarafındaki Kitap isimli veritabanını referans olarak alacağız. Bu veritabanının altında birbirleriyle ilişkili Marka, Kategori, Urun, Musteri ve Siparis tabloları bulunmaktadır. Aşağıdaki şekilde bu tablolarla ilgili diyagram gösterilmiştir SqlMetal Aracı İlişkisel veritabanını nesne dünyasına uyarlamak için kullanabileceğimiz basit bir araçtır. Komut satırında çalışan bu araç C:\Program Files\Microsoft SDKs\Windows\vn.nn\Bin klasöründe bulunur. SqlMetal komutu birçok parametre almaktadır. En basit haliyle bir sunucudaki veritabanına özel varlık sınıflarını oluşturmak için aşağıdaki gibi kullanılır. Papatya Yayıncılık Eğitm

321 SQL ve XML için LINQ Kullanımı 789 SqlMetal /server:localhost /database:kitap /code:c:\kitap.cs Bu komut çalıştığı zaman sunucudan Kitap isimli veritabanının şeması okunur ve ona göre Kitap.cs dosyası oluşturulur. Bu dosyada veritabanındaki tablo ve bunlar arasındaki ilişki bilgisi bulunur. Dosyayı çalıştığımız güncel projemize kopyalayıp Class Diagram aracıyla yapısını görelim. DLINQ işlemleriyle ilgili üyeler, System.Data.Linq kütüphanesinde bulunur. Bu yüzden oluşturduğumuz sınıf dosyasının doğru çalışabilmesi için projemize System.Data.Linq.dll kütüphanesini referans olarak eklemeliyiz. Bu kütüphanenin en önemli sınıfı, DLINQ mimarisinin başlangıç noktasını temsil eden DataContext sınıfıdır. Diyagramda da görüldüğü gibi DataContext türünde Kitap sınıfı oluşturulmuş ve veritabanındaki üyelerin bilgisi bu ana sınıfın birer özelliği olarak tanımlanmıştır. DLINQ mimarisinde veritabanındaki her üye için bir sınıf oluşturulur ve o üye ile ilgili yapılacak işlemler (execute, select, insert, delete, update) için o sınıfın içinde yordam ve özellikler oluşturulur. Bu sınıf ve üyeler, ilişkisel veritabanını nesnesel yöntemlerle sorgulamamızı sağlamaktadır. Bölüm 32

322 790 C# Programlama Dili Burada yaptığımız işlem veritabanından doğrudan kaynak kodları oluşturmak oldu. Bu yönteme alternatif olarak önce.dbml dosyasını üretip ardından bu dosyadan kaynak kodlar oluşturulabilir. Bunun için SqlMetal aracının Output parametresi kullanılır. /dbml [:file]: Çıktıyı.dbml olarak üretir. /map seçeneğiyle birlikte kullanılamaz. /code [:file]: Çıktıyı kaynak kod olarak üretir. /dbml seçeneğiyle birlikte kullanılamaz. /map [:file]: Çıktıyı XML kod-şablonu olarak üretir. /code seçeneğiyle birlikte kullanılması gerekir. Veritabanına bağlanıp veritabanının şemasından.dbml dosyasını oluşturalım. SqlMetal /server:localhost /database:kitap /dbml:c:\kitap.dbml Kitap.dbml dosyasında veritabanı ve ona ait üyelerin bilgisi bulunur. <?xml version="1.0" encoding="utf-8"?> <Database Name="Kitap" xmlns=" <Table Name="dbo.Kategori" Member="Kategori"> <Type Name="Kategori"> <!--Tablo Kolonları--> </Type> </Table> <Table Name="dbo.Marka" Member="Marka"> <Type Name="Marka"> <!--Tablo Kolonları--> </Type> </Table> <Table Name="dbo.Musteri" Member="Musteri"> <Type Name="Musteri"> <!--Tablo Kolonları--> </Type> </Table> <Table Name="dbo.Siparis" Member="Siparis"> <Type Name="Siparis"> <!--Tablo Kolonları--> </Type> </Table> <Table Name="dbo.Urun" Member="Urun"> <Type Name="Urun"> <!--Tablo Kolonları--> </Type> </Table> </Database> Kitap.dbml dosyasında Urun tablosu için oluşturulmuş olan element yapısı şu şekildedir. <Table Name="dbo.Urun" Member="Urun"> <Type Name="Urun"> <Column Name="UrunId" Type="System.Int32" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" /> Papatya Yayıncılık Eğitm

323 SQL ve XML için LINQ Kullanımı 791 <Column Name="UrunAd" Type="System.String" DbType="NChar(10)" CanBeNull="true" /> <Column Name="KategoriId" Type="System.Int32" DbType="Int" CanBeNull="true" /> <Column Name="MarkaId" Type="System.Int32" DbType="Int" CanBeNull="true" /> <Column Name="Adet" Type="System.Int32" DbType="Int" CanBeNull="true" /> <Association Name="FK_Siparis_Urun" Member="Siparis" OtherKey="UrunId" Type="Siparis" DeleteRule="NO ACTION" /> <Association Name="FK_Urun_Kategori" Member="Kategori" ThisKey="KategoriId" Type="Kategori" IsForeignKey="true" /> <Association Name="FK_Urun_Marka" Member="Marka" ThisKey="MarkaId" Type="Marka" IsForeignKey="true" /> </Type> </Table> Dosyada görüldüğü gibi tablo üyelerinin özellikleri ve SQL veri türlerin CLR tarafındaki hangi veri türleriyle eşleştirildiği bilgisi bulunmaktadır..dbml dosyasından kaynak kodu oluşturmak için aşağıdaki parametre yapısı kullanılır. sqlmetal /namespace:linqproje /code:kitap.cs /language:csharp Kitap.dbml Önceki sayfada diyagramını verdiğimiz veri erişim katmanında Kategori tablosu genel olarak aşağıdaki gibi ifade edilir. [Table(Name="dbo.Kategori")] public partial class Kategori:INotifyPropertyChanging, INotifyPropertyChanged { private int _KategoriId; private string _KategoriAd; private EntitySet<Urun> _Urun; public Kategori() { this._urun = new EntitySet<Urun>(new Action<Urun>(this.attach_Urun), new Action<Urun>(this.detach_Urun)); OnCreated(); [Column(Storage="_KategoriId", AutoSync=AutoSync.OnInsert, DbType="Int NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true)] public int KategoriId { get { return this._kategoriid; set { if ((this._kategoriid!= value)) { this.onkategoriidchanging(value); this.sendpropertychanging(); this._kategoriid = value; this.sendpropertychanged("kategoriid"); this.onkategoriidchanged(); Bölüm 32

324 792 C# Programlama Dili [Column(Storage="_KategoriAd", DbType="NChar(10)")] public string KategoriAd { get { return this._kategoriad; set { if ((this._kategoriad!= value)) { this.onkategoriadchanging(value); this.sendpropertychanging(); this._kategoriad = value; this.sendpropertychanged("kategoriad"); this.onkategoriadchanged(); [Association(Name="FK_Urun_Kategori", Storage="_Urun", OtherKey="KategoriId", DeleteRule="NO ACTION")] public EntitySet<Urun> Urun { get { return this._urun; set { this._urun.assign(value); SqlMetal aracı, varsayılan olarak sadece table türündeki öğelerin şemasını okur. Diğer öğelerin şemasını çıkarması için aşağıdaki parametreler kullanılır. /views : Veritabanındaki view öğelerinin şemasını çıkarır. /functions : Veritabanındaki fonksiyonların şemasını çıkarır. /sprocs : Veritabanındaki saklı yordamların şemasını çıkarır O/R Tasarım Aracı O/R Tasarım aracı, LINQ to SQL varlık sınıflarını grafiksel ortamda oluşturan VS.NET ile birlikte gelen basit bir araçtır. Bu aracı kullanarak, bir uygulamada veritabanındaki üyeleri temsil edecek bir nesne modelini bir iki adımda oluşturabiliriz. O/R Tasarım aracı şu anda sadece SQL Server 2000, SQL Server 2005 ve SQL Server Express veri tabanlarını desteklemektedir. O/R Designer aracını kullanarak öncelikle LINQ to SQL sınıfları ile veritabanı nesneleri arasındaki eşleştirmeyi yapan.dbml dosyası oluşturulur. O/R Tasarım aracını kullanmak için aktif projeye Add» New Item bölümünden bir LINQ to SQL Classes elemanı eklenir. Papatya Yayıncılık Eğitm

325 SQL ve XML için LINQ Kullanımı 793 Projemize LINQ to SQL Classes nesnesini eklediğimizde O/R Tasarım aracının kullanacağı kütüphaneler projeye referans olarak eklenir. Bu işlemden sonra O/R Tasarım aracının çalışma alanı açılmış olur. Bu alana ait Toolbox kullanılarak veya alanda ilgili yeri sağ tıklanarak varlıklar ve onlara bağlı yordam, özellik ve olay (event) gibi üyeler oluşturulur. Bölüm 32

326 794 C# Programlama Dili Sınıfları bu şekilde manual oluşturmak tasarım sürecinde ciddi zaman almaktadır. Varlık sınıflarını otomatik oluşturmak için Server Explorer» Database Explorer bölümünden ilgili veritabanına bağlanıp veritabanındaki üyeleri, O/R Tasarım çalışma alanına sürükleyip bırakmamız yeterli olacaktır. Table, view, stored procedure ve function gibi SQL Server tarafındaki üyeleri sürükle bırak yöntemiyle dbml alanına taşıdığımızda görsel olarak tabloları temsil e- den varlıkların ve aralarındaki ilişkilerin oluşturulduğuna şahit oluruz. Papatya Yayıncılık Eğitm

327 SQL ve XML için LINQ Kullanımı 795 Varlıklarla ilgili düzenleme yapmak için Properties bölümü kullanılabilir. O bölümde varlıkların sınıf adı ve veritabanı tarafında hangi tabloları temsil ettiği gibi bilgiler bulunur. O/R Tasarım aracının iki tasarım yüzeyi bulunur. Sol tarafta varlıklar, sağ tarafta varlıklara ait yordamlar listelenir. Bu işlemden sonra veri erişim katmanını temsil eden DataContext sınıfı, onun alt varlık sınıfları ve onlara ait üyeler oluşturulmuş olur. Bu yapılara ait kodlar Kitap.designer.cs dosyasında bulunmaktadır Veri Çekme Sorguları Veritabanındaki tablolar KitapDataContext sınıfının özellikleri olarak tanımlanmıştır. Özellikler tablo isimlerinin sonuna s iyelik eki eklenerek isimlendirilmiştir. public partial class KitapDataContext : System.Data.Linq.DataContext { //... public System.Data.Linq.Table<Kategori> Kategoris public System.Data.Linq.Table<Marka> Markas public System.Data.Linq.Table<Musteri> Musteris public System.Data.Linq.Table<Siparis> Siparis public System.Data.Linq.Table<Urun> Uruns //... Veritabanındaki tablolara ulaşmak için bu özellikler kullanılır. Şimdi basit bir örnekle bu üyeleri kullanalım. Aşağıdaki satırlarda Musteri tablosundaki kayıtlar listelenmiştir. KitapDataContext db = new KitapDataContext(); var Sonuc = from m in db.musteris select m; foreach (var s in Sonuc) Console.WriteLine(s.MusteriId +"» "+ s.adsoyad); 1» Ali Korkmaz 2» Ayşe Korkmaz 3» Mert Şensoy 4» Bu örneği çalıştırırken SQL Server tarafında Profiler aracını açıp sorgumuzun SQL Server tarafındaki yansımasını görebiliriz. Bölüm 32

328 796 C# Programlama Dili PÜF! DataContext sınıfının kullandığı connection string tanımı projenin Properties»Settings bölümünde saklanmaktadır. Bu bilgi, Kitap.designer.cs dosyasındaki DataContext yapılandırıcısı tarafından okunur. public KitapDataContext() : base(global::projeadi.properties.settings.default.kitapconnectionstring, mappingsource) { OnCreated(); DataContext sınıfının farklı bir Connection nesnesi kullanmasını sağlamak için bu sınıfın overload edilmiş olan yapıcı yordamına bağlantı cümlesi (connection string) ifadesi veya IDbConnection nesnesi parametre olarak gönderilir. O/R Tasarım aracı, veritabanı üzerinde farklı şekillerde veri çekmek için gerekli tüm sorgu alt yapısını oluşturur. Örneğin Urun tablosunda MarkaId alanı NULL o- lan kayıtları çekelim. Bu durumda herhangi bir dönüştürme işlemi yapmamıza gerek yok çünkü sınıf dosyasında Urun sınıfında MarkaId özelliği Nullable türünde tanımlanmıştır. private System.Nullable<int> _MarkaId; var Sonuc = from u in db.uruns where u.markaid==null select u; Veritabanı şemasından da görüleceği gibi Urun tablosu Kategori ve Marka tablolarıyla ilişkili durumdadır. LINQ to SQL düzeninde alt tablolar, ana tabloların özellikleri olarak tanımlanır bu yapılara varlık kümesi (entity set) denilir. Varlık kümesi, ER Model konusundan hatırlanacağı üzere aynı türden varlıkların oluşturduğu kümedir. Aynı şekilde alt tablodan ana tabloya erişmek için de ana tablo, ana tablonun bir özelliği olarak tanımlanır. Papatya Yayıncılık Eğitm

329 SQL ve XML için LINQ Kullanımı 797 Örneğin "Kategori1" altındaki ürünleri listelemek için aşağıdaki gibi nesnesel bir ifade kullanılır. var Sonuc = from u in db.uruns where u.kategori.kategoriad=="kategori1" select u; Bu ifade, SQL Server tarafında Left Join olarak çalıştırılır. SELECT t0.urunid, t0.urunad, t0.kategoriid, t0.markaid, t0.adet FROM Urun AS t0 LEFT OUTER JOIN Kategori AS t1 ON t1.kategoriid = t0.kategoriid WHERE t1.kategoriad = 'Kategori1' Daha önce örneklerini verdiğimiz LINQ standart operatörlerini kullanarak farklı türden veri çekme sorguları yazabiliriz. DLINQ sorguları, LINQ konusundaki örnek listeler üzerinde uyguladığımız sorgularla benzer olduğu için burada yeniden örneklendirmeyeceğiz. Bölüm 32

330 798 C# Programlama Dili Veri Güncelleme Sorguları DLINQ ifadelerini kullanarak veri güncelleme, silme işlemleri de yapılabilir. Bu işlemleri yapmak için Linq.Table sınıfının yordamlarından yararlanılır. Konunun başında ifade ettiğimiz gibi her tablo (entity), DataContext sınıfının birer özelliği olarak tanımlanır. Bu özellikler System.Data.Linq.Table türüne sahiptir. Veri tabanı tabloları üzerinde CRUD (create, read, update, delete) işlemleri yapmak için Table sınıfının şekilde görülen yordamları kullanılır. Örneğin veritabanındaki bir tabloya yeni kayıt eklemek için InsertOnSubmit(), toplu kayıt eklemek için InsertAllInSubmit(), tablodan kayıt silmek için DeleteOnSubmit() yordamları kullanılır. DataContext üzerinde yapılan değişiklikleri veritabanına kaydetmek için SubmitChanges() yordamı çağrılır. Urun tablosunda UrunId bilgisi 2 olan ürünün adetini 3 artıralım. KitapDataContext db = new KitapDataContext(); // Bir Urun değişkeni tanımlayalım. Urun ur = db.uruns.single(u => u.urunid == 2); // Bu ürünün özelliklerini ayarlayalım. ur.adet += 3; // Değişikliği kaydedip sunucuya gönderelim. db.submitchanges(); Papatya Yayıncılık Eğitm

331 SQL ve XML için LINQ Kullanımı 799 Bu işlem çalıştığı zaman SQL Server tarafından öncelikle KitapId alanı 2 olan ürün belleğe taşınır ve ardından istenilen alan üzerinde güncelleme yapılır. Nitekim SQL Profiler aracında aşağıdaki kod görülür. exec sp_executesql N'SELECT [t0].[urunid], [t0].[urunad], [t0].[kategoriid], [t0].[markaid], [t0].[adet] FROM [dbo].[urun] AS [t0] WHERE [t0].[urunid] int',@p0=2 GO exec sp_executesql N'UPDATE [dbo].[urun] SET [Adet] WHERE ([UrunId] AND ([UrunAd] AND ([KategoriId] AND ([MarkaId] AND ([Adet] int,@p1 nchar(10),@p2 int,@p3 int,@p4 int,@p5 int',@p0=2,@p1=n'urun2',@p2=1,@p3=2,@p4=5,@p5=8 Örneğimizi biraz daha geliştirelim. Yeni bir kategori oluşturalım ardından iki yeni ürünü oluşturup bu yeni kategoriyle ilişkilendirelim. Ardından bu üç kaydı da veritabanına kayıt edelim. KitapDataContext db = new KitapDataContext(); // Bir Kategori değişkeni tanımlayalım. Kategori kt = new Kategori(); kt.kategoriad = "Kategori3"; // İlk ürünü oluşturalım. Urun ur1 = new Urun(); ur1.urunad = "Urun5"; ur1.markaid = 2; ur1.adet = 7; // İkinci ürünü oluşturalım. Urun ur2 = new Urun{ UrunAd="Urun6", MarkaId=1, Adet=11 ; // Ürünleri yeni kategoriyle ilişkilendirelim. kt.uruns.add(ur1); kt.uruns.add(ur2); // Yeni kategoriyi Kategori tablosuna ekleyelim. db.kategoris.insertonsubmit(kt); // Değişikliği kaydedip sunucuya gönderelim. db.submitchanges(); Bu işlemin sonucunda SQL Server e aşağıdaki scriptler gönderilir. exec sp_executesql N'INSERT INTO [dbo].[kategori]([kategoriad]) VALUES (@p0) SELECT CONVERT(Int,SCOPE_IDENTITY()) AS [value]',n'@p0 nchar(10)',@p0=n'kategori3 ' exec sp_executesql N'INSERT INTO [dbo].[urun]([urunad], [KategoriId], [MarkaId], [Adet]) @p3) SELECT CONVERT(Int,SCOPE_IDENTITY()) AS [value]',n'@p0 nchar(10),@p1 int,@p2 int,@p3 int',@p0=n'urun5',@p1=3,@p2=2,@p3=7 Bölüm 32

332 800 C# Programlama Dili exec sp_executesql N'INSERT INTO [dbo].[urun]([urunad], [KategoriId], [MarkaId], @p3) SELECT CONVERT(Int,SCOPE_IDENTITY()) AS Bu örnekle birlikte LINQ teknolojisinin veritabanı işlemlerini ne kadar kolaylaştırdığını daha net görmüş oluyoruz. PÜF! LINQ sorgularında veri kaydı yapılacak tablonun primary key içermesi zorunludur. Aksi taktirde Can't perform Create, Update or Delete operations on 'Table(<Tablo Adı>)' because it has no primary key. hata mesajı alınır. En son oluşturduğumuz Kategori3 isimli kategori satırını silelim. KitapDataContext db = new KitapDataContext(); // Silme işlemini tek satırda yazalım. db.kategoris.deleteonsubmit( (from k in db.kategoris where k.kategoriad=="kategori3" select k).first() ); // Değişikliği kaydedip sunucuya gönderelim. db.submitchanges(); Bu ifadenin sonucunda üretilen SQL Script şu şekildedir. exec sp_executesql N'SELECT TOP (1) [t0].[kategoriid], [t0].[kategoriad] FROM [dbo].[kategori] AS [t0] WHERE [t0].[kategoriad] N'@p0 nvarchar(9)',@p0=n'kategori3' exec sp_executesql N'DELETE FROM [dbo].[kategori] WHERE ([KategoriId] AND ([KategoriAd] nchar(10)',@p0=3,@p1=n'kategori3 ' DLINQ de SQL İfadelerinin Çalıştırılması DLINQ işlemlerinde bazı durumlarda doğrudan SQL script yazarak sorgulama yapabiliriz. Bunun için DataContext sınıfının ExecuteQuery() ve Execute- Command() yordamları kullanılır. ExecuteQuery() yordamı, string türünde veritabanında çalıştırılacak SQL kodu parametre olarak alır. Generic yapıda olan bu yordam, veritabanından dönen sonucu daha önce belirlenmiş bir veri türünde tutar. ExecuteCommand() yordamı ise parametre olarak veritabanı üzerinde çalışacak komutu string türünde alır ve geriye komut sonucundan etkilen kayıtların sayısını döndürür. Papatya Yayıncılık Eğitm

333 SQL ve XML için LINQ Kullanımı 801 KitapDataContext db = new KitapDataContext(); var urunler = db.executequery<urun>("select * FROM Urun"); foreach (var u in urunler) Console.WriteLine(u.UrunId +"» "+ u.urunad); 1» Urun1 2» Urun2 3» Urun3 4» Urun4 Kategori tablosunda KategoriId kolonu 3 olan kaydı silelim. int sonuc= db.executecommand("delete FROM Kategori WHERE KategoriId=3"); Console.WriteLine("Kategori tablosundan {0 kayıt silindi.",sonuc); Kategori tablosundan 0 kayıt silindi. Konuyla ilgili diğer önemli yordam ise Translate() yordamıdır. Translate() yordamı, DbDataReader türünde parametre alır. Böylece bir DataReader nesnesini nesne koleksiyonuna dönüştürerek LINQ ifadeleriyle sorgulama imkanı elde etmiş oluruz. KitapDataContext db = new KitapDataContext(); //Context'in kullandığı bağlantıyı açalım. db.connection.open(); //Command nesnesini oluşturalım. SqlCommand ocmd = new SqlCommand("SELECT * FROM Musteri", db.connection as SqlConnection); //Context'in Translate yordamını kullanarak DataReader nesnesini nesne koleksiyonuna dönüştürelim. IEnumerable<Musteri> musteriler = db.translate<musteri>( ocmd.executereader(commandbehavior.closeconnection)); foreach (var m in musteriler) { Console.WriteLine(m.AdSoyad); Ali Korkmaz Ayşe Korkmaz Mert Şensoy Stored Procedure ve Function Kullanımı LINQ to SQL ortamında SQL Server tarafında oluşturduğumuz yordamları da kullanabiliriz. Daha önce VS.NET in Server Explorer bölümünden veritabanındaki table ve view öğelerini O/R Tasarım aracına sürüklediğimiz gibi saklı prosedüre veya function öğelerini de tasarım alanına sürüklediğimizde nesne modeli tarafında otomatik olarak ilgili yordamlar oluşturulacaktır. Bölüm 32

334 802 C# Programlama Dili SQL Server tarafında dışarıdan müşteri kodunu alıp o müşterinin sipariş verdiği ü- rünleri döndüren bir prosedür yazalım. CREATE PROCEDURE int AS SELECT UrunAd,SUM(S.Adet)ToplamAdet FROM Musteri M, Siparis S, Urun U WHERE M.MusteriId=@MusteriId AND M.MusteriId=S.MusteriId AND S.UrunId=U.UrunId GROUP BY UrunAd Server Explorer bölümünden GetMusteriUrunHit isimli yordamı tasarım alanına taşıdığımızda yordam panelinde yordamın yapısına uygun CLR tabanlı bir yordam oluşturulur. Bu yordamın, veri erişim katmanındaki kod yapısı aşağıdaki gibi oluşturulur. [Function(Name="dbo.GetMusteriUrunHit")] public ISingleResult<GetMusteriUrunHitResult> GetMusteriUrunHit( [Parameter(Name="MusteriId", DbType="Int")] System.Nullable<int> musteriid) { //... public partial class GetMusteriUrunHitResult { private string _UrunAd; private System.Nullable<int> _ToplamAdet; public GetMusteriUrunHitResult() { [Column(Storage = "_UrunAd", DbType = "NChar(10)")] public string UrunAd { get { set { [Column(Storage = "_ToplamAdet", DbType = "Int")] public System.Nullable<int> ToplamAdet { get { set { Papatya Yayıncılık Eğitm

335 SQL ve XML için LINQ Kullanımı 803 Ana program içerisinde bu yordamı kullanarak 1 nolu müşterinin sipariş ettiğin ü- rünleri sorgulayalım. KitapDataContext db = new KitapDataContext(); var urunler = db.getmusteriurunhit(1); foreach (var u in urunler){ Console.WriteLine(u.UrunAd +"» "+ u.toplamadet); Urun1» 2 Urun2» 3 Bu prosedür, dışarıdan bir parametre alıp o parametreye uygun kayıt dizisi döndürmektedir. Veri erişim katmanı tarafında dönen sonucun türüne uygun olarak GetMusteriUrunHitResult sınıfı oluşturulmuştur. Başka bir prosedür olarak dışarıdan kodunu aldığı ürüne ait stok adetini bir çıkış parametresiyle döndüren GetUrunStok yordamını yazalım. CREATE PROCEDURE int OUTPUT AS FROM Urun WHERE UrunId=@UrunId Aynı şekilde bu yordamı da O/R Tasarım alanına taşıdığımızda veri erişim katmanında yordamında aşağıdaki formatta GetUrunStok() fonksiyonu oluşturulur. [Function(Name="dbo.GetUrunStok")] public int GetUrunStok( [Parameter(Name="UrunId", DbType="Int")] System.Nullable<int> urunid, [Parameter(Name="StokAdet", DbType="Int")] ref System.Nullable<int> stokadet) { // İşlemler Görüldüğü gibi SQL Server tarafındaki çıkış parametresi nesne modeli tarafında ref nitelikli bir değişken olarak tanımlanmıştır. Bu yordamı aşağıdaki gibi kullanabiliriz. int? stokadet =0; db.geturunstok(1, ref stokadet); Console.WriteLine("1 nolu ürünün stok adet : {0",stokAdet); 1 nolu ürünün stok adet : 10 Bilindiği gibi SQL Server tarafında saklı prosedür içerisinden birden fazla kayıt dizisi döndürülebilir veya bir koşul aracılığıyla her defasında farklı türden bir sonuç döndürülebilir. Bu şekilde bir yordam yazalım. Bölüm 32

336 804 C# Programlama Dili CREATE PROCEDURE int) AS = 1) SELECT KategoriId, KategoriAd FROM Kategori ELSE = 2) SELECT MarkaId, MarkaAd FROM Marka Bu yordama parametre olarak 1 değeri gönderildiği Kategori tablosu, 2 değeri gönderildiği zaman Marka tablosu sonuç olarak döndürülür. Şu ana kadar kullandığımız yordamlar geriye tek bir kayıt dizisi döndürdüğü için veri erişim katmanında bu yordamları temsil eden yordamlar geriye ISingleResult arabirimi türünde değer döndürmekteydi. GetBilgi yordamı geriye birden fazla kayıt dizisi döndüreceği için bunu temsil edecek yordam geriye IMultipleResults arabirimi türünde değer döndürür. Ve yordamın muhtemel sonuç türleri ResultType niteliğiyle belirtilir. GetBilgi() yordamı basitçe aşağıdaki gibi temsil edilir. [Function(Name="dbo.GetBilgi")] [ResultType(typeof(Kategori))] [ResultType(typeof(Marka))] public IMultipleResults GetBilgi(int eylem) {... O/R Designer aracı şimdilik bu şekilde birden fazla kayıt dizisini döndüren yordamlar için doğru kodu oluşturamamaktadır. Yordamın sadece ilk döndürdüğü kaydı dikkate almaktadır. Bu yüzden Kitap.designer.cs dosyasına GetBilgi() yordamını sonradan bizim eklememiz gerekecek. Bu aşamada bu tür yordamları destekleyen SqlMetal aracı bize yardımcı olacaktır. SqlMetal aracının yazdığı belgede GetBilgi() yordamı için oluşturulmuş olan kodları Kitap.designer.cs dosyasına ekleyelim. Bu durumda GetBilgi() yordamı şu şekilde tanımlanmış olur. [Function(Name = "dbo.getbilgi")] [ResultType(typeof(GetBilgiResult1))] [ResultType(typeof(GetBilgiResult2))] public IMultipleResults GetBilgi( [Parameter(Name = "Eylem", DbType = "Int")] System.Nullable<int> eylem) { IExecuteResult result = this.executemethodcall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), eylem); return ((IMultipleResults)(result.ReturnValue)); Burada önemli olan bu yordamın GetBilgiResult1 ve GetBilgiResult2 türünde değer döndürebiliyor olmasıdır. GetBilgiResult1 ve GetBilgiResult2 türleri, SQL yordamındaki sonuçları temsilen oluşturulmuş olan 2 şer özellik üyesi bulunan sınıflardır. Kod yapısının nasıl olduğu tahmin edileceği için burada vermedik. Bu yordamı nasıl kullanacağımızı gösterelim. Papatya Yayıncılık Eğitm

337 SQL ve XML için LINQ Kullanımı 805 KitapDataContext db = new KitapDataContext(); IMultipleResults sonuc1 = db.getbilgi(1); var kategori = sonuc1.getresult<getbilgiresult1>(); Console.WriteLine("---Kategoriler---"); foreach (GetBilgiResult1 k in kategori) Console.WriteLine(k.KategoriAd); IMultipleResults sonuc2 = db.getbilgi(2); var marka= sonuc2.getresult<getbilgiresult2>(); Console.WriteLine("\n---Markalar---"); foreach (GetBilgiResult2 m in marka) Console.WriteLine(m.MarkaAd); ---Kategoriler--- Kategori1 Kategori2 ---Markalar--- Marka1 Marka2 SQL Server tarafında tanımlı fonksiyonları da aynı mantıkla kullanabiliriz. Fonksiyon geriye tek bir değer döndürüyorsa (scalar-value function) From dışındaki bölümlerde kullanılabilir. Kayıt dizisi döndüren bir fonksiyon ise (table-valued function) From bölümünde kullanılabilir. SQL Server tarafında girilen değerin harflerini büyük harf olarak yazdıran bir fonksiyon yazalım. CREATE FUNCTION BuyukHarfYap(@Deger varchar(50)) RETURNS varchar(50) AS BEGIN RETURN UPPER(@Deger) END Veri erişim katmanında BuyukHarfYap fonksiyonu aşağıdaki şekilde oluşturulur. [Function(Name="dbo.BuyukHarfYap", IsComposable=true)] public string BuyukHarfYap([Parameter(Name="Deger", DbType="VarChar(50)")] string deger) { return ((string)(this.executemethodcall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), deger).returnvalue)); Bu yordamı örnek içerisinde kullanalım. var sonuc = from m in db.musteris select db.buyukharfyap(m.adsoyad); foreach (var s in sonuc) Console.WriteLine(s); ALİ KORKMAZ AYŞE KORKMAZ MERT ŞENSOY Bölüm 32

338 806 C# Programlama Dili Bu işlemin SQL Server tarafındaki yansıması şu şekildedir. SELECT [dbo].[buyukharfyap]([t0].[adsoyad]) AS [value] FROM [dbo].[musteri] AS [t0] PÜF! Uygulama içerisinde yazdığımız "LINQ to SQL" ifadelerinin T-SQL tarafında nasıl şekillendiğini DataContext sınıfının Log özelliğiyle yakalayabiliriz. TextWriter değerinde olan bu özellik, SQL sorgusunu farklı çıktılara yazdırabilir. Örneğin aşağıdaki satır, çıktıyı ekrana yazdırır. KitapDataContext db = new KitapDataContext(); db.log = Console.Out; Aynı şekilde oluşan çıktıyı fiziksel bir dosyaya da yazdırabiliriz. db.log = new StreamWriter(@"C:\log.txt"); Aynı bilgisi VS.NET Debugger aracıyla da öğrenebiliriz. Örneğin önceki örneği debug ettiğimizde sonuc değişkeninin değerini aşağıdaki gibi görürüz. Geriye tablo türünde kayıt döndüren fonksiyonların kullanımını örneklendirelim. Aşağıdaki FnMusteriSiparis() fonksiyonu parametre olarak aldığı müşteriye ati siparişleri listelemektedir. CREATE FUNCTION FnMusteriSiparis(@MusteriId int) RETURNS TABLE AS RETURN (SELECT * FROM Siparis WHERE MusteriId=@MusteriId) VS.NET Server Explorer bölümünden fonksiyonu O/R Designer aracılığıyla veri erişim katmanına taşıdığımızda integer türünde MusteriId giriş parametresini alan ve geriye IQueryable<FnMusteriSiparisResult> türünde değer döndüren FnMusteriSiparis() yordamı yazılır. FnMusteriSiparisResult sınıfı, Siparis tablosunun şemasına uygun üyelere sahiptir. Veri erişim kodlarını incelediğimizde SQL Server tarafındaki yordamlardan yalnızca bu fonksiyonun IQueryable türünde parametre döndürdüğünü görürüz. Bu da bu fonksiyonu sorgulama kaynağı olarak kullanabileceğimiz anlamına gelmektedir. Müşteri kodu 1 olan müşterinin 2008 den önce verdiği siparişleri sorgulayalım. var sonuc = from s in db.fnmusterisiparis(1) where s.tarih < Convert.ToDateTime(" ") select s; Papatya Yayıncılık Eğitm

339 SQL ve XML için LINQ Kullanımı 807 Console.WriteLine("--Müşterinin sipariş verdiği tarihler--"); foreach (var s in sonuc) Console.WriteLine(s.Tarih.Value.ToShortDateString()); --Müşterinin sipariş verdiği tarihler DLINQ de Transaction Desteği Varsayılan olarak DataContext.SubmitChanges() yordamı herhangi bir transaction bloğu belirtilmemiş olsa bile sorguyu bir transaction yönetiminde gerçekleştirir. Bununla birlikte LINQ to SQL işleminde transaction yönetimini programcı kendisi de yönlendirebilir. Birden fazla SubmitChanges() yordamı çalıştırdığı zaman veya farklı veri tabanlarında işlem yaptığı zaman sorgularda nasıl bir transaction yönetiminin yapılacağı programcı tarafından belirtilebilir. Öncelikle SQL Server tarafında Urun tablosu üzerinde bir kısıtlayıcı tanımlayalım. USE [Kitap] GO ALTER TABLE [dbo].[urun] WITH CHECK ADD CONSTRAINT [CK_Urun] CHECK (([Adet]>(0))) GO ALTER TABLE [dbo].[urun] CHECK CONSTRAINT [CK_Urun] Bu constraint Urun tablosundaki Adet kolonundaki değerin sıfırdan büyük olmasını garantilemektedir. Yani ürünlerin stok değeri eksi olmamalı. Uygulama içerisinde birden fazla ürünün stok değerini güncelleyelim. İşlemde hataya neden olmak için ürünlerden birinin stok değerini eksi yapmaya çalışalım. //2 nolu ürünün stoğunu değiştirelim db.uruns.single(u => u.urunid == 2).Adet=1; //3 nolu ürünün stoğunu değiştirelim db.uruns.single(u => u.urunid == 3).Adet = -2; //Güncellemeleri veritabanına gönderelim db.submitchanges(); Bu güncelleme satırlarını çalıştırdığımızda aşağıdaki hata mesajıyla karşılaşırız. Bu işlemde, ilk ürüne ait güncellemenin yapılıp yapılamadığını ve transaction işleminin nasıl gerçekleştiğini görmek için SQL Profiler aracında transaction olaylarını izleyelim. Bölüm 32

340 808 C# Programlama Dili Görüldüğü gibi her iki güncelleme ifadesi aynı transaction içerisinde çalıştırılmış ve ikinci ifade başarısız olduğu için her iki ifadenin sonucu geri alınmıştır. Örnekteki iki güncelleme ifadesini veritabanına ayrı ayrı gönderelim. // 2 nolu ürünün stoğunu değiştirelim. db.uruns.single(u => u.urunid == 2).Adet=1; // Bu değişikliği veritabanına gönderelim. db.submitchanges(); // 3 nolu ürünün stoğunu değiştirelim db.uruns.single(u => u.urunid == 3).Adet = -2; // Bu değişikliği veritabanına gönderelim. db.submitchanges(); Uygulama yine hata verecektir. Fakat bu durumda her güncelleme emri birbirinden bağımsız transaction alanları kullandıkları için ilk güncelleme başarılı olacaktır. SQL Profiler aracından bu durumu daha net görebiliriz. Görüldüğü gibi her SubmitChanges() yordamı çağrımı için yeni bir transaction bloğu açılmaktadır. Bu durum, her zaman istediğimiz bir yöntem olmayabilir. O anda yapılan değişikliklerden birinde hata meydana geldiyse önceki işlemlerin geri alınmasını isteyebiliriz. Bunun için TransactionScope nesnesini kullanarak tüm sorgu işlemlerini aynı transaction bloğunda gerçekleştirmeliyiz. Projeye System Papatya Yayıncılık Eğitm

341 SQL ve XML için LINQ Kullanımı 809.Transaction.dll kütüphanesini referans olarak ekleyip örneğimizi aşağıdaki gibi değiştirelim. using (TransactionScope tscope = new TransactionScope()) { //2 nolu ürünün stoğunu değiştirelim db.uruns.single(u => u.urunid == 2).Adet = 1; //Bu değişikliği veritabanına gönderelim db.submitchanges(); //3 nolu ürünün stoğunu değiştirelim db.uruns.single(u => u.urunid == 3).Adet = -2; //Bu değişikliği veritabanına gönderelim db.submitchanges(); tscope.complete(); Bu durumda başından sonuna kadar yapılan tüm değişiklikler SQL Server tarafında geçmişi hepsi başarıyla sonuçlandıktan sonra transaction onaylanır. Stok değerini negatif yaptığımız satırı pozitif yaparsak sorgunun SQL Server tarafından yansıması şu şekilde olacaktır. TransactionScope sınıfına alternatif olarak yerel transaction kullanılarak ta aynı yönetim sağlanabilir. KitapDataContext db = new KitapDataContext(); db.connection.open(); db.transaction = db.connection.begintransaction(); try { // 2 nolu ürünün stoğunu değiştirelim. db.uruns.single(u => u.urunid == 2).Adet = 1; // Bu değişikliği veritabanına gönderelim. db.submitchanges(); // 3 nolu ürünün stoğunu değiştirelim. db.uruns.single(u => u.urunid == 3).Adet = -2; Bölüm 32

342 810 C# Programlama Dili // Bu değişikliği veritabanına gönderelim. db.submitchanges(); db.transaction.commit(); catch(exception ex) { db.transaction.rollback(); Console.WriteLine("Hata Oluştu : {0",ex.Message); finally { db.transaction = null; if (db.connection.state == ConnectionState.Open) db.connection.close(); Hata Oluştu : The UPDATE statement conflicted with the CHECK constraint "CK_Urun". The conflict occurred in database "Kitap", table "dbo.urun", column 'Adet'. The statement has been terminated. Transaction yönetimini başlatmak için veritabanı bağlantısının açık olması gerekir. Bu yüzden öncelikle bağlantı açıldı ve ardından DataContext nesnesine ait bağlantı nesnesi üzerinden transaction işlemi başlatıldı. Bütün değişimler başarılı olduktan sonra onaylama işlemi yapıldı herhangi bir hatayla karşılaşması durumunda aktif transaction geri alındı. Bu örnek aynı zamanda LINQ to SQL teknolojisinin ADO.NET in önceki özelliklerini de desteklediğini göstermektedir. Hatta ADO.NET klasik DbCommand nesnesiyle ve LINQ ile yapılan güncellemeleri aynı transaction alanında gerçekleştirebiliriz. Aşağıdaki örneği inceleyelim. // Klasik bir ADO.NET connection nesnesi oluşturalım. // Connection string olarak projenin Settings bölümündeki değer alınır. SqlConnection ocnn = new SqlConnection(global::CSharpConsoleApplication.Properties.Settings. Default.KitapConnectionString); ocnn.open(); // Aynı bağlantıyı kullanacak DataContext nesnesi tanımlayalım. KitapDataContext db = new KitapDataContext(oCnn); // Transaction nesnesi. SqlTransaction otrs = ocnn.begintransaction(); try { // 1 nolu ürünün stoğunu güncelleyecek SqlCommand nesnesi. SqlCommand ocmd = new SqlCommand("UPDATE Urun SET Adet=7 WHERE UrunId=1"); ocmd.connection = ocnn; ocmd.transaction = otrs; ocmd.executenonquery(); db.transaction = otrs; // 2 ve 3 nolu ürünlerin stoğunu LINQ to SQL ile değiştirelim. db.uruns.single(u => u.urunid == 2).Adet = 1; db.uruns.single(u => u.urunid == 3).Adet = -2; db.submitchanges(); otrs.commit(); Papatya Yayıncılık Eğitm

343 SQL ve XML için LINQ Kullanımı 811 catch(exception ex) { // Güncelleme esnasında herhangi bir hata olduğu zaman transaction rollback edilir. Console.WriteLine("Hata Oluştu : {0",ex.Message); finally { if (ocnn.state == ConnectionState.Open) ocnn.close(); Bu işlem sonucu hem DbCommand nesnesinin sorgusu hem de Linq To SQL sorgusu aynı transaction alanını kullanmış olur. Burada dikkat edilmesi gereken konu, hem ADO.NET klasik nesnelerinin hem de DataContext nesnesinin aynı bağlantıyı kullanmasıdır DataContext Sınıfının Veritabanı Yordamları DataContext sınıfının veritabanı seviyesinde işlem yapmak için aşağıdaki yordamları sunar: CreateDatabase() DeleteDatabase() DatabaseExists() SubmitChanges() : Sunucu üzerinde bir veritabanı oluşturur. : Sunucu üzerindeki veritabanını siler. : Sunucu üzerinde parametre olarak veritabanının olup olmadığını bildirir. : Bellek üzerinde yapılan değişiklikleri veritabanına gönderir. Örnek olarak SQL Server sunucusu üzerinde ETicaret isimli bir veritabanı oluşturalım. Ardından bu veritabanı üzerinde Urun isimli bir tablo oluşturup tabloya 2 örnek kayıt ekleyelim. Bunun için öncelikle veri erişim katmanı tarafında oluşturulacak veritabanı ve tablonun şemasını oluşturalım. using System.Data; using System.Data.Linq; using System.Data.Linq.Mapping; Bölüm 32

344 812 C# Programlama Dili // tburun tablo şeması. [Table(Name = "tburun")] public class Urun { [Column(DbType = "Int", IsPrimaryKey = true)] public int UrunId; [Column(DbType = "NChar(10)")] public string UrunAd; // ETicaret veritabanı şeması. public class ETicaret : DataContext { public Table<Urun> Uruns; public ETicaret(IDbConnection connection) : base(connection) { Uygulama içerisinde bu şema yapısına uygun veritabanını oluşturalım. // Sunucuyla bağlantı kuracak DbConnection nesnesi. SqlConnection ocnn = new SqlConnection("Server=.;Uid=sa;Pwd=123;"); ocnn.open(); ETicaret db = new ETicaret(oCnn); // Eğer sunucu üzerinde bu ETicaret veritabanı yoksa oluştur. if (db.databaseexists()) Console.WriteLine("Sunucu üzerinde ETicaret veritabanı mevcut."); else { Console.WriteLine("Sunucu üzerinde ETicaret veritabanı mevcut değil."); Console.WriteLine("Eticaret veritabanı, sunucu üzerinde oluşturuldu."); db.createdatabase(); // tburun tablosuna yeni kayıt ekleyelim. Urun our = new Urun {UrunId=1,UrunAd="Yeni Ürün1" ; db.uruns.insertonsubmit(our); our = new Urun { UrunId = 2, UrunAd = "Yeni Ürün2" ; db.uruns.insertonsubmit(our); // Yaptığımız değişikliği veritabanına gönderelim. db.submitchanges(); // tburun tablosundaki kayıtları listeleyelim. var sorgu = from u in db.uruns select u; foreach (var u in sorgu) Console.WriteLine(u.UrunId +"» "+ u.urunad); // Oluşturduğumuz veritabanını silelim. db.deletedatabase(); Console.WriteLine("Eticaret veritabanı, sunucudan silindi."); Sunucu üzerinde ETicaret veritabanı mevcut değil. Eticaret veritabanı, sunucu üzerinde oluşturuldu. 1» Yeni Ürün1 2» Yeni Ürün2 Eticaret veritabanı, sunucudan silindi. Papatya Yayıncılık Eğitm

345 SQL ve XML için LINQ Kullanımı 813 Bu işlemlerin SQL Server tarafındaki sorgu ifadesi aşağıdaki gibi oluşur. use [ETicaret] go CREATE DATABASE [ETicaret] go use [ETicaret] go SET ARITHABORT ON CREATE TABLE [tburun]( [UrunId] Int, [UrunAd] NChar(10), CONSTRAINT [PK_tbUrun] PRIMARY KEY ([UrunId]) ) go SELECT [t0].[urunid], [t0].[urunad] FROM [tburun] AS [t0] go use [MASTER] go DROP DATABASE [ETicaret] go Bu bölümde SQL Server üzerinde oluşacak veritabanı ve onun altındaki tablo şemasını kodlayarak oluşturduk. Bu tür durumlarda O/R Tasarım aracını kullanmak hem kolaylık sağlayacak hem de zaman kazandıracaktır. Böyle bir örnek yapmak için VS.NET te yeni bir proje oluşturup projeye LINQ to SQL classes elemanını ekleyelim. Bu işlemden sonra O/R Tasarım aracının çalışma alanı açılır. Bu alana ait Properties bölümünden varlık sınıflarının bağlı olacağı veritabanı bağlantısı, isim-uzayı gibi bilgiler düzenlenebilir. Bölüm 32

346 814 C# Programlama Dili O/R Tasarım çalışma alanında varlık sınıflarını oluşturmak için Toolbox bölümünde Class isimli kontrol tasarım alanına sürükleyip bırakılır. Aynı işlem alanda sağ tıklayıp Add» Class bölümünden de yapılabilir. Ekrana bir Class kontrolünü taşıyıp kontrolün adını Urun olarak değiştirelim. Bu sınıfın veritabanında hangi tabloyu temsil edeceği Properties bölümündeki Source alanına girilir. Source alanını tburun olarak yerleştirelim. Ardından sınıfı sağ tıklanarak Add» Property bölümünden bu sınıfa ait özellikler girilir. Bu özellikler veritabanındaki kolonları temsil edecek şekilde tasarlanır. Sınıf üzerinde oluşturulan özelliklerle ilgili tüm ayrıntılar Properties bölümünden düzenlenir. Papatya Yayıncılık Eğitm

347 SQL ve XML için LINQ Kullanımı 815 Bu şekilde varlıkları ve onlara ait özellikleri oluşturduktan sonra birbirleriyle ilişkili olacak varlıklar arasındaki ilişkiyi de aynı ekranda düzenleyebiliriz. Bu grafiksel işlemlerden sonra şemaya uygun kodlar arka tarafta otomatik olarak oluşmuş olur. Bu bölümde yaptığımız gibi DataContext sınıfından bir nesne oluşturup veritabanıyla ilgili yordamları çalıştırabiliriz. Sonuç olarak "LINQ to SQL" mimarisinde ilişkisel veritabanı üyeleri istemci uygulama tarafında varlık denilen nesneler aracılığıyla temsil edilir ve LINQ standart operatörleri aracılığıyla bu üyelerin nesnesel yöntemlerle sorgulanması sağlanır XML için LINQ (XLINQ) ADO.NET bölümünde ele aldığımız XML konusunda dile getirdiğimiz gibi veri depolama ve dağıtımı konusunda kolaylık sağlayan XML belgelerinin en büyük sıkıntısı sorgulama teknolojilerinin yeterince kullanışlı veya güçlü olmamasıdır. Bilindiği gibi XML belgelerini sorgulamak için DOM arabirimi, XQuery veya XSLT gibi XML belgelerine özgü teknolojiler kullanılır. Fakat bu yöntemler günümüz ya- Bölüm 32

348 816 C# Programlama Dili zılım geliştirme ölçülerine göre teknolojik olarak geride kalmış olmaları veya uzmanlık gerektirmeleri sebebiyle programcılar tarafından şikayet konusu olmuştur. XML belgelerini ilişkisel veri tabanlarını sorguladığımız kolaylıkta sorgulamak için LINQ teknolojisinin en önemli bacağı olan LINQ to XML konsepti geliştirildi. LINQ to SQL yaklaşımında kaynak olarak ilişkisel veri tabanlarını aldığımız gibi LINQ to XML yaklaşımında da XML belgeleri kaynak olarak alınır. Kısaca XLINQ olarak temsil edilen LINQ to XML için gerekli üyeler, System.Xml.Linq kütüphanesinde yer alır. LINQ to SQL yaklaşımında nasıl ki tablo, fonksiyon, saklı yordam üyelerinin uygulama tarafında bir karşılığı varsa LINQ to XML yaklaşımında da XML belgesindeki öğeleri temsil eden XLinq kütüphanesine ait sınıflar mevcuttur. Bu sınıflar, X önekiyle başlamaktadır. Böylece bu sınıflar, System.Xml altındaki sınıflardan kolayca ayırt edebilmekteyiz. XDocument: Bellek üzerinde yaşayan XML belgesini temsil eder. Bu sınıf aracılığıyla XML belgesini ağaç yapısında oluşturabilir, düzenleyebilir ve diske yazdırabiliriz. Xelement: XML belgesindeki elementlerin Framework tarafındaki türüdür. XAttribute: XML belgesindeki niteliklerin Framework tarafındaki türüdür. XComment: XML belgesindeki açıklama satırlarını temsil eder. XNode: XML belgesindeki her türlü düğümü ifade edebilmek için kullanılır. XProcessingInstruction : XML belgesinin başında bulunan belge tanımlayıcı satırı temsil eder. Bu satırı aynı zaman XDecleration türüyle de ifade edebiliriz. Papatya Yayıncılık Eğitm

349 SQL ve XML için LINQ Kullanımı 817 XText: Belgedeki CData türündeki içeriğin Framework tarafında temsil edildiği veri türüdür. XNamespace: Belgedeki ad alanlarının ifade edildiği veri türüdür. XDocumentType : XML belgesinin ilişkili olduğu DTD yapısını temsil eder. <?xml version="1.0" encoding="utf-8"?> <Musteriler> <Musteri MusteriId="1"> <AdSoyad>Zeynep Özilhan</AdSoyad> <Adres> <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> <Sehir>Ankara</Sehir> </Adres> </Musteri> <Musteri MusteriId="2"> <AdSoyad>Cem Can</AdSoyad> <Adres> <CaddeSokak>Büyükdere Cad.</CaddeSokak> <Sehir>İstanbul</Sehir> </Adres> </Musteri> </Musteriler> Tablodaki XML belgesini XDocument sınıfını kullanarak oluşturalım. XDocument xmusteri = new XDocument( new XDeclaration("1.0", "utf-8", "yes"), new XElement("Musteriler", new XElement("Musteri", new XAttribute("MusteriId", "1"), new XElement("AdSoyad", "Zeynep Özilhan"), new XElement("Adres", new XElement("CaddeSokak", "Umut Cad. Bahar Sk."), new XElement("Sehir", "Ankara") )//Adres ),//Musteri new XElement("Musteri", new XAttribute("MusteriId", "2"), new XElement("AdSoyad", "Cem Can"), new XElement("Adres", new XElement("CaddeSokak", "Büyükdere Cad."), new XElement("Sehir", "İstanbul") )//Adres )//Musteri )//Musteriler ); Console.WriteLine(xMusteri); Örneklerde oluşturduğumuz içerik bellek üzerinde tutulmaktadır. Bu içeriği fiziksel bir kaynağa kayıt etmek için Save() yordamı kullanılır. xmusteri.save(@"c:\musteri.xml"); Bölüm 32

350 818 C# Programlama Dili Aynı şekilde fiziksel kaynaktaki belgeyi belleğe taşımak için de Load() yordamı kullanılır. Load() yordamı Save() yordamı gibi overload edilmiş olup farklı türden parametre alabilir. Musteri.xml dosyasını belleğe yükleyip belgeye yeni bir düğüm ekleyelim. XDocument xdmusteri = XDocument.Load(@"C:\Musteri.xml"); // Yeni bir Müşteri düğümü oluşturalım. XElement xemusteri = new XElement("Musteri", new XAttribute("MusteriId", "3"), new XElement("AdSoyad", "Ayşe Coşkun"), new XElement("Adres", new XElement("CaddeSokak", "Maslak Cad."), new XElement("Sehir", "İstanbul") )//Adres );//Musteri // xemusteri düğümümü kök düğüme ekleyelim. xdmusteri.element("musteriler").add(xemusteri); Console.WriteLine(xdMusteri); <Musteriler> <Musteri MusteriId="1"> <AdSoyad>Zeynep Özilhan</AdSoyad> <Adres> <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> <Sehir>Ankara</Sehir> </Adres> </Musteri> <Musteri MusteriId="2"> <AdSoyad>Cem Can</AdSoyad> <Adres> <CaddeSokak>Büyükdere Cad.</CaddeSokak> <Sehir>İstanbul</Sehir> </Adres> </Musteri> <Musteri MusteriId="3"> <AdSoyad>Ayşe Coşkun</AdSoyad> <Adres> <CaddeSokak>Maslak Cad.</CaddeSokak> <Sehir>İstanbul</Sehir> </Adres> </Musteri> </Musteriler> XML belgesini bu şekilde tanımlamak yerine herhangi bir koleksiyondan dinamik olarak oluşturabiliriz. Önceki örneklerimizde kullandığımız MusteriList isimli listeyi kullanalım. static List<Musteri> MusteriList; static List<Siparis> SiparisList; public static void Main(string[] args) { // Müşteri listesini yükleyelim. MusteriListeOlustur(); // Kök elementi tanımlayalım. XElement xemusteri = new XElement("Musteriler"); Papatya Yayıncılık Eğitm

351 SQL ve XML için LINQ Kullanımı 819 foreach (Musteri m in MusteriList) { xemusteri.add(new XElement("Musteri", new XAttribute("MusteriId", m.musteriid), new XElement("AdSoyad", m.adsoyad))); Console.WriteLine(xeMusteri); <Musteriler> <Musteri MusteriId="1"> <AdSoyad>Ali Korkmaz</AdSoyad> </Musteri> <Musteri MusteriId="2"> <AdSoyad>Ayşe Korkmaz</AdSoyad> </Musteri> </Musteriler> LINQ to XML teknolojisi, XML içeriği aşağıdaki gibi LINQ sorgulama yöntemiyle de oluşturmamıza olanak sağlar. // Müşteri listesini yükleyelim. MusteriListeOlustur(); XElement xemusteri = new XElement("Musteriler"); xemusteri.add(from m in MusteriList select new XElement("Musteri", new XAttribute("MusteriId",m.MusteriId), new XElement("AdSoyad",m.AdSoyad))); Bu örneklerde XML içeriği hafıza üzerinde yaşayan IEnumerable türündeki listeden oluşturduk. Aynı mantıkla veri erişim katmanında tanımlı olan varlıklar üzerinden SQL Server sunucusunda yaşayan bir kaynaktan da XML içeriği oluşturabiliriz XML Belgesini XLINQ ile Sorgulamak XLINQ teknolojisinin bu küçük düzenlemelere imkan sağlamasıyla birlikte asıl a- macı XML içerik veya belgelerini nesnesel yöntemlerle sorgulayabiliyor olmamızdır. Herhangi bir XML içeriği LINQ standart operatörleri kullanarak kolaylıkla sorgulama yapabiliriz. Bu bölümün başında verdiğimiz Musteri.xml dosyası üzerinde çeşitli sorgulamalar yapalım. Örneğin adı n ile biten kişileri sorgulayalım. // Musteri.xml dosyasını olduğu gibi hafızaya taşıyalım. XDocument xdmusteri = XDocument.Load("C:\\Musteri.xml"); XElement xemusteri = new XElement("MusteriListe", from m in xdmusteri.elements("musteriler").elements("musteri") where m.element("adsoyad").value.endswith("n") select m.element("adsoyad")); Console.WriteLine(xeMusteri); <MusteriListe> <AdSoyad>Zeynep Özilhan</AdSoyad> <AdSoyad>Cem Can</AdSoyad> <AdSoyad>Ayşe Coşkun</AdSoyad> </MusteriListe> Bölüm 32

352 820 C# Programlama Dili XDocument sınıfının Load() yordamını kullanarak fiziksel kaynaktaki XML belgesini hafızaya taşıdık. Bir elemente ait tüm alt elementleri elde etmek için Elements özelliği kullanılır. IEnumerable türünde değer döndüren bu özellik, System.Xml.Linq.XNode sınıfının bir üyesidir. Konunun başında verdiğimiz XLinq sınıfına ait sınıf diyagramında görüldüğü gibi XAttribute dışındaki birçok sınıf XNode sınıfından türemiştir. Elementler üzerinde kullandığımız temel yordam ve özellikleri XNode sınıfından miras alınır. Bu basit örnekle görüleceği gibi XML belgelerini LINQ yöntemiyle sorgulamak XPath ve XQuery gibi teknikleri kullanmaktan daha esnek ve güçlü bir yapı sunmaktadır. XML içerikten sorguladığımız veriyi daha önce oluşturduğumuz MusteriList koleksiyonuna aktaralım. Papatya Yayıncılık Eğitm

353 SQL ve XML için LINQ Kullanımı 821 XDocument xdmusteri = XDocument.Load("C:\\Musteri.xml"); // MusteriList nesnesini oluşturalım. MusteriList = new List<Musteri>(); MusteriList = ( from e in xdmusteri.elements("musteriler").elements("musteri") select new Musteri { MusteriId = (int)e.attribute("musteriid"), AdSoyad = (string)e.element("adsoyad") ).ToList<Musteri>(); foreach (Musteri m in MusteriList){ Console.WriteLine("{0» {1", m.musteriid, m.adsoyad); 1» Zeynep Özilhan 2» Cem Can 3» Ayşe Coşkun XLINQ ile ilgili örneklerimize devam edelim. İstanbul da oturan müşterilerin adreslerini listeleyelim. XElement xemusteri = new XElement("MusteriListe", from m in xdmusteri.elements("musteriler").elements("musteri") where m.element("adres").element("sehir").value=="istanbul" select m.element("adres")); Console.WriteLine(xeMusteri); <MusteriListe> <Adres> <CaddeSokak>Büyükdere Cad.</CaddeSokak> <Sehir>İstanbul</Sehir> </Adres> <Adres> <CaddeSokak>Maslak Cad.</CaddeSokak> <Sehir>İstanbul</Sehir> </Adres> </MusteriListe> Müşteri kodu 2 olan müşterinin adres bilgilerini sorgulayalım. IEnumerable<XElement> xdmusteri; xdmusteri= XDocument.Load("C:\\Musteri.xml").Elements("Musteriler"); var sorgu = from m in xdmusteri.elements("musteri") where m.attribute("musteriid").value =="2" select m.element("adres"); foreach(var sonuc in sorgu) Console.WriteLine(sonuc); <Adres> <CaddeSokak>Büyükdere Cad.</CaddeSokak> <Sehir>İstanbul</Sehir> </Adres> Bölüm 32

354 822 C# Programlama Dili Alt veya Üst Düğümleri Seçme XML belgesinde konumlanmış olan düğümün alt veya üst düğümlerini veya elementleri seçmek için çeşitli yordamlar kullanılır. Bu yordamların işleyiş farkını basit bir örnekle göstereceğiz. Müşteri numarası 1 olan Musteri elementine konumlanıp her yordamın nasıl bir sonuç oluşturduğunu görelim. IEnumerable<XElement> xdmusteri; xdmusteri = XDocument.Load("C:\\Musteri.xml").Elements("Musteriler"); var kaynak = xdmusteri.elements("musteri").where(m => m.attribute("musteriid").value == "1"); Ancestors(): Mevcut düğümün atası dahil olmak üzere herhangi bir seviye üstündeki düğümü döndürür. var sorgu = kaynak.ancestors(); foreach (var sonuc in sorgu) Console.WriteLine(sonuc); <Musteriler> <Musteri MusteriId="1"> <AdSoyad>Zeynep Özilhan</AdSoyad> <Adres> <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> <Sehir>Ankara</Sehir> </Adres> </Musteri> <Musteri MusteriId="2"> <AdSoyad>Cem Can</AdSoyad> <Adres> <CaddeSokak>Büyükdere Cad.</CaddeSokak> <Sehir>İstanbul</Sehir> </Adres> </Musteri> <Musteri MusteriId="3"> <AdSoyad>Ayşe Coşkun</AdSoyad> <Adres> <CaddeSokak>Maslak Cad.</CaddeSokak> <Sehir>İstanbul</Sehir> </Adres> </Musteri> </Musteriler> AncestorsAndSelf(): Mevcut düğümün üst seviye üstündeki düğümleriyle birlikte kendisini döndürür. <Musteri MusteriId="1"> <AdSoyad>Zeynep Özilhan</AdSoyad> <Adres> <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> <Sehir>Ankara</Sehir> </Adres> </Musteri> Papatya Yayıncılık Eğitm

355 SQL ve XML için LINQ Kullanımı 823 <Musteriler> <Musteri MusteriId="1"> <AdSoyad>Zeynep Özilhan</AdSoyad> <Adres> <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> <Sehir>Ankara</Sehir> </Adres> </Musteri> <Musteri MusteriId="2"> <AdSoyad>Cem Can</AdSoyad> <Adres> <CaddeSokak>Büyükdere Cad.</CaddeSokak> <Sehir>İstanbul</Sehir> </Adres> </Musteri> <Musteri MusteriId="3"> <AdSoyad>Ayşe Coşkun</AdSoyad> <Adres> <CaddeSokak>Maslak Cad.</CaddeSokak> <Sehir>İstanbul</Sehir> </Adres> </Musteri> </Musteriler> Attributes(): Mevcut düğümün özniteliklerini listeler. MusteriId="1" DescendantNodes(): Mevcut düğümün en alt seviyeye kadar herhangi türdeki tüm alt düğümlerini ayrı ayrı listeler. <AdSoyad>Zeynep Özilhan</AdSoyad> Zeynep Özilhan <Adres> <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> <Sehir>Ankara</Sehir> </Adres> <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> Umut Cad. Bahar Sk. <Sehir>Ankara</Sehir> Ankara DescendantNodesAndSelf(): DescendantNodes() yordamının döndürdüğü listeyle birlikte mevcut düğümün kendisini de döndürür. <Musteri MusteriId="1"> <AdSoyad>Zeynep Özilhan</AdSoyad> <Adres> <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> <Sehir>Ankara</Sehir> </Adres> </Musteri> <AdSoyad>Zeynep Özilhan</AdSoyad> Zeynep Özilhan <Adres> Bölüm 32

356 824 C# Programlama Dili <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> <Sehir>Ankara</Sehir> </Adres> <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> Umut Cad. Bahar Sk. <Sehir>Ankara</Sehir> Ankara Descendants(): Mevcut düğümün en alt seviyeye kadar tüm alt elementleri ayrı ayrı listeler. <AdSoyad>Zeynep Özilhan</AdSoyad> <Adres> <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> <Sehir>Ankara</Sehir> </Adres> <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> <Sehir>Ankara</Sehir> Bu yordamın, DescendantNodes() yordamından farkı, alt elemanları sadece element bazında listelemesidir. DescendantsAndSelf(): Descendants() yordamının döndürdüğü listeyle birlikte mevcut elementin kendisini de döndürür. <Musteri MusteriId="1"> <AdSoyad>Zeynep Özilhan</AdSoyad> <Adres> <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> <Sehir>Ankara</Sehir> </Adres> </Musteri> <AdSoyad>Zeynep Özilhan</AdSoyad> <Adres> <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> <Sehir>Ankara</Sehir> </Adres> <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> <Sehir>Ankara</Sehir> Elements(): Mevcut düğümün en alt seviye kadar tüm alt elementlerini listeler. <AdSoyad>Zeynep Özilhan</AdSoyad> <Adres> <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> <Sehir>Ankara</Sehir> </Adres> XML belgesindeki tüm içerikleri metin olarak yazdıralım. var sorgu = xdmusteri.descendantnodes().oftype<xtext>(); foreach(var sonuc in sorgu) Console.WriteLine(sonuc); Papatya Yayıncılık Eğitm

357 SQL ve XML için LINQ Kullanımı 825 Zeynep Özilhan Umut Cad. Bahar Sk. Ankara Cem Can Büyükdere Cad. İstanbul Ayşe Coşkun Maslak Cad. İstanbul XML Belgesinde DML Örnekleri XLINQ teknolojisini sayesinde XML belgeleri üzerinde kolayca veri düzenleme işlemleri yapabiliriz. Bu bölümde bulunla ilgili birkaç örnek yapacağız. XML belgesindeki elemente yeni bir alt element veya öznitelik eklemek için XElement sınıfının Add() yordamı kullanılır. Add() yordamı, yeni elementi, alt e- lementlerinin sonuna yazdırır, listenin başına yazdırmak için AddFirst() kullanılır. var xdmusteri = Xdocument.Load(@"C:\Musteri.xml"); XElement xemusteri; xemusteri = xdmusteri.elements("musteriler").elements("musteri").first(); xemusteri.add(new XAttribute("Kod","ZÖ")); xemusteri.add(new XElement("Telefon", "(0216) ")); Console.WriteLine(xeMusteri); <Musteri MusteriId="1" Kod="ZÖ"> <AdSoyad>Zeynep Özilhan</AdSoyad> <Adres> <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> <Sehir>Ankara</Sehir> </Adres> <Telefon>(0216) </Telefon> </Musteri> String türündeki bir XML içeriği, XElement sınıfının Parse() yordamı aracılığıyla XElement türüne dönüştürebiliriz. xemusteri.add(xelement.parse("<telefon>(0216) </Telefon>")) Bir elementin altına birden fazla element veya içeriği ekleyelim. var altelement = new XElement("yeni", new XComment("Yeni Telefon"), new XElement("Telefon", "(0216) "), new Xattribute("Kod","ZO")); xemusteri.add(altelement.nodes()); <Musteri MusteriId="1"> <AdSoyad>Zeynep Özilhan</AdSoyad> <Adres> Bölüm 32

358 826 C# Programlama Dili <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> <Sehir>Ankara</Sehir> </Adres> <!--Yeni Telefon--> <Telefon>(0216) </Telefon> </Musteri> Bir elemente ait elementleri silmek için RemoveAll(), öznitelikleri silmek için de RemoveAttributes() yordamı kullanılır. xemusteri.removeall(); <Musteri /> Musteri elementindeki MusteriId özniteliğini kaldıralım. xemusteri.attribute("musteriid").remove(); <Musteri> <AdSoyad>Zeynep Özilhan</AdSoyad> <Adres> <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> <Sehir>Ankara</Sehir> </Adres> </Musteri> Bir özniteliğin değerini de aynı mantıkla güncelleyebiliriz. xemusteri.attribute("musteriid").value = "100"; <Musteri MusteriId="100"> <AdSoyad>Zeynep Özilhan</AdSoyad> <Adres> <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> <Sehir>Ankara</Sehir> </Adres> </Musteri> XML belgesindeki bir elementin değerini değiştirmek için aşağıdaki gibi bir satır yazılabilir. xemusteri.element("adsoyad").value ="Zeynep İlhan"; // Veya // xemusteri.setelementvalue("adsoyad", "Zeynep İlhan"); <Musteri MusteriId="1"> <AdSoyad>Zeynep İlhan</AdSoyad> <Adres> <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> <Sehir>Ankara</Sehir> </Adres> </Musteri> Papatya Yayıncılık Eğitm

359 SQL ve XML için LINQ Kullanımı 827 AdSoyad elementini silelim. xemusteri.element("adsoyad").remove(); // Veya // xemusteri.setelementvalue("adsoyad", null); <Musteri MusteriId="1"> <Adres> <CaddeSokak>Umut Cad. Bahar Sk.</CaddeSokak> <Sehir>Ankara</Sehir> </Adres> </Musteri> Özet DLINQ kavramı, ilişkisel veritabanındaki öğeleri nesnesel ifadelerle sorgulamayı temsil eder. Bir uygulama içerisinde bu tür sorgulamaları yapabilmek için öncelikle veritabanı üyelerini o uygulama içerisinde temsil edecek varlıkların oluşturulması gerekir. Her varlık sınıfı veritabanındaki bir table veya view nesnesine denk gelir. Uygulama, bu sınıfların içindeki yordam ve öznitelikler aracılığıyla veriye erişir, veriyi işler..net Framework te varlık sınıflarını otomatik oluşturmak için iki araç kullanılır; SqlMetal.exe ve VS.NET e gömülü O/R Designer. Depolama ve dağıtımı konusunda kolaylık sağlayan XML belgelerinin en büyük sıkıntısı sorgulama teknolojilerinin yeterince kullanışlı veya güçlü olmamasıdır. Bilindiği gibi XML belgelerini sorgulamak için DOM arabirimi, XQuery veya XSLT gibi XML belgelerine özgü teknolojiler kullanılır. Fakat bu yöntemler günümüz yazılım geliştirme ölçülerine göre teknolojik olarak geride kalmış olmaları veya uzmanlık gerektirmeleri sebebiyle programcılar tarafından şikayet konusu olmuştur. XML belgelerini ilişkisel veri tabanlarını sorguladığımız kolaylıkta sorgulamak için LINQ teknolojisinin en önemli bacağı olan LINQ to XML kavramı geliştirildi. LINQ to SQL yaklaşımında kaynak olarak ilişkisel veri tabanlarını aldığımız gibi LINQ to XML yaklaşımında da XML belgeleri kaynak olarak alınır Sorular 32.1) DLINQ, XLINQ teknolojileri nasıl kullanılır? 32.2) DLINQ da SQL Stored Procedure yapılarının kullanımı örneklendiriniz? 32.3) DLINQ da transaction desteğiyle ilgili bir örnek veriniz? 32.4) XLINQ kullanarak XML belgesinde arama, seçme işlemlerini yapınız? Bölüm 32

360 828 C# Programlama Dili Papatya Yayıncılık Eğitm

361 Ek A..NET Framework Mimarisi ve Bilişenleri A.1. Framework Class Library (FCL) nin Önemli İsim-uzayları İsim-uzayı Adı Kullanılma Amacı Örnek Sınıflar System Veri türlerini içerir. Integer, String System.Data Veritabanı yönetim ve işlemlerini içerir. SqlConnection DataSet System.Diagnostics System.Drawing System.EnterpriseServices System.Globalization System.IO Performans Günlükleri ve Uyarıları ve hata ayıklama işlemleri için gerekli sınıfları içerir. İki boyutlu grafiklerin oluşturulması ve yönetiminde görevlidir. COM+ uygulaması, transaction, Object Pooling, JIT Activation, Queue bileşenlerini destekler. Dil, takvim, para birimi, sayı ve sözce formatı gibi ulusal dil değişkenlerin biçimlerini sunar. G/Ç işlemlerini yapmak, klasör ve dosyalara erişmek, onları yönetmek için kullanılır. Debug Trace Bitmap Color Calendar CultureInfo File, FileStream StreamWriter StreamReader System.Linq Yeni nesil veri erişim yöntemlerini içerir. Enumerable Queryable System.Net Ağ işlemlerini barındırır. HttpWebResponse HttpWebRequest System.Reflection Üst-veriyi inceler ve b-kod öğelerine eriştirir. Assembly, Module System.Runtime.Remoting Uzaktan erişmeyi sağlar. TcpChannel HttpChannel System.Security System.Text Veri ve kaynakların güvenliğinden sorumludur. ASCII, Unicode ve UTF gibi farklı kodlama işlemlerini yürütür. Permissions Policy ASCIIEncoding StringBuilder System.XML XML şema ve veri işlemleri sağlar. XmlDocument XmlConvert System.Windows.Forms Windows GUI uygulamaları için. TextBox ColorDialog System.Web.UI Web formları için gerekli sınıfları içerir. System.Web.Services Web hizmetleri için gerekli sınıfları içerir. Bu isim uzaylarının sayısı ve içerikleri, Framework ün sürümüne bağlı olarak değişebilmektedir.

362 830 C# Programlama Dili A.2.NET Dosya Formatı (Portable Executable - PE) Bir dosyanın işletim sistemi tarafından yürütülebilmesi için onun çözebildiği ve anlayabildiği dosya formatına sahip olması gerekir. Yani o dosyanın, çalışacağı ortamın anlayacağı veri ve kodları içeriyor olması gerekir. Windows işletim sistemlerinin ortak dosya formatı PE (Portable Executable) olarak bilinir. Win32 PE dosya yapısı, Windows işletim sistemlerinde çalışacak dosyalar (EXE, DLL, OBJ, SYS, OCX vs.) için oluşturulmuş ortak bir formattır. UNIX tarafından geliştirilen Common Object File Format (COFF) isimli standart formattan üretilmiştir. Portable kavramı 32-bit ve 64-bit arası geçişi simgelemektedir. PE dosyaları: DOS başlığı (Programın DOS ortamındaki uygunluğunu belirtir.) DOS stub (DOS ortamı için çalışacak alan. This program cannot be run in DOS mode. mesajı buradan verilir.) PE Dosya Başlığı Bölüm Tablosu (Bellek veya disk üzerinde kullanılacak alanın büyüklüğü gibi uygulama ile ilgili özel bilgileri belirtir.) Bölüm 1 Bölüm 2... Bölüm n gibi bölümlerden oluşur. Bu bölümler, o dosyanın hangi ortamda geliştirildiği, hangi kütüphanelerin kullandığı, hangi ortamlarda çalışabileceği, hangi işlemcileri desteklediği, varsa dışardan kullanılan kütüphaneleri ve programın başlama alanını belirtir. Tüm PE dosyaları DOS Başlığı ve PE Başlığı ile başlamak zorundadır. PE dosyaları bir disk olarak varsayılırsa PE Dosya Başlığı alanını boot sektör, bölüm (section) alanlarını da diskteki dosyalar gibi düşünülebilir. Programda başka DLL lerden yararlanılmışsa bunlar da kodun içine dahil edilir. Örneğin CLR tabanlı uygulamalarda MSCorEE.dll.idata Section bölümünden referans edilir..net Framework, bu bölümlere ek olarak PE dosyalarına CLR Başlığı ve CLR Veri alanlarını ekler. CLR Başlık alanı, dosyanın.net uygulaması olduğu bilgisini, CLR Veri alanı ise üst-veri/kimlik tablosu (Metadata) ve ara-dil kodunu (IL) içerir. Bu yeni alanlar, Visual Studio (VS.NET) ile birlikte gelen DUMPBIN.EXE aracıyla görülebilir. Metadata kavramına geçilmeden önce DUMPBIN ile bu tabloları görelim. Visual Studio.Net Komut Satırını açıp sahip olduğumuz EXE ve DLL dosyasını DUMP- BIN aracıyla incelemek için tüm sonuç bir metin dosyasına yazdırılırsa: Papatya Yayıncılık Eğitim

363 .NET Framework Mimarisi ve Bilişenleri 831 C:\>DUMPBIN Selamver.dll /all /out:selamverdll.txt DLL Dosyasının Sonucu (Sonucun hepsi değerlendirilmemiştir.) Dump of file SelamVer.dll PE signature found File Type: DLL FILE HEADER VALUES 14C machine (x86) 4 number of sections 430A6309 time date stamp Mon Aug 22 16:43: file pointer to symbol table 0 number of symbols E0 size of optional header 210E characteristics Executable (YÜRÜTÜLEBİLEN DOSYA) Line numbers stripped Symbols stripped 32 bit word machine (32 BIT İşletim Sistemi) DLL (DLL dosyası olduğu) SECTION HEADER #1 ( ) flags Code Execute Read (CLR tarafından Execute edilecek anlamında) RAW DATA #1 (CLR Data Alanı) : %...H... ( ) clr Header*: 48 cb 2.00 runtime version 2074 [ 46C] RVA [size] of MetaData Directory 1 flags 0 entry point token 2050 [ 0] RVA [size] of Resources Directory ( ) Section contains the following imports: mscoree.dll Import Address Table Import Name Table 0 time date stamp 0 Index of first forwarder reference 0 _CorDllMain * CLR Header bölümünde mscoree.dll dosyasına link verildiği belirtilmiştir. Burası CLR ın çekirdeğidir. mscoree.dll da bulunan _CorDllMain() isimli fonksiyonun dışarında alındığı görülüyor. İşletim sistemi, bu fonksiyon aracılığıyla CLR ı başlatıp aktif olmasını sağlamaktadır. Ayrıca sonucun hepsi incelediğinde PE dosyalarının RVA (Relative Virtual Address) isimli terimi kullandığı görülür. RVA değeri belirli bir bağıntıya göre hesaplanarak, öğelerin disk üzerindeki adreslerini belirtir. Ek A

364 832 C# Programlama Dili Şimdi de EXE dosyasının farklılıklarını görelim: C:\>DUMPBIN Selamver.exe /all/out:selamverexe.txt EXE Dosyasının Sonucu (Sonucun hepsi değerlendirilmemiştir.) Dump of file Selamver.exe PE signature found File Type: EXECUTABLE IMAGE FILE HEADER VALUES 14C machine (x86) 4 number of sections 430BBD6D time date stamp Tue Aug 23 17:21: file pointer to symbol table 0 number of symbols E0 size of optional header 10E characteristics Executable Line numbers stripped Symbols stripped 32 bit word machine ( ) clr Header: 48 cb 2.00 runtime version 3194 [ A20] RVA [size] of MetaData Directory 1 flags entry point token 2050 [ EF7] RVA [size] of Resources Directory ( ) Directory Section contains the following imports: mscoree.dll Import Address Table 11003BDC Import Name Table 0 time date stamp 0 Index of first forwarder reference 0 _CorExeMain Bu iki sonuçtan da görüldüğü gibi C# ve VB.NET gibi dillerle geliştirilen kodlar derlenince bir DLL veya EXE dosyası oluşur. Bunlar PE (Portable Executable) veya Microsoft COFF (Common Object File Format) formatına sahip dosyalardan farklı olarak CLR Başlık ve CLR Veri bölümlerini de içerirler. Bu nedenle klasik PE dosyaları, işletim sistemleri tarafından çok rahat çalıştırılabilirken.net ortamında oluşturulmuş PE dosyalarının işlenip yürütülmesi için CLR ın devreye girmesi gerekmektedir. Sonuç olarak bu yeni nesil PE dosyaları dört bölüm içermiş olmaktadır: PE Başlık (Header): Uygulamanın türü hakkında bilgi veren standart Windows PE dosya başlığıdır. İşlemcinin anlayacağı makine kodu hakkında bilgi tutmaktadır. Dosyanın indisini gibi düşünülebilir. CLR Başlık (Header): CLR ın uyarlaması, bayraklar ve modülün başlama noktası (Main() yordamı) gibi CLR tarafından yorumlanan bilgileri içerir. Papatya Yayıncılık Eğitim

365 .NET Framework Mimarisi ve Bilişenleri 833 Üst-veri (Metadata): Programda kullanılmış veri türleri ve elemanlar hakkında bilgi veren üst-veri tablosudur. Veriyi tanımlar. IL Kod: Kullanılan dilin derleyicisiyle derlenmiş ve CLR tarafından makine koduna derlenecek IL kodudur. PÜF! PE dosyaları Disassembler, Decompiler, Hexadecimal viewer veya Hex editor gibi araçlar kullanılarak incelenebilir. A.3. NGEN mi JIT mi? IL kodu, günümüz işlemcileri tarafından doğrudan çalıştırılamaz. Çalışması için makine koduna dönüştürülmesi gerekir. Bu dönüştürme işlemi JIT derleyicileri veya NGEN aracı ile yapılır. JIT, yordamları sadece çağrıldıkları zaman makine koduna çevirirken, NGEN tüm yordamları bir kerede makine koduna çevirir. JIT derleyicisinin derlediği kodlar diskte tutulmaz; bellekte tutulur. Dolayısıyla sistem kapatılıp açıldığında kodlar yeniden derlenir. NGEN.EXE aracı kullanılarak derlenen kodlar ise diskte doğal kod olarak saklanır. Bazı yazılım tasarımcıları.net kodlarının işleme sürecinin yavaş olmasından şikayetçi olmaktadır. Örneğin ilk kullanımda.net ortamında geliştirilmiş Windows uygulamasının açılmasının biraz gecikmesi onları rahatsız etmektedir. Bu işlemi hızlandırmak için NGEN.EXE aracı kullanılabilir. PÜF! NGEN.EXE aracına, <drive>:\winnt\microsoft.net\framework\<version>\ngen.exe adresinden ulaşılabilir. NGEN (Native Image Generator),.NET bütünleştirilmiş-koddan doğal kod oluşturmak ve bunu yerel makinedeki native image cache alanına kurmakta kullanılır. NGEN, bir b-kodun (assembly) tüm MSIL kodunu x86 mimarisine uygun bir şekilde makine koduna dönüştürür ve sonucu diskte saklar. B-kod ilk çalıştırıldığında CLR onun ön derlenmiş olup olmadığını kontrol eder; eğer öyle ise ön derleme kodunu yükler..net, doğal kod (native image) alanından veri okumayı, JIT üzerinden kod oluşturup okumaktan daha hızlı yaptığı için NGEN sonucu b-kod daha hızlı yüklenmiş olacaktır. Doğal kod doğrudan derlenmiş makine kodu içerdiği için IL kodlarından daha hızlı cevap verir. Doğal kodlar <drive>\winnt\assembly klasöründen görülebilir. PÜF! NGEN aracını kullanmak için yerel sistemde yönetici hakkına sahip olmak gerekiyor. Ek A

366 834 C# Programlama Dili Bilindiği gibi.net uygulamaları, CLR yönetiminde çalışır ve uygulamanın belleği en iyi şekilde kullanabilmesi için gerekli işlemleri CLR üstlenir. Fakat NGEN.EXE aracı kullanılarak derlenmiş kodlar, doğal makine kodu olduğu için bellek optimizasyonu olmaz. Bu da NGEN yönteminin olumsuz yanı olarak karşımıza çıkar. A.4..NET Yürütme Modeli (CLR Execution Model).NET kodlarının proses içerisindeki işleme sürecini inceleyelim: 1. Öncelikle bir CLR host kullanılarak CLR yüklenir ve başlatılır. Bir host, hem yönetilebilen hem de yönetilemeyen-kodlardan oluşur. Default Domain içinde çalışan yönetilen-kod kullanıcı kodlarının bulunacağı AppDomain i oluşturmakla sorumludur. Yürütme işlemi unmanaged kod içinde başlamak zorunda olduğu için tüm CLR ana sitemleri, unmanaged kod içermek zorundadır. NET Framework, CLR ı yapılandırmak, CLR ı bir proses içine yüklemek, managed kodu Default Domain e taşımak ve unmanaged koddan managed koda geçiş yapmak gibi işlemler için bir kısım unmanaged arabirim (API) içerir. Örneğin barındırma ortamları, CLR ı bir proses içine yüklemek için CorBindToRuntimeEx API sini kullanır. Bu a- rabirimler CLR başlangıç kodunda saklıdır (mscoree.dll). En önemli arabirimler şunlardır: CorBindToRuntimeEx GetCorVersion ICorRuntimeHost IAppDomainSetup CLR ı bir proses içerisine yüklemek için kullanılır. CLR ile etkileşimi sağlayacak GetCorVersion arabiriminin işaretçi değerini döndürür. Uygulamada kullanılan geçerli CRL ın uyarlamasını döndürür. Eğer CLR hala yüklenmemişse null döndürür. Çalışma motorunu başlatma ve durdurma işlemleri için kullanılır. Ayrıca CLR başlamadan önce ilgili ayarlamaları destekler. Çöp toplayıcının yöneteceği heap alanı büyüklüğü gibi. AppDomain ile ilgili ayarları düzenler. PÜF! CorBindToRuntimeEx arabiriminin birkaç parametresinden biri CLR ın hangi versiyonunun çalıştırılacağını belirtir. Eğer bu parametre, null ise başlama noktası, CLR ın son versiyonunu kullanır. 2. Yönetilebilir kodların bir proses içinde çalışmasının ikinci adımı ise kullanıcı kodlarının yürütüleceği Application Domain in oluşturulmasıdır. 3. Üçüncü adımda ise kullanıcı kodları, önceki adımda oluşturulan bir veya daha fazla Application Domain in içinde yürütülür. Papatya Yayıncılık Eğitim

367 .NET Framework Mimarisi ve Bilişenleri 835 Kısacası, yönetilen-kodları çalıştırmadan önce CLR Hosting Interface olarak adlandırılan arabirimler tarafından, CLR, bir proses içine yüklenir, bu proses içinde Application Domain ler tanımlanır ve kodlar yürütülür. NOT! Mscoree, CLR ın kendisi değildir. Uygun CLR ı bellekte barındırmakla görevli bir COM bileşenidir. mscoree.dll kütüphanesi aynı sistemde CLR ın farklı uyarlamaların kurulum ve çalışmasını destekleme yeteneğine sahiptir. Uygulamanın, CLR ı temsil eden DLL dosyalarının (mscorsvr.dll, mscorwks.dll) hangi uyarlamalarını kullanacağını yönlendirir. Bu dosyalar, %windir%\microsoft.net\framework\%versiyon%, klasöründe bulunmaktadır. Makinede Framework ün bulunduğu klasörünü öğrenmek için System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory() fonksiyonu kullanılır. Şimdi de kodlarımızın, CLR tarafında hangi aşamalardan geçerek yürütüldüğünü görelim. Hatırlarsanız CLR bölümünde CLR ın oluştuğu bileşenleri işlemiştik. Kodlarımızın, CLR tarafından yürütülüp doğal koda dönüştürülmesi bu bileşenler aracılığıyla olmaktadır. Bildiğiniz gibi programların geliştirilmesinde standart olan adımlar geliştirme, derleme / yorumlama ve yürütmedir. Compile Tasarım Derleme Yürütme Şekil-A.1. Bir program önce kodlanır sonra derlenip çalıştırılır CLR ın yürütme modeline geçmeden önce derleme (compilation) ve yorumlama (interpretation) kavramlarını açıklamak yararlı olacaktır. Bilindiği gibi işlemciler sadece kendilerine ait makine kodlarını yürütebilirler. Makine kodunu yazmak kolay ve anlaşılır olmadığı için yüksek düzeyli programlama dilleri kullanılır. Bilgisayar ile aramızdaki iletişimi sağlayan bu dilleri kullanarak uygulama geliştirir ve derleme işlemleri sayesinde bu uygulamaları makine diline çeviririz. Programlama dilleriyle geliştirdiğimiz koda kaynak kod, bu kodun makine dilinde dönüştüğü koda ise ikili kod denilir. Kaynak kodları ikili kodlara dönüştürme işlemi çalışma anında veya çalışma öncesinde gerçekleştirilir. Birinci durumda kodlar her çalıştırmada okunur ve o anda makine diline çevrilip öylece çalıştırılır. Bu yönteme yorumlama yöntemi, gerçekleştirene de yorumlayıcı denilir. İkinci durumda ise kaynak kodlar yazılır ve çalıştırılmadan önce tüm kodlar bir kereliğine makine koduna dönüştürülür; ondan sonraki çalıştırmalarda, doğrudan makine kodu devreye girer bu yüzden yorumlamadan daha hızlıdır. Bu yönteme de derleme yöntemi, gerçekleştirene de derleyici denilir. Bu yöntemleri.net üzerinde anlatacak olursak Ek A

368 836 C# Programlama Dili VB.NET veya C# dilindeki kodlarımız öncelikle derleyiciler tarafından ara dile derlenir ve ilk çalışma anında çalıştırılacağı makinenin türüne göre JIT tarafından o makinenin diline çevrilir. Aslında bu özelliğiyle.net veya Java ortamları ne tam derleme ne de yorumlama sayılır. Çünkü bu teknolojilerde bir ara-dil sözkonusu. Böylece.NET ortamında kodlarımızı geliştirir, kullanılan dilin derleyicisiyle derlenir; ancak yürütmek için (JIT tarafından) bir kereye mahsus olmak üzere yeniden derlenir. Derlenen dosyalar üst-veri ve IL kodu içerir. CLR, yürütme esnasında bu dosyayı dört adımdan geçirir: 1. Sınıf Yükleyici (Class Loader) 2. Tür Doğrulayıcı (Type Verifier) 3. JIT Derleme (Compilation) 4. Yürütme (Execution) Aşağıdaki şekil CLR ın önemli aşamalarını göstermektedir: VB.NET C# Compile PE Dosya (Metadata+IL) Sınıf Yükleyici Sınıf Kütüphanesi Doğrulayıcı Econo JIT Default JIT PreJIT Bellek yönetimi Güvenlik işlemleri Debug etme işlemleri İstisnai durum yönetimi Eski bileşenlerle çalışma Yönetilen doğal kod Yönetilen Kodu Yürütme Hizmetleri Şekil-A.2. CLR nin yürütme modeli.net ortamında geliştirilmiş program çalıştırılmaya başlandığı zaman Windows, PE dosyasının.idata Section bölümünde MSCorEE.dll dosyasının alındığını ve DLL dosyasının içindeki _CorExeMain() yordamı çağrıldığını görecek, DLL dosya- Papatya Yayıncılık Eğitim

369 .NET Framework Mimarisi ve Bilişenleri 837 sının yüklenip yüklenmediğini kontrol ettikten sonra (JMP _CorExeMain komutunu gördüğü yerde) _CorExeMain() fonksiyonunu çalıştırır. _CorExeMain(), CLR ı başlatır ve dosyanın CLR başlığına bakarak b-kodun başlangıç yordamını öğrenir. Sınıf yükleyici programın ana sınıfında bulunan başlama noktasına konumlanır. Buradan itibaren başvurulan sınıfları, yordamları bulup belleğe yükler. Hangi sınıfların yükleneceğini üst-veriyi sorgulayarak öğrenir. Üst-veri sınıf yükleyiciye sınıfların hangi b-kodlardan yükleneceğini de söyler. Sınıf yükleyici doğru b-kodu buluncaya kadar üst-veriye bakar; eğer orada yok ise uygulama klasörüne, orada da yok ise paylaşmış assembly klasörü ne (GAC) bakar. CLR sınıf yükleme işleminden sonra doğrulama (verification) işlemini yapar. Bir doğrulayıcı, kodun IL dilinin formatına uyup uymadığını denetler. Ayrıca kod içinde kullanılan veri türlerinin Ortak Tür Sistemine (CTS) uygunluğunu denetler. Sonraki adımda JIT derleyicileri devreye girerek, doğrulama adımından geçmiş kodları, managed native code biçimine çevirir. Sınıf yükleyici, bir yordamı ilk defa çağırdığında yordamın sonuna o yordamın native kod adresini ekler. Böylece sonraki çağırmada direk ilgili bellek bloğundaki doğal kodu kullanır. Bu süreci özetleyip konuyu noktalayalım:.net dilleriyle yazılan kodlar kullanılan dilin derleyicisiyle derlenir. Derleyici, E- XE veya DLL dosyasının üst kısmına MSIL kodunu ve üst-veriyi ekler. Ayrıca işletim sistemi doğrudan MSIL tabanlı bu programı çalıştıramadığı için derleyici, programın başlangıç noktasını, MSCorEE.dll kütüphanesindeki _CorExeMain() veya _CorDllMain() yordamı olarak ayarlar. Böylece işletim sistemi bu programımızı çalıştırdığı zaman _Cor* yordamlarına atlar. Bu yordamlar da programın MSIL kodunun yürütülmesini sağlar yani CLR ı devreye alır. CLR da bir JIT kullanarak bu kodları işletim sisteminin anlayacağı koda çevirir. Bundan sonraki aşamada, kodumuzun doğru çalışmasını, güvenliğini sağlayacak işlemler yapılır. Bu işlemlerin en önemlileri aykırı durum yönetimi, güvenlik yönetimi ve bellek yönetimidir. A.5. Bellek Yönetimi ve Çöp Toplama Program yazılırken dikkat edilmesi gereken konulardan biri de belleği optimum şekilde kullanmaktır. Özellikle kurumsal uygulamalarda kullanılacak programların, kaynakları ekonomik ve ergonomik kullanması her zaman tercih nedeni olmuştur. Bir programcı başta bellek olmak üzere sistem kaynaklarını daima idareli kullanmalı ve meşgul ettiği alanı işi bittikten sonra serbest bırakmalıdır! Yüksek düzeyli dillerinde sınıflardan ürettiğimiz nesneleri, onlarla işimiz bittiğinde serbest bırakmamız gerekiyor. Fakat bu süreçte her zaman bu kurala uyulamadığı için programlardan istenmeyen durumlar oluşabilmektedir: Ek A

370 838 C# Programlama Dili Bazı programcılar kullandıkları nesneleri silmedikleri için bellek sarfiyatı veya bellek sızıntısı problemi yaşanıyordu. Bazı programcılar da, nesneleri iki defa silmeye çalıştıkları için programda hatalar oluşuyordu. Başka bir programcı grubu da daha önce serbest bırakılmış nesneye erişmeye kalkışıyordu. Bellek yönetimi için geliştirilen çöp toplama (GC: Garbage Collection) aracı, yeni nesnelere yer açmak veya kullanım dışı alanları serbest bırakan düzenektir. GC sistemi uzun süre erişilmeyen ve kullanılmayan nesneleri bellekten kaldırır. Oluşturulan yeni nesneler için heap üzerinde yer açar. Daha önceki Microsoft uygulamalarında bir nesneyi bellekten temizlemek için Nothing yöntemi kullanılıyordu. Bu yöntemde serbest bırakılacak nesne Nothing değerine eşitlenir. Fakat çoğu zaman nesneyi Nothing olarak sabitlemeyi unutmamız, yanlış nesneyi sonlandırmamız veya birbirine bağlı kaynakları hangi sırayla serbest bırakacağımızı kolayca yönetemeyişimiz, program içerisinde istenmeyen durumlarda neden olabilmekteydi. İş bu noktada,.net Framework ün sunduğu çöp toplama aracı, programlarımızda kontrol etmekte zorluk çektiğimiz bu bellek sızıntılarını bizim yerimize otomatik olarak yok etmektedir. GC, her zaman hem de maliyet açısından çöplerimizi bir başkasının toplaması gerektiğinin bir sonucu olarak ortaya çıkmıştır. Çöp toplama mekanizması algoritmasını daha iyi anlamanın yolu bellek birimlerini tanımaktan geçer. Bir programın çalışmaya başlama ve devam etme sürecinde işlemci ve RAM tarafında bazı operasyonlar gerçekleşir. Program belleğe yüklendiğinde belleğin segment adı verilen üç farklı alanına yayılır: Kod segmenti (Programın kod alanı) Yığın (Stack) Heap Kod segment programa ait makine kodunu tutar. Yığın ve heap alanında ise programın kullandığı veri ve kaynaklar tutulur. Bellekteki bu verilere ulaşmak için işlemci tarafında iç saklayıcılar kullanılır. Konumuzla ilgili olanlar CS (Code Segment) ve DS (Data Segment) saklayıcılarıdır. Bir program yüklendiği zaman işletim sistemi, programdaki komut ve değişkenleri belleğe yükler. Yığın (stack) alanı, işlemci tarafından verilerin geçici olarak saklandığı veya değişkenlerin tutulduğu bellek bölgesidir. Yığının büyüklüğü ve yönetimi programcının değil uygulamanın sorumluluğundadır. Derleyici veya çalıştırma ortamı, program içindeki değişkenleri ve uzunluklarını tarar ve yığın üzerinde ona göre ayırım yaparak yerleştirir. Bu yüzden derleyici, programı oluşturmadan önce yığın üzerinde oluşturulacak verilerin uzunluğunu bilmek zorundadır. Bir değişkenin hangi bellek bölgesine kaydedileceği işlemcideki yığın işaretçisi (SP: Stack Pointer) de- Papatya Yayıncılık Eğitim

371 .NET Framework Mimarisi ve Bilişenleri 839 nilen saklayıcı belirler. Yığın işaretçisi, kısaca SP yığın alanının en üst gözünü işaret eder. Saklanacak verilerin eklenmesi veya silinmesiyle SP değeri bir azaltılır veya artırılır. Bu bellek biriminin yığın olarak anılması LIFO (Son giren ilk çıkar, Last-In First- Out) ilkesine göre çalışmasından kaynaklanmaktadır. İşlemci tarafından verilerin yığın alanına konulması push, alandan alınması pop olarak tanımlanır *. Yığın, dinamik değişkenleri saklamanın yanısıra yordam çağrıları yapılırken, geri dönüş adresini saklamak, yerel değişkenleri tutmak ve yordamlara parametre yollamak için de kullanılır. Yığın bellek alanı program çalışmaya başladığı anda belirlenir ve daha sonra bu a- lanın boyutu değiştirilemez. Bu alan, işletim sistemi tarafından genel olarak kısıtlı şekilde belirlendiği için yığın üzerindeki yoğun ekleme işlemlerinde bellek birimi taşması (stack overflow) sorunu yaşanabilmektedir. Yığın Heap Şekil-A.3. Yığın ve heap alanlarının temsili gösterimi Heap alanı ise, işletim sistemi tarafından programcının yönetimine bırakılmış olup daha geniş ve kalıcı bir alandır. Genel olarak ne kadar yer kaplayacağı belirli olmayan değişkenlerin/nesnelerin veya büyük verilerin geçici olarak saklanması i- çin kullanılır. Yığın bölgesinin tersine çalıştırma ortamı (yani CLR), heap alanında ne kadar yer kullanılacağını bilmek zorunda değildir. new anahtar sözcüğüyle bir nesne oluşturulduğunda CLR, bu nesne için heap üzerinde yer tahsis eder. İş bitince heap üzerindeki nesneleri silindiğinde heap biriminin o bölgesi boşaltılmış olur. Böylece yeni değişkenler o bölgede saklanabilir. Heap alanı program etkn olduğu sürece yaşamaya devam eder. Bu nedenle genel (global) ve statik (static) değişkenler bu alan içinde tutulur. * Yığın konusunda ayrıntılı bilgi ve iç yapısı yine yayınevimizin bir kitabı olan Veri Yapıları ve Algoritmalar, Rifat Çölkesen kitabındaki Yığın ve Kuyruk bölümünden incelenebilir. Ek A

372 840 C# Programlama Dili Uygulama geliştirirken iki farklı değişken türü kullanırız; değer (value) türleri ve başvuru (reference) türleri. Değer türleri doğrudan veriyi tutan değişkenlerdir. Bu türden değişkenler tanımlandığında, CLR tarafından, belleğin yığın alanı üzerinde o türün kapladığı uzunluk kadar yer ayrılır; ve çalışma esnasında yerleri değiştirilmez. Ayrıca başka bir değişkeni de etkileyemezler. int X = 2005; şeklinde tanımladığımız X değişkeni için yığın üzerinde 4 Byte (32 bit) yer ayrılır. Başvuru türleri ise doğrudan veriyi değil de verinin atanacağı nesneyi veya nesnenin işaretçisini belirtir. Referans türleri değer türlerinden farklı olarak boş olabilir ve belleğin heap alanı üzerinde tutulurlar. Bu değişkenlerin kapladıkları alan sabit olmayıp programcı tarafından değiştirilebilir. Bu yüzden dinamik değişken olarak ta tanımlanırlar. Başvuru türündeki değişkenler o türdeki adresleriyle bilinirler. Bu adresleri tutan göstergelere işaretçi denilir. Programcı, işaretçi aracılığıyla bu nesnelere erişir ve onları yönetir. İşaretçiler, yığın üzerinde saklanır; ancak temsil ettikleri nesneler ise heap üzerinde saklanırlar..net Framework te bu heap adreslerine erişilemez. Sınıf, arabirim ve temsilci değişkenleri başvuru türüne örnek gösterilebilir..net Framework ortamında yeni bir nesne, yeni bir başvuru türü new (MSIL dilindeki newobj komutuna karşılık) operatörüyle oluşturulur. PÜF! Daha önce C, Pascal ve C++ dillerinde tanımladığımız işaretçilere karşılık.net te başvuru türleri kullanılır. SqlConnection myconnection; myconnection = new SqlConnection (); SqlConnection türünde myconnection isimli bir değişken tanımladık. Aynı zamanda alt satırda bunu bir nesneyle ilişkilendireceğimiz için yığın üzerinde bir işaretçi tanımlamış olduk. SqlConnection türündeki bu işaretçiyi SqlConnection türündeki bir nesneye bağladık. Bundan böyle bu nesne myconnection isimli işaretçiyle temsil edilecektir. Dolayısıyla myconnection, normal bir değişkenden farklı olarak bir nesneyle bağlantılı bir değişkendir. new operatörü nesne için heap üzerinde alan ayırıp bu alanın adresini döndürerek ilgili işaretçiye aktarır. Bu aşamada bir başvuru türü olan myconnection değişkeni ile yukarıda tanımlanmış değer türü olan X değişkeni arasındaki farkı daha net görebiliriz. X, tamsayı türde bir değişken olup 2005 değerini barındırır; myconnection ise nesnenin kendisini değil nesneyi işaret eden işretçiyi barındırır. Ayrıca görüldüğü gibi bir referans değeri tek başına bir şey ifade etmiyor; onu, new anahtar sözcüğünü kullanarak bir nesneyle ilişkilendirmek gerekir. Framework ortamında referans değişkenini nesneyle ilişkilendirmeden kullanmaya çalıştığımızda Object reference not set to an instance of an object. hatası alınır. Papatya Yayıncılık Eğitim

373 .NET Framework Mimarisi ve Bilişenleri 841 Bir sınıfı kullanmak istediğimizde yani new operatörüyle kendisinden bir örnek o- luşturduğumuzda otomatik olarak constructor (yapıcı) olarak tanımlanan sınıfa ait başlangıç yordamı çağrılmış olur. Aynı şekilde bu nesne, yok edilirken de arka tarafta destructor (yıkıcı) olarak bilinen sonlandırıcı yordamı tetiklenir (destruction işlemi). Destructor işleminin.net Framework teki karşılığı Finalize() yordamıdır; bu, çöp toplayıcısının bir nesneyi yok etmeden önce yapmasını istediğimiz işlemlerin tanımlandığı yordamdır. Bu yüzden sadece GC tarafından çöp temizleme esnasında otomatik olarak çağrılır; programcı tarafından doğrudan çağrılamaz. Programcı, bu yordamı çağırmak için Dispose() yordamını kullanır. Değişkenleri, genel olarak bu şekilde belleğe yüklemiş oluruz. Peki geri yüklenmeyi nasıl yapacağız; yani yüklenmiş kaynakları nasıl serbest bırakacağız. Bu işlemi programcı kendisi yapabildiği gibi çöp toplayıcısına da bırakabilir. Konumuz GC olduğu için ikinci seçenek üzerinde duracağız. GC nin çalışma mantığı, program içindeki değişkenlerin veya nesnelerin durumlarını takip edip ona göre davranış sergilemekten ibarettir. Günümüzde birkaç GC algoritması kullanılıyor. Bu algoritmalarda önemli olan değişkenlerin veya nesnelerin ne zaman silinmeye hazır olduklarının bilinmesidir. Değer türleri, yani yığın tabanlı değişkenler geçerli oldukları alandan çıktıkları zaman silinmeye hazır birer parça haline gelmiş olur. public void BirseylerYap () { // Geçerlilik alanı başlangıcı. int X = 125; // Geçerlilik alanı bitişi. Sonraki satırlarda X değişkenine ulaşılamaz. Nesneler ise kendilerine başvuru olmadığı zaman silinmeye hazır hale gelirler. Son paragraflarda anlattıklarımızı şekil üzerinde anlatmamız, çalışma ortamında neden bellek yönetiminin gerekli olduğunu daha açık gösterecektir. Yazdığımız programda Main() yordamı içerisinden aşağıdaki fonksiyonu çağırdığımızı varsayalım: public int KareAl (int Sayi) { int Sonuc; Sonuc = Sayi*Sayi; return Sonuc; // KareAl yordamının sonu. Program yüklenmeye başladığında Main() yordamı içerisindeki tüm değişken ve yordamlar sırayla yığın üzerine taşınır. Bu aşamada KareAl() yordamı ve yordamın yerel parametresi olan Sayi değişkeni ardışıl şekilde yığın birimi üzerine taşınır. Ek A

374 842 C# Programlama Dili YIĞIN Sayi (int) KareAl CLR, KareAl() fonksiyonunun bulunduğu yığın alanına konumlanır ve Sayi parametresine atama yapar. KareAl() fonksiyonu çalışırken içeride Sonuc isimli değişkenin yaratıldığı görülür. Bu değişken için de yığın üzerinde yer ayrılır. YIĞIN Sonuc (int) Sayi (int) KareAl Fonksiyonun çalışması bittikten sonra, yani bu fonksiyonu çağıran koda geri dönüldüğünde yığın alanında bu fonksiyon için açılmış tüm alanlar temizlenir. YIĞIN Sonuc (int) Sayi (int) Bu alanlar silinir KareAl Burada Sonuc değişkeni yığın alanı üzerinde oluşturuldu. Bir yordam içerisinde tanımlanmış değer türleri, otomatik olarak yığın alanı üzerinde depolanır ancak bazen heap alanı üzerinde de oluşturulabilir. Aşağıdaki kodları inceleyelim: public class Islem { public int Sonuc; //Islem public Islem KareAl (int Sayi) { Islem oislem = new Islem (); oislem.sonuc = Sayi * Sayi; return oislem; // KareAl Yine aynı şekilde KareAl() fonksiyonu çağrıldığı zaman yığın alanına taşınmış olur. Papatya Yayıncılık Eğitim

375 .NET Framework Mimarisi ve Bilişenleri 843 YIĞIN Sayi (int) KareAl Fonksiyon içindeki oislem değişkeni bir referans türüne işaret ettiği için kendisi yığın üzerinde, nesnenin kopyası da heap üzerinde depolanır. YIĞIN oislem (Pointer) Sayi (int) KareAl HEAP Islem Sonuc (int) Görüldüğü gibi Sonuc değişkeni bir değer türü olduğu halde heap üzerinde tanımlandı. Önceki durumda olduğu gibi fonksiyonun işlenmesi bittikten sonra, geçerlilik alanından çıkılma esasından dolayı yığındaki değişkenler temizlenecek ve heap alanındaki Islem nesnesinin yığın ile bağlantısı kesilmiş olacaktır. YIĞIN HEAP Islem Sonuc (int) Heap üzerindeki bu bellek alanına programcı tarafından da ulaşılamayacaktır. Bu durumda, GC nin devreye girmesi beklenir. GC, çalıştığı zaman heap üzerinde yığın ile bağlantısı kesilmiş nesneleri belirleyip onları bellekten kaldırır. Program içerisinde, bir yordamdan başka bir yordamı geçildiği zaman ikinci yordamın çalışması bittikten sonra programın kaldığı yerden devam edebilmesi için yığın üzerinde bir geri dönüş işaretçisi oluşturulur. Bu geri dönüş işaretçisi, fonksiyon çağrıları yapılırken geri dönüş adresini saklamak için kullanılır. A.5.1. Çöp Toplama Algoritması Bu ek ayrıtta MSDN deki bilgiler ışığında çöp toplayıcının (GC) algoritmasını anlamaya çalışacağız. Çöp toplayıcı işaretle ve silkele (mark and sweep) yöntemini kullanır. CLR, uygulamanın kullandığı belleği iyi yönetmek için uygulama ve kul- Ek A

376 844 C# Programlama Dili lanılacak kaynaklar için bellek üzerinde boş bir alan ayırır. Bu alana yönetilebilir heap denilir. GC, program başladığında programın üst-verisini okuyarak heap alanı için ne kadar bellek alanı ayıracağını hesaplar ve o kadar alan ayırır. Böylece her prosesin kendine ait bir yönetilebilir heap alanı oluşturulur. Değer türleri yığın üzerinde referans türleri ise yönetilen heap üzerinde tutulur. Ayrıca yönetilen heap üzerinde bir sonraki nesnenin başlangıç adresini gösteren ve kayıtlı son nesnenin sonunda bulunan bir işaretçi (NextObjPtr) bulunur. Bir program başladığı zaman tahmin edileceği gibi bu işaretçi heap alanının başlangıcını gösterecektir. Bu işaretçi sayesinde her yeni nesne yaratıldığında bellek taraması yapılmaz; nesne, doğrudan bu işaretçinin gösterdiği adrese taşınır. Yönetilen Heap Boş alan Class1 instance Obje C Obje B Obje A NextObjPtr NextObjPtr [C#] Class1 o1 =new Class1 (); [MSIL] newobj instance void Class1::.ctor () stloc.s o1 Şekil-A.4. Yönetilen heap koyulacak Class1 türündeki yeni nesne, C nesnesinden sonra yer alır Her uygulamanın merkez işaretçi bilgisinin bulunduğu kök (root) olarak bilinen tablosu vardır. Uygulamanın kök tablosu, uygulama tarafından kullanılan depolama alanlarının adreslerini tutan işaretçi kümesidir. Bunlara, genel ve statik nesnelerin işaretçileri, yerel değişken ve parametrelerin işaretçileri veya yönetilebilir heap üzerindeki nesnelerin işaretçilerini içeren işlemci saklayıcıları örnek olarak gösterilebilir. Yani root kavramı, managed heap üzerinde bulunan nesnelere veya null olan nesnelere işaret eden işaretçileri tanımlar. Bir program çalışmaya başladığında JIT ve CLR, programı çalıştırdığı AppDomain alanı içinde uygulamaya ait kök tablosunu da oluşturur. Çöp toplayıcı ilk defa çalıştığında kök tablosundan erişilebilen tüm nesneler için bir çizelge oluşturur. Heap alanında olmasına rağmen çizelgede olmayan nesneler erişilmeyen nesnelerdir. GC, onları temizlemesi gerektiğini düşünerek temizlik esnasında alanı gözden geçirir ve erişilmeyen nesnelerin meşgul ettiği bellek bloklarını arar; rastladığı zaman da onu serbest bırakır ve kalan boş yerlere yaşayan nesneleri yerleştirerek belleği düzenler. Bu düzenleme esnasında erişilebilen nesnelerin adresleri değişebilir. Uygulamaya ait kök tablosunun, nesnelerin yeni adreslerini gös- Papatya Yayıncılık Eğitim

377 .NET Framework Mimarisi ve Bilişenleri 845 terebilmesi için ilgili işaretçilere yeni adresler atanır. Ayrıca nesneler sadece kök tablosundan referans almak zorunda olmayıp heap üzerindeki başka bir nesneden de referans edilebilir. Kökler Yönetilen Heap Root3 Root2 Root1 Obje D Obje C Obje B Obje A NextObjPtr Yönetlien Heap Kökler Global Static Locals CPU Registers Obje H Obje G Obje F Obje E Obje D Obje C Obje B Obje A NextObjPtr Şekil-A.5. Nesnelerin heap üzerinde yerleşimi Şekilde görüldüğü gibi uygulamanın kök tablosu tarafından doğrudan A ve C nesnelerine referans verilmiştir. Dolayısıyla A ve C nesneleri çizelgede bulunacaktır. A veya C nesneleri çizelgeye eklendiği zaman çöp toplayıcı, bu nesnelerin E nesnesini referans gösterdiğini görecek ve E nesnesini de çizelgeye ekleyecektir. Aynı şekilde E nesnesini eklerken bu nesnesinin G nesnesini referans ettiğini görecek ve G nesnesini de çizelgeye ekleyecektir. Böylece çizelgeye A, C, E ve G nesneleri eklenmiş olacaktır. GC, kök üzerinde oluşturulmuş işaretçileri taramayı bitirdikten sonra heap üzerinde aşağıdan yukarıya tarama gerçekleştirir. Kendisine başvuru olmayan nesneleri (B, D, F ve H) temizler ve bir sonraki nesneyi aşağı kaydırır. GC nin bu düzenleme işleminden sonra heap alanı aşağıdaki şekilde görünecektir: Ek A

378 846 C# Programlama Dili Kökler Yönetilen Heap Global Static Locals CPU Registers Obje G Obje E Obje C Obje A NextObjPtr Şekil-A.6. Yönetilen Heap ın çöp toplayıcının düzeltme işleminden sonraki yerleşimi Görüldüğü gibi kullandığımız kaynakların yaşam sürelerini kontrol etmek için ek kod geliştirmiyoruz. GC, sadece boşa düşmüş nesneleri temizlemekle yetinmeyip bizim yerimize belleği da düzenli hale getirmektedir. A.5.2. Finalization Çöp toplayıcı işimizi kolaylaştıracak ek bir özellik daha sunar: Finalization. Finalization, bir kaynağın silinmeye başlamadan önce sorunsuz bir şekilde son işlemlerini yerine getirmesidir. GC, bir nesneyi serbest bırakacağı zaman finalization işlemini gerçekleştirir. Finalize() yordamı System.Object sınıfının bir üyesidir..net Framework te tüm nesneler, bu sınıftan türediği için Finalize() yordamı o- tomatik olarak oluşturulan tüm nesneler için de tanımlanır. Nesnenin sonlandırılması esnasında başka işlemlerin de gerçekleşmesi isteniyorsa, bu işlemleri nesnenin Finalize() yordamına yazılması yeterli olacaktır. GC Finalization işlemi otomatik olarak GC tarafından çağrılır. C# ta başlangıç yordamı <Sınıf Adı> şeklinde, Finalize() yordamı ise doğrudan tanımlanmayıp ~<Sınıf Adı> şeklinde bitiş yordamı olarak tanımlanır. public class Class1 { public Class1 () { DosyayaYaz ("Nesne Oluşturuldu."); ~Class1 () { DosyayaYaz ("Nesne Kaldırıldı."); //Class1 Finalize() yordamına sahip Class1 sınıfından aşağıdaki satırda olduğu gibi bir nesne oluşturulur. Bu nesneyle işimiz bittikten bir süre sonra GC devreye girecek ve nesnenin Finalize() yordamını çalıştırır. Class1 o1 = new Class1 (); Burada dikkat edilmesi gereken nokta, Finalize() yordamının bilinçli kullanılmasıdır. Finalize() içerisinde yapılacak özel bir işlem yoksa Finalize() yordamını tanımlayarak GC ye zaman kaybettirmemeliyiz. Papatya Yayıncılık Eğitim

379 .NET Framework Mimarisi ve Bilişenleri 847 A.5.3. Finalization ve Çöp Tolayıcı GC, Finalize() yordamına sahip nesneyi hemen silmez. Bunun için farklı bir iş akışı gerçekleştirir. GC bu işin yönetimi için Finalization Queue ve Freachable Queue şeklinde iki tablo oluşturur. GC, öncelikle bu kök alanlarını belirleyip tarar ve erişilebilir nesne tablosu çıkarır. Bu nesnelerin üst-verisini sorgulayarak nesnelerden yıkıcı yordamı (Finalize()) olanları ulaşılabilir olarak işaretler ve onları gösterecek bir işaretçiyi Finalization Queue tablosuna ekler. Yönetilen Heap Kökler Global Static Locals CPU Registers Obje H Obje G Obje F Obje E Obje D Obje C Obje B Obje A NextObjPtr Finalization Kuyruğu Obje E Obje D Obje C Obje A Şekil-A.7. Çöp toplayıcı Finalize() yordamı olan her nesne için tabloya bir işaretçi ekler Çöp toplayıcı devreye girdiği zaman B, D, E, G ve H nesneleri kök tablosundan kendilerine referans bir işaretçi olmadığı için silmeye hazır alanlar olacaktır. GC, finalization listesinde bu nesnelere ait herhangi bir işaretçinin olup olmadığını öğrenmek için listeyi tarar ve bulduğu işaretçileri bu listeden çıkarıp Freachable Queue listesine ekler. Freachable Queue, Finalization Queue gibi GC tarafından kontrol edilen bir tablodur. Freachable listesindeki her işaretçi bitiş yordamı çağrılacak olan ilgili nesneyi gösterir. GC nın düzenleme işleminden sonraki heap görüntüsü şu şekilde olacaktır. Şekilde görüldüğü gibi B, G ve H nesneleri, bitiş yordamına sahip olmadıkları için bekletilecek bir durum olmadığı için GC tarafından hemen silindiler. Fakat belleği meşgul etmeye devam eden D ve E nesneleri silinmeyip bir sonraki taramada bitiş yordamları çağrılsın diye beklemeye alındılar. Bir süre sonra GC tekrar devreye girecek ve Freachable kuyruğundaki nesnelerin bitiş yordamını çalıştırıp, kapladıkları bellek alanını serbest bırakır. Sonuçta heap üzerinde A, C ve F nesneleri kalacaktır. Ek A

380 848 C# Programlama Dili Kökler Global Static Locals CPU Registers Yönetilen Heap Obje F Obje E Obje D Obje C Obje A NextObjPtr Finalization Kuyruğu Obje C Obje A Freachable Kuyruğu Obje E Obje D A.5.4. Şekil-A.8. Freachable kuyruğunun oluşturulması Çöp Toplayıcının Programlanması Çöp toplayıcının ne zaman çalışacağını ve nesneleri ne zaman temizleyeceğini önceden kestiremeyiz. GC, genel olarak belleğin, yeni bir nesneyi karşılayamayacak kadar yetersiz olduğu anlarda devreye girer. Örneğin RAM i düşük bir sistemde GC daha kısa sürede devreye girmektedir. Program içerisinde GC yi başlatmak için GC.Collect() kullanılır. Collect() yordamıyla GC yi çalıştırmaya zorladığımız için işini bitirinceye kadar beklenmesi için GC.WaitForPendingFinalizers() yordamını da kullanmamız yararlı olacaktır. Class1 mynesne = new Class1 (); GC.Collect () Buraya kadar ki örneklendirdiğimiz nesneler GC tarafından kolaylıkla silinen yönetilebilen nesnelerdir. Yani CLR tarafından yönetilebilen kaynaklardı. Uygulamada yönetilemeyen nesneler de kullanılmışsa ne yapılması gerekir? Bir nesneyi heap alanından silerken onunla ilgili yönetilemeyen kaynakları da serbest bırakmamız gerekiyor. Bunun için Dispose() yordamı kullanılır. Dispose() yordamı GC devreye girmeden, uygulama geliştiricinin nesnenin kullandığı yönetilebilir veya yönetilemeyen kaynakları serbest bıraktığı bir yöntemdir. Bu yordam çöp toplayıcı tarafından doğrudan çağrılmaz. PÜF! Dispose() yordamı IDisposable arabiriminin bir üyesidir. Dolayısıyla bu yordamın tanımlanacağı sınıfın IDisposable arabirimini desteklenmesi sağlanmalıdır. GC, sadece Finalize() yordamını çalıştırdığı için nesne, programcı tarafından değil de GC tarafından silineceği zaman yönetilemeyen kaynakların da yok edilmesi için nesnenin Finalize() yordamı içinden Dispose() yordamının çağrılması gerekir. Bu esnada GC nın yönetilebilir kaynakları kaldırmasına gerek kalmayacağı için Dispose() yordamında bool türünde bir şart koyarak GC yönlendirilir. Kısacası GC, sadece yönetilebilir kodları/kaynakları yönetebildiği için uygulama içinde yönetilemeyen kaynak kullanmışsak onun temizliğini biz yapmalıyız. Temizlik ya- Papatya Yayıncılık Eğitim

381 .NET Framework Mimarisi ve Bilişenleri 849 pan kodlarımızı da Dispose() yordamına ekleyip CG nin de bunları çalıştırmasını sağlıyoruz. Bu bilgiler ışığında Class1 nesnemiz yeni şekliyle şu şekilde olacaktır: public class Class1: IDisposable { public Class1 () { MessageBox.Show ("Nesne Oluşturuldu."); //Implement IDisposable. public void Dispose () { Dispose (true); GC.SuppressFinalize (this); //Dispose protected virtual void Dispose (bool disposing) { if (disposing) { // Yönetilen kaynakları serbest bırak. // Yönetilmeyen kodları ve büyük parçaları serbest bırak. MessageBox.Show ("Kaynaklar Temizlendi"); //Dispose ~Class1 () { Dispose (false); //Class1 Bu sınıftan bir nesne oluşturup kullandıktan sonra nesnenin Dispose() yordamını çalıştırırsak kaynakları serbest bırakmış oluruz. Class1 mynesne = new Class1 (); mynesne.dispose (); Dispose() yordamında GC.SuppressFinalize(this) ifadesi dikkatinizi çekmiştir. Bu yordam GC nin nesneyi tararken nesne için tanımlı bitiş yordamını çağırmasını engeller. Bu nesne, programcı tarafından dispose() yordamı aracılığıyla temizlendiği için Finalization Queue tablosunda pasif durumuna düşer ve GC nin, ikinci taramasında bu nesnenin Finalize() yordamını çalıştırmasına gerek kalmaz. Dispose() yordamını herhangi bir hata oluşması durumunda hatayı yönetebilmek için genellikle try-catch-finally bloğunu kullanarak çağırırız. Aşağıdaki tabloda SqlConnection ve SqlCommand nesnelerinin basit kullanımı gösterilmiştir; SqlConnection cnn = new SqlConnection ("connectionstring"); SqlCommand cmd = new SqlCommand ("commandstring", cnn); cnn.open (); cmd.executenonquery (); cnn.close (); Ek A

382 850 C# Programlama Dili Bu format basit olduğu için tercih edilir. Fakat SqlConnection ve SqlCommand nesneleri IDisposable arabirimini uyguladıkları için bu nesnelerle işimiz bittikten sonra herhangi bir istisnai durum olsa da olmasa da nesnelerin Dispose() yordamını çağırmalıyız. SqlConnection cnn = null; SqlCommand cmd = null; try { cnn = new SqlConnection ("connectionstring"); cmd = new SqlCommand ("commandstring", cnn); cnn.open (); cmd.executenonquery (); finally { if (cmd!= null) cmd.dispose (); if (cnn!= null) cnn.dispose (); // cnn.close () de çağrılır Dispose() yordamının çağrılmasına alternatif ikinci bir yöntem daha bulunmaktadır; using anahtar sözcüğünün kullanılması. using bloğu içerisinde kullanılan nesne, işi bitince otomatik olarak dispose edilir. Önceki örneği using yöntemiyle aşağıdaki gibi kodlayabiliriz: using (SqlConnection cnn = new SqlConnection ("connectionstring")) { using (SqlCommand cmd = new SqlCommand ("commandstring", cnn)) { cnn.open (); cmd.executenonquery (); //cmd.dispose () çağrılır //cnn.dispose () çağrılır Bu örneğin MSIL kodu incelendiğinde aslında CLR ın arka tarafta yine aynı şekilde try-catch-finally bloğunu kullandığına şahit oluruz..try { IL_000c: nop IL_000d: ldstr "commandstring" IL_0012: ldloc.0 IL_0013: newobj instance void [System.Data]System.Data.SqlClient.SqlCommand::.ctor (string, class [System.Data]System.Data.SqlClient.SqlConnection) IL_0018: stloc.1.try { IL_0019: nop IL_001a: ldloc.0 IL_001b: callvirt instance void [System.Data]System.Data.Common.DbConnection::Open () IL_0020: nop IL_0021: ldloc.1 IL_0022: callvirt instance int32 Papatya Yayıncılık Eğitim

383 .NET Framework Mimarisi ve Bilişenleri 851 [System.Data]System.Data.Common.DbCommand::ExecuteNonQuery () IL_0027: pop IL_0028: nop IL_0029: leave.s IL_003b // end.try finally { IL_002b: ldloc.1 IL_002c: ldnull IL_002d: ceq IL_002f: stloc.2 IL_0030: ldloc.2 IL_0031: brtrue.s IL_003a IL_0033: ldloc.1 IL_0034: callvirt instance void [mscorlib]system.idisposable::dispose () IL_0039: nop IL_003a: endfinally // end handler IL_003b: nop IL_003c: nop IL_003d: leave.s IL_004f // end.try finally { IL_003f: ldloc.0 IL_0040: ldnull IL_0041: ceq IL_0043: stloc.2 IL_0044: ldloc.2 IL_0045: brtrue.s IL_004e IL_0047: ldloc.0 IL_0048: callvirt instance void [mscorlib]system.idisposable::dispose () IL_004d: nop IL_004e: endfinally // end handler using bloğuyla ilgili en önemli nokta bu blok içerisinde kullanılacak nesnenin IDisposable arabirimini desteklemesi gerekir. Sonuç olarak GC yi manual çalıştırmaya zorlayabiliriz; ancak mimarisi gereği düzenlemeyi GC her zaman bizden daha iyi yapacağı için bu konuda GC yi özgür bırakmak önerilir. Bununla birlikte kullandığımız nesne, yönetilmeyen kaynak kullanıyorsa bu kaynakları serbest bırakmak için nesnenin Dispose() yordamını çağırmalıyız. A.5.5. Çöp Toplama Performansı GC, işlemciyi yoğun şekilde kullanan bir işlemdir. Bu yüzden daha performanslı çalışmak ve zaman kaybetmemek için bazı yöntemler kullanır: Generations GC, performansı arttırmak için nesneleri yaşadıkları süreye generation denilen yaşam evrelerine göre sınıflandırır. Ek A

384 852 C# Programlama Dili.NET Framework, heap alanını nesnelerin yaşam evrelerine göre üç alana/kuşağa (generation) ayırır. Bir nesne heap biriminde oluşturulduğu anda Generation 0 olarak işaretlenir. Böylece GC tarafından hiç gözden geçirilmemiş yeni nesneler, Generation 0 alanında bulunur. Tüm yeni nesneler için ayrılan bu alan dolunca GC devreye girerek heap üzerindeki nesnelerin yaşam durumlarını kontrol eder. Silinmeye hazır nesneler, varsa onlara ait bitiş yordamları çalıştırılarak heap alanından temizlenir. Temizleme işleminden sonra GC heap alanının sonundaki nesneleri başa çekerek boşlukları doldurur; gerekli düzenlemeleri yapar. Bu işlemden canlı kurtulan nesneler bir nesil daha yaşlanarak Generation 1 olarak işaretlenir. GC, başlamadan önce sıfır yaşında olan nesneler bu işlemden sonra 1 yaşına gelmiş olur. Aynı şekilde yeni nesneler oluşturulurken ilk nesil alanı doluysa tekrar temizlik işlemi başlar ve Generation 1 işaretli nesneler Generation 2 olarak işaretlenir..net Framework'te üç tane Generation aşaması olduğu için bu adımdan sonra Generation 2 deki nesneler yine Generation 2 de kalır; ayrıca tüm Generation alanları dolarsa OutOfMemoryException türünden hata oluşur. Önceki paragraflarda çöp toplayıcının ne zaman çalışacağından bahsetmiştik. GC, genellikle sözü edilen Generation 0 alanı dolduğu zaman devreye girer. GC, bu nesillendirmeyi iki ilkeye dayanarak yapmaktadır: 1. Yeni oluşturulmuş nesneler, az yaşamaya meyillidir. 2. Eski oluşturulmuş nesneler, daha uzun yaşar. Bir uygulama heap üzerinde büyük bir alan ayırdığı zaman çöp toplayıcının çalışması uzun süreceği için Generation yöntemiyle işini parçalara ayırır. GC, her çalıştığında nesneleri bir adım ileriye öteler. Böylece her yeni nesne, belleğin ön bölümüne yerleşmiş olur. Aşağıdaki şekilde çöp toplayıcı üç defa devreye girmiştir. Her devreye girişinde referansları silinmiş nesneleri kontrol ederek onları heap biriminden silmiş, geri kalanları sondan başa doğru sıralamış ve işlemlerden canlı kalan her nesne bir nesil daha yaşlanmıştır. Bundan sonraki işlemlerde canlı kalan eski nesneler heap Gen 2 de yani 2. nesil alanında kalacaklardır. PÜF! GC.Collect() fonksiyonu, void Collect(int generation) şeklinde Generation alanını parametre alarak da çalışabilir. Böylece GC nin heap biriminin sadece belirlediğimiz Generation alanını gözden geçirmesini sağlayabiliriz. Oluşturduğumuz nesne için kendi geçerlilik alanında GC.Collect() yordamını çağırdığımızda nesne hemen silinmeyecek sadece sırasıyla Generation 1, Generation 2 olarak yaşam evresi değişecektir. Eğer geçerlilik alanı içinde GC.Collect() yapıldığı zaman nesneyi yaşlandırmak yerine nesnenin hemen yok edilmesini istersek Collect() işleminden önce nesneyi serbest bırakmamız yani null değerine eşitlememiz gerekir. Papatya Yayıncılık Eğitim

385 .NET Framework Mimarisi ve Bilişenleri 853 obja Gen 0 objb Gen 1 Gen 0 obja objb objd obje objf GC.Collect() GC.Collect() Gen 2 Gen 1 Gen 0 obja objb objd obje objf objg objh Gen 2 Gen 1 Gen 0 obja objb objd obje objg objh obji objj objk objl GC.Collect() Şekil-A.9. Garbage Collector nesneleri, yaşlarına göre gruplandırır Class1 mynesne; MessageBox.Show (GC.GetGeneration (mynesne)); //Sonuç: 0 GC.Collect (); MessageBox.Show (GC.GetGeneration (mynesne)); //Sonuç: 1 GC.Collect (); MessageBox.Show (GC.GetGeneration (mynesne)); //Sonuç: 2 GC.Collect (); MessageBox.Show (GC.GetGeneration (mynesne)); //Sonuç: 2 GC.Collect (); MessageBox.Show (GC.GetGeneration (mynesne)); //Sonuç: 2 Kodların sonuçlarında görüldüğü gibi GC nin belleği üçüncü defa düzenleme işleminden sonra hala bellekte bulunan nesneler, Generation 2 de (üçüncü nesil) duruyor. Eğer nesneyle işimiz bittikten sonra onu serbest bırakırsak destruction eyleminden nesne silinmiş olacaktır. Class1 mynesne = new Class1 (); mynesne = null; GC.Collect (); // Bu işlemde mynesne serbest bırakılacaktır. PÜF!.NET Framework te bir nesnenin hangi evrede, hangi Generation alanına sahip olduğunu öğrenmek için, int GetGeneration(object obj) fonksiyonu kullanılır. Parametre olarak bilgisi alınacak nesnenin adı girilir. Ek A

386 854 C# Programlama Dili A.5.6. Güçlü ve Zayıf Referanslar Nesneleri yaratırken rutin olarak öncelikle işaretçilerini oluştururuz. İşaretçiler güçlü referans olarak anılır. İşaretçiler etkin oldukları sürece işaret ettikleri nesneler de GC tarafından silinemez. Fakat bazı nesneler, yapıları itibariyle, başlangıç anında ağır bir yüklenmeye neden olur. Bu nesnelerin tekrar kullanılma ihtimalinden dolayı, referanslarını korumak zorunda kalırız. Bu da GC nin yükünün katlanmasına neden olur. Uzun süre, bu nesnelerin işaretçilerinin büyük bir bellek bloğunu meşgul etmemesi için asıl işaretçi iptal edilip ilgili nesne için zayıf referans olarak nitelendirilen weak reference tanımlanır. Bilindiği üzere bir nesnenin işaretçisi serbest bırakıldığı zaman GC, o nesneyi siler. Fakat bu referans türü sayesinde nesne, hem ihtiyaç duyulduğunda uygulama tarafından erişilebilir hem de belleğin yetmediği durumda GC tarafından silinebilir. Yani bu işaretçiyle GC ye şu mesajı vermiş oluyoruz; nesnenin referansını şimdilik serbest bırakıyorum; fakat daha sonra yeniden bu nesneye erişmek isteyebilirim. Bu nedenle belleği taradığın zaman bellekte yer sıkıntısı yok ise silme, var ise sil. //strong referans tanımlıyoruz. Class2 mynesne = new Class2 (); // Bu alanda nesneyi işimize uygun olarak kullanıyoruz. // Daha sonrası için yeniden ihtiyaç duyabilirim diye bir tane Weak referans tanımlıyoruz. WeakReference WkRef = new WeakReference (mynesne); // GC'nin çalıştığı zaman ihtiyaç duyuyorsa işaretçiyi silmesi için referansı kaldırıyoruz. mynesne = null; // Bu alanda başka işlemler yapıyoruz. // Objeye tekrar ihtiyaç duyduk. Burada Weak referansı kullanıyoruz. mynesne = (Class2) WkRef.Target; if (mynesne == null) { //GC aktif olup objeyi kaldırmışsa. mynesne = new Class2 (); //Objeyi yeniden oluştur. Eğer oluşturmazsak objenin öğelerini kullanmaya çalıştığımızda "Object reference not set to an instance of an object." hatasını alırız. else { // GC aktif olmamışsa, obje yok edilmediği için tekrar kullanabiliriz. // Yeni yükleme yaparak yük oluşturmamıza gerek yok. // Artık objeyi tekrar kullanabiliriz. Görüldüğü gibi CLR, çöp toplama sistemiyle, uygulama geliştiricilere önemli kolaylıklar sağlamaktadır. Bizler, kodumuzu yazıp çalışır hale getiririz; derleme, debug ve yürütme işlemlerini de CLR a bırakırız. Bu aşamada CLR ın uygulamamızı güvenli kılması, zararlı etkenlere izin vermemesi ve programımızın bellek üzerinde bıraktığı kirliliği temizliyor olması kadar güzel bir şey olamaz. Papatya Yayıncılık Eğitim

387 Kaynakça Andrew Troelsen, Pro C# 2008 and the.net 3.5 Platform, Apress, 4 th Edition, Asuman Doğaç ve Övgü Öztürk, Turgay Aytaç, XML - Türkiye Bilişim Ansiklopedisi (Başeditörler: Tuncer Ören, Tuncer Üney ve Rifat Çölkesen), Sayfa , Papatya Yayıncılık Eğitim (PYE) ve Türkiye Bilişim Vakfı (TBV), Grady Booch, James Rumbaugh ve Ivar Jacobson, UML - The Unified Modeling Language User Guide, Addison-Wesley Professional, 1 st Edition, Joseph Albahari ve Ben Albahari, C# 3.0 in a Nutshell: A Desktop Quick Reference, O'Reilly Media, 3 rd Edition, Joseph C. Rattz, Pro LINQ: Language Integrated Query in C# 2008, Apress, Nevcihan Duru ve ark., Bilgisayar Mühendisliğine Giriş, Papatya Yayıncılık Eğitim (PYE), 1. Basım, Rifat Çölkesen, Bilgisayar ve Yazılım Mühendisliğinde Veri Yapıları ve Algoritmalar, Papatya Yayıncılık Eğitim (PYE), 5. Basım, Tom Archer, Andrew Whitechapel, Inside C#, Microsoft Press, 2 nd Edition, Turgay Aytaç, UML - Türkiye Bilişim Ansiklopedisi (Başeditörler: Tuncer Ören, Tuncer Üney ve Rifat Çölkesen), Sayfa , Papatya Yayıncılık Eğitim (PYE) ve Türkiye Bilişim Vakfı (TBV),

388 856 C# Programlama Dili Papatya Yayıncılık Eğitim Programlama Kitapları Papatya Yayıncılık, programlama konusunda nitelikli ve özgün birçok kitaba sahiptir. Programlama konusunda yeni yeni kitaplar hazırladığı gibi var olan kitaplarını da güncellemektedir. Programlama dilleri ve bu konudaki gelişmeler Papatya Yayıncılık Eğitim kitaplarıyla yakından izlenebilir. İşte yayınevimizin programlama üzerine kitapları: Yazılım Mühendisliği Dr. Erhan SARIDOĞAN 672 sayfa, 16,5x24 cm 2, 80 gr. 1. hamur kağıt. C Programlama Dili Dr. Rifat ÇÖLKESEN 376 sayfa (11. basım), 16,5x24 cm 2, 80 gr. 1. hamur kağıt. Uygulamalı C Programlama Dili Bora TUNÇER 344 sayfa, 16,5x24 cm 2, 80 gr. 1. hamur kağıt. C++ ve Nesneye Yönelik Programlama Dr. Erhan SARIDOĞAN 416 sayfa, 16,5x24 cm 2, 80 gr. 1. hamur kağıt. JAVA ve Yazılım Tasarımı Altuğ B. ALTINTAŞ 688 sayfa, 18,5x24 cm 2, 80 gr. 1. hamur kağıt. Programlama Sanatı Algoritmalar (C ile) Yazar: Dr. Rifat ÇÖLKESEN Editör: Dr. Cengiz UĞURKAYA 336 sayfa, 16,5x24 cm 2, 80 gr. 1. hamur kağıt. Linux Altında Programlama M. Ali VARDAR 288 sayfa, 16,5x24 cm 2, 80 gr. 1. hamur kağıt. Matlab Kılavuzu Dr. Aslan İNAN 424 sayfa, 18,5x24 cm 2, 80 gr. 1. hamur kağıt. Sistem Analizi ve Tasarımı Prof. Dr. Oya KALIPSIZ ve ark. 192 sayfa, 16,5x24 cm 2, 80 gr. 1. hamur kağıt. Veri Yapıları ve Algoritmalar (Prog. ve Yazılım Müh.) Dr. Rifat ÇÖLKESEN 424 sayfa, 18,5x24 cm 2, 90 gr. 1. hamur kağıt. ve diğerleri için bakınız: Papatya Yayıncılık Eğitim

389 Bilginin Kaynağı 857 Bilgisayar Haberleşmesi ve Ağ Teknolojileri Bilgisayar Ağları ve İnternet Mühendisliği İnternet Teknolojisi ve İntranet Uygulamaları Network Uygulamaları Veri Yapıları ve Algoritmalar Veri Yapıları ve Algoritma Temelleri Programlama Sanatı ve Algoritmalar Algoritma Geliştirme ve Veri Yapıları Veritabanı ve Uygulamaları Veri Madenciliği Yöntemleri Kavram ve Algoritmalarıyla Temel Veri Madenciliği Sistem Analizi ve Tasarımı Java ve Yazılım Tasarımı C Programlama Dili C# Programlama Dili ve Yazılım Tasarımı Yazılım Mühendisliği

390

Veritabanı İşlemleri

Veritabanı İşlemleri Veritabanı İşlemleri Bu bölümde; Veritabanı bağlantısı Komutların Yürütülmesi ADO.NET Nesne Modeli kavramları incelenecektir. ADO.NET (ActiveX Data Objects) ADO.NET, var olan Windows API lerinden çok daha

Detaylı

Asp.Net Veritabanı İşlemleri

Asp.Net Veritabanı İşlemleri Asp.Net Veritabanı İşlemleri Asp.Net Veritabanı İşlemleri Birçok uygulamada bilgiler geçici olarak tutulur ve oturum sonlandırıldığında bu bilgiler bellekten silinir. Ancak etkileşimli web sitelerinde

Detaylı

İNTERNET PROGRAMLAMA 2 A S P. N E T. Marmara Teknik Bilimler MYO / Hafta 5 Veri Tabanı İşlemleri

İNTERNET PROGRAMLAMA 2 A S P. N E T. Marmara Teknik Bilimler MYO / Hafta 5 Veri Tabanı İşlemleri İNTERNET PROGRAMLAMA 2 A S P. N E T Marmara Teknik Bilimler MYO / Hafta 5 Veri Tabanı İşlemleri VERİTABANI BAĞLANTISI Site içindeki bilgilerin saklanması / düzenlenmesi ve kullanıcı etkileşiminin sağlanabilmesi

Detaylı

1-) Veritabanımıza bağlanmak için bir SqlConnection nesnesi, 2-) Veritabanındaki bilgileri kullanmak (seçme, kaydetme, silme, güncelleme) için

1-) Veritabanımıza bağlanmak için bir SqlConnection nesnesi, 2-) Veritabanındaki bilgileri kullanmak (seçme, kaydetme, silme, güncelleme) için Selçuk ÖZKAN 1-) Veritabanımıza bağlanmak için bir SqlConnection nesnesi, 2-) Veritabanındaki bilgileri kullanmak (seçme, kaydetme, silme, güncelleme) için SqlCommand 3-) SqlCommand ın yapacağı işlem için

Detaylı

ADO.NET. Öğr. Gör. Emine TUNÇEL Kırklareli Üniversitesi Pınarhisar Meslek Yüksekokulu

ADO.NET. Öğr. Gör. Emine TUNÇEL Kırklareli Üniversitesi Pınarhisar Meslek Yüksekokulu ADO.NET Öğr. Gör. Emine TUNÇEL Kırklareli Üniversitesi Pınarhisar Meslek Yüksekokulu ADO.NET ve ADO Bir uygulamanın esas gücünü, veri, veritabanları veya veri kaynakları ile olan ilişkisi belirler. Geleneksel

Detaylı

İlk Konsol Uygulamamız 2 İlk Windows Uygulamamız 9.Net Framework Yapısı 18 Neler Öğrendik 19. Veri Tipleri 24 Tanımlı Veri Tipleri 27 Basit Tipler 28

İlk Konsol Uygulamamız 2 İlk Windows Uygulamamız 9.Net Framework Yapısı 18 Neler Öğrendik 19. Veri Tipleri 24 Tanımlı Veri Tipleri 27 Basit Tipler 28 ix 1 İlk Konsol Uygulamamız 2 İlk Windows Uygulamamız 9.Net Framework Yapısı 18 Neler Öğrendik 19 23 Veri Tipleri 24 Tanımlı Veri Tipleri 27 Basit Tipler 28 Kayan Nokta Tipleri 30 Sayısal Veri Tipi Dönüşümleri

Detaylı

«BM364» Veritabanı Uygulamaları

«BM364» Veritabanı Uygulamaları HAFTA 9 İstemci Tarafta DB Erişimi Kodlamak" Yaşar GÖZÜDELİ ygozudeli@verivizyon.com http://blog.verivizyon.com/ygozudeli «BM364» Veritabanı Uygulamaları Konu Akışı ADO.NET Bileşenleri Entity FrameWork

Detaylı

«BM364» Veritabanı Uygulamaları

«BM364» Veritabanı Uygulamaları HAFTA 8 DB içerisinde CLR Bileşenleri" Yaşar GÖZÜDELİ ygozudeli@verivizyon.com http://blog.verivizyon.com/ygozudeli «BM364» Veritabanı Uygulamaları Konu Akışı SQL Server ve.net CLR SQL Server içerisinde

Detaylı

Veri tabanları birbirleriyle ilişkili bilgilerin depolandığı alanlardır. Bilgi artışıyla birlikte bilgisayarda bilgi depolama ve bilgiye erişim konularında yeni yöntemlere ihtiyaç duyulmuştur. Veri tabanları;

Detaylı

Veri Merkezli Uygulamalar Bağlantılı (Connected) Veri Ortamları

Veri Merkezli Uygulamalar Bağlantılı (Connected) Veri Ortamları Veri Merkezli Uygulamalar Bağlantılı (Connected) Veri Ortamları Bağlantılı veri ortamları, uygulamaların veri kaynağına sürekli bağlı kaldığı ortamlardır. Bu ortamlarda veri alma ve değiştirme işlemleri

Detaylı

C Sharp /Veri tabanı işlemleri

C Sharp /Veri tabanı işlemleri C Sharp /Veri tabanı işlemleri C#'ta veri tabanı işlemleri System.Data isim alanındaki ve bu isim alanının altındaki alt isim alanlarındaki türlerle yapılır. System.Data isim alanına programcılar ADO.NET

Detaylı

İnternet Programcılığı

İnternet Programcılığı 1 PHP le Ver tabanı İşlemler Yaptığımız web sitelerinin daha kullanışlı olması için veritabanı sistemleri ile bağlantı kurup ihtiyaca göre verileri okuyup yazmasını isteriz. 1.1 Veritabanı Nedir? Veritabanı

Detaylı

Kepware Veritabanı Ürünleri. Teknolojiye Genel Bir Bakış

Kepware Veritabanı Ürünleri. Teknolojiye Genel Bir Bakış Kepware Veritabanı Ürünleri Teknolojiye Genel Bir Bakış Gündem Veritabanı Client API teknolojisinin gözden geçirilmesi ODBC istemci sürücüsü- bir KEPServerEX Plug-In Haberleşme Sürücüsüdür. DataLogger-

Detaylı

Veritabanı İşlemleri

Veritabanı İşlemleri Veritabanı İşlemleri ADO.NET.Net tabanlı uygulamalar için birincil veriye ulaşım modeli. ADO nun sonraki versiyonu İki kısma ayrılabilir Provider (sağlayıcı) objeleri DataSet objeleri System.Data namespace

Detaylı

Swing ve JDBC ile Database Erişimi

Swing ve JDBC ile Database Erişimi Swing ve JDBC ile Database Erişimi JDBC API, tablolanmış herhangi bir tür veriye, özellikle İlişkisel Veritabanı, erişim sağlayan bir Java API sidir. JDBC, aşağıda verilen üç etkinliğin gerçekleştirilebileceği

Detaylı

Üst Düzey Programlama

Üst Düzey Programlama Üst Düzey Programlama JDBC (Java Database Connectivity) Üst Düzey Programlama-ders07/ 1 JDBC JDBC ilişkisel veritabanlarına erişim için Java dilinde kullanılan standart bir kütüphanedir. Bu kütüphanedeki

Detaylı

Veri Tabanı-I 1.Hafta

Veri Tabanı-I 1.Hafta Veri Tabanı-I 1.Hafta 2010-2011 Bahar Dönemi Mehmet Akif Ersoy Üniversitesi Meslek Yüksekokulu Burdur 2011 Muhammer İLKUÇAR 1 Veri ve Veri Tabanı Nedir? Veri Bir anlamı olan ve kaydedilebilen

Detaylı

VERİ TABANI YÖNETİM SİSTEMLERİ

VERİ TABANI YÖNETİM SİSTEMLERİ VERİ TABANI YÖNETİM SİSTEMLERİ Veri Tabanı Nedir? Sistematik erişim imkânı olan, yönetilebilir, güncellenebilir, taşınabilir, birbirleri arasında tanımlı ilişkiler bulunabilen bilgiler kümesidir. Bir kuruluşa

Detaylı

VERİTABANI Veritabanı Yönetimi

VERİTABANI Veritabanı Yönetimi VERİTABANI Veritabanı Yönetimi YAPILANDIRILMIŞ SORGULAMA DİLİ (SQL) Veritabanı yönetimi, veritabanının yapısal özelliklerini belirtmek ve değiştirmek, veritabanına kullanıcı erişimlerini ve yetkilerini

Detaylı

VERİ TABANI YÖNETİM SİSTEMLERİ

VERİ TABANI YÖNETİM SİSTEMLERİ VERİ TABANI YÖNETİM SİSTEMLERİ ÖĞR.GÖR.VOLKAN ALTINTAŞ 26.9.2016 Veri Tabanı Nedir? Birbiriyle ilişkisi olan verilerin tutulduğu, Kullanım amacına uygun olarak düzenlenmiş veriler topluluğunun, Mantıksal

Detaylı

Veritabanı. Ders 2 VERİTABANI

Veritabanı. Ders 2 VERİTABANI Veritabanı Veritabanı Nedir? Birbiri ile ilişkili verilerin bir arada uzun süreli bulundurulmasıdır. Veritabanı bazen Veritabanı Yönetim sistemi veya Veritabanı Sistemi yerine de kullanılır. Gerçek dünyanın

Detaylı

1 Temel Kavramlar. Veritabanı 1

1 Temel Kavramlar. Veritabanı 1 1 Temel Kavramlar Veritabanı 1 Veri Saklama Gerekliliği Bilgisayarların ilk bulunduğu yıllardan itibaren veri saklama tüm kurum ve kuruluşlarda kullanılmaktadır. Veri saklamada kullanılan yöntemler; Geleneksel

Detaylı

Yaptığımız web sitelerinin daha kullanışlı olması için veritabanı sistemleri ile bağlantı kurup ihtiyaca göre verileri okuyup yazmasını isteriz.

Yaptığımız web sitelerinin daha kullanışlı olması için veritabanı sistemleri ile bağlantı kurup ihtiyaca göre verileri okuyup yazmasını isteriz. 1 PHP ile Veritabanı İşlemleri Yaptığımız web sitelerinin daha kullanışlı olması için veritabanı sistemleri ile bağlantı kurup ihtiyaca göre verileri okuyup yazmasını isteriz. 1.1 Veritabanı Nedir? Veritabanı

Detaylı

SQL veri tabalarına erişmek ve onları kullanmak için geliştirilmiş bir lisandır.

SQL veri tabalarına erişmek ve onları kullanmak için geliştirilmiş bir lisandır. SQL veri tabalarına erişmek ve onları kullanmak için geliştirilmiş bir lisandır. Bu dersimizde biz Microsoft SQL Server veritabanı sistemini kullanmayı öğreneceğiz. SQL Nedir? SQL Structured Query Language

Detaylı

İleri Web Programlama

İleri Web Programlama SAKARYA ÜNİVERSİTESİ İleri Web Programlama Hafta 11 Prof. Dr. Ümit KOCABIÇAK Bu ders içeriğinin basım, yayım ve satış hakları Sakarya Üniversitesi ne aittir. "Uzaktan Öğretim" tekniğine uygun olarak hazırlanan

Detaylı

EĞİTİM : ADO.NET. Bölüm : Veriye Erişim Teknolojileri & SQL Server.Net Veri Sağlayıcısı. Konu : Veri ve Veriye Erişim Teknolojileri

EĞİTİM : ADO.NET. Bölüm : Veriye Erişim Teknolojileri & SQL Server.Net Veri Sağlayıcısı. Konu : Veri ve Veriye Erişim Teknolojileri EĞİTİM : ADO.NET Bölüm : Veriye Erişim Teknolojileri & SQL Server.Net Veri Sağlayıcısı Konu : Veri ve Veriye Erişim Teknolojileri Veri ve Veriye Erişim Teknolojileri Birçok uygulama bazı bilgileri geçici

Detaylı

Arş.Gör.Muhammet Çağrı Gencer Bilgisayar Mühendisliği KTO Karatay Üniversitesi 2015

Arş.Gör.Muhammet Çağrı Gencer Bilgisayar Mühendisliği KTO Karatay Üniversitesi 2015 Arş.Gör.Muhammet Çağrı Gencer Bilgisayar Mühendisliği KTO Karatay Üniversitesi 2015 KONU BAŞLIKLARI 1. Yazılım Mimarisi nedir? 2. Yazılımda Karmaşıklık 3. Üç Katmanlı Mimari nedir? 4. Üç Katmanlı Mimari

Detaylı

Lambda İfadeleri (Lambda Expressions)

Lambda İfadeleri (Lambda Expressions) Lambda İfadeleri (Lambda Expressions) Lambda İfadeleri, değişkenlere değer atamak için kullanılan sadeleştirilmiş anonim (isimsiz) fonksiyonlardır. Bu fonksiyonlar matematikteki ve bilgisayar bilimlerindeki

Detaylı

Veritabanı Uygulamaları Tasarımı

Veritabanı Uygulamaları Tasarımı Veritabanı Uygulamaları Tasarımı Veri Tabanı Veritabanı yada ingilizce database kavramı, verilerin belirli bir düzene göre depolandığı sistemlere verilen genel bir isimdir. Günümüzde özel veya kamu kuruluşların

Detaylı

ADO.NET VERİTABANINA BAĞLANTI. Bir web formu üzerinden veritabanına bağlantımızı anlatacağım. UYGULAMA 1

ADO.NET VERİTABANINA BAĞLANTI. Bir web formu üzerinden veritabanına bağlantımızı anlatacağım. UYGULAMA 1 ADO.NET Web üzerinden veritabanına bağlanmak ve veri işlemleri gerçekleştirmek için ADO.NET teknolojinden faydalanarak işlemlerimizi gerçekleştireceğiz. ADO.NET her ne kadar ADO teknolojine benzesede aslında

Detaylı

LINQ (Temel Kavramlar)

LINQ (Temel Kavramlar) LINQ (Temel Kavramlar) Ele Alınacak Başlıklar Temel Kavramlar Lambda İfadeleri (*Lambda Expressions) Query İfadeleri (*Query Expressions) Tür Çıkarsama (*Type Inference) Anonim Türler (*Anonymous Types)

Detaylı

Bölüm 10: PHP ile Veritabanı Uygulamaları

Bölüm 10: PHP ile Veritabanı Uygulamaları Bölüm 10: PHP ile Veritabanı Uygulamaları -231- Öğr.Gör. Serkan DİŞLİTAŞ 10.1. PHP PHP, platformdan bağımsız sunucu taraflı çalışan betik bir web programlama dilidir. PHP programlama dili ile MySQL, MSSQL,

Detaylı

C# nedir,.net Framework nedir?

C# nedir,.net Framework nedir? 1 C# nedir,.net Framework nedir? C# nedir? C#, C/C++ ve Java dillerinde türetilmiş,bu dillerin dezavantajlarının elenip iyi yönlerinin alındığı, güçlü basit, esnek, tip-güvenli(typesafe,tür dönüşümlerindeki

Detaylı

VeriTabanı Uygulamaları

VeriTabanı Uygulamaları VeriTabanı Uygulamaları Bir uygulamanın esas gücünü, veri, veri tabanları ve veri kaynakları ile olan ilişkisi belirler. Eğer bunlara kolayca hakim olan, yöneten teknolojilere sahipse o uygulama gerçekten

Detaylı

Data Programming SQL Language. Elbistan Meslek Yüksek Okulu Bahar Yarıyılı

Data Programming SQL Language. Elbistan Meslek Yüksek Okulu Bahar Yarıyılı Data Programming SQL Language Elbistan Meslek Yüksek Okulu 2015 2016 Bahar Yarıyılı Öğr.Gör. Murat KEÇECĠOĞLU 15 Mar 2016 1 SQL deyimleri veritabanları üzerinde çeşitli işlemleri yerine getirirler. Veritabanından

Detaylı

1 GİRİŞ 1 C# Hakkında Genel Bilgiler 1.Net Framework 1 CLR 2 CLR Ve CTS 2 Temel Sınıf Kütüphanesi 3 CIL 3 Algoritma Nedir? 4 Sözde Kod (Pseudocode) 5

1 GİRİŞ 1 C# Hakkında Genel Bilgiler 1.Net Framework 1 CLR 2 CLR Ve CTS 2 Temel Sınıf Kütüphanesi 3 CIL 3 Algoritma Nedir? 4 Sözde Kod (Pseudocode) 5 İÇİNDEKİLER IX İÇİNDEKİLER 1 GİRİŞ 1 C# Hakkında Genel Bilgiler 1.Net Framework 1 CLR 2 CLR Ve CTS 2 Temel Sınıf Kütüphanesi 3 CIL 3 Algoritma Nedir? 4 Sözde Kod (Pseudocode) 5 2 VISUAL STUDIO GELİŞTİRME

Detaylı

1 C#.NET GELİŞTİRME ORTAMI 1 Visual Studio 2015 Arayüzü 4 Menu Window 6 Solution Explorer 7 Properties Window 8 Server Explorer 8 Toolbox 9

1 C#.NET GELİŞTİRME ORTAMI 1 Visual Studio 2015 Arayüzü 4 Menu Window 6 Solution Explorer 7 Properties Window 8 Server Explorer 8 Toolbox 9 VII 1 C#.NET GELİŞTİRME ORTAMI 1 Visual Studio 2015 Arayüzü 4 Menu Window 6 Solution Explorer 7 Properties Window 8 Server Explorer 8 Toolbox 9 2 KOD YAZMAYA BAŞLANGIÇ 11.Net Framework 11 Yeni Proje Oluşturmak

Detaylı

ORM & Hibernate. Ahmet Demirelli. SCJP 5.0, SCWCD 1.4 ahmetdemirelli@sabanciuniv.edu

ORM & Hibernate. Ahmet Demirelli. SCJP 5.0, SCWCD 1.4 ahmetdemirelli@sabanciuniv.edu ORM & Hibernate Ahmet Demirelli SCJP 5.0, SCWCD 1.4 ahmetdemirelli@sabanciuniv.edu Sabancı Üniversitesi Bilişim Teknolojileri Yüksek Lisans Programı Seminerleri 2008 Hakkımızda SabancıÜniversitesi BT Yüksek

Detaylı

Öğr. Gör. Serkan AKSU http://www.serkanaksu.net. http://www.serkanaksu.net/ 1

Öğr. Gör. Serkan AKSU http://www.serkanaksu.net. http://www.serkanaksu.net/ 1 Öğr. Gör. Serkan AKSU http://www.serkanaksu.net http://www.serkanaksu.net/ 1 JavaScript JavaScript Nedir? Nestcape firması tarafından C dilinden esinlenerek yazılmış, Netscape Navigator 2.0 ile birlikte

Detaylı

2 VISUAL STUDIO 2012 GELİŞTİRME ORTAMI

2 VISUAL STUDIO 2012 GELİŞTİRME ORTAMI İÇİNDEKİLER VII İÇİNDEKİLER 1 GİRİŞ 1 C# Hakkında Genel Bilgiler 1.NET Framework 1 CLR 2 CLR Ve CTS 2 Temel Sınıf Kütüphanesi 3 CIL 3 Algoritma Nedir? 4 Sözde Kod (Pseudocode) 5 2 VISUAL STUDIO 2012 GELİŞTİRME

Detaylı

İLERİ VERİTABANI SİSTEMLERİ SUAT ÜSTKAN

İLERİ VERİTABANI SİSTEMLERİ SUAT ÜSTKAN 1 AHMET YESEVİ ÜNİVERSİTESİ İLERİ VERİTABANI SİSTEMLERİ ORACLE VERİTABANI KURULUMU VE PL/SQL DEYİMLERİ SUAT ÜSTKAN 102173019 BİLGİSAYAR MÜHENDİSLİĞİ YÜKSEK LİSANS ARALIK 2010 2 İçindekiler 1. Oracle Database

Detaylı

Veri Tabanı-I 1.Hafta

Veri Tabanı-I 1.Hafta Veri Tabanı-I 1.Hafta 2015-2016 Bahar Dönemi Mehmet Akif Ersoy Üniversitesi Teknik Bilimler Meslek Yüksekokulu Burdur 2015 Yrd.Doç.Dr. M. İLKUÇAR 1Muhammer İLKUÇAR, MAKÜ-2011 BURDUR

Detaylı

ELIF KIOTZEOGLOU RESUL MURAD MERT PACOLARI

ELIF KIOTZEOGLOU RESUL MURAD MERT PACOLARI ELIF KIOTZEOGLOU 0510130077 RESUL MURAD 0510120082 MERT PACOLARI 0510120083 SQL SQL,(İngilizce "Structured Query Language", Türkçe: Yapılandırılmış Sorgu Dili) verileri yönetmek ve tasarlamak için kullanılan

Detaylı

İlişkisel Veri Tabanları I

İlişkisel Veri Tabanları I İlişkisel Veri Tabanları I Erdem Alparslan Bahçeşehir Üniversitesi 1 Veri Tabanı Modelleri Veri Tabanları tasarımında kullanılan modeller: Tablolar : Veriler tek bir tabloda veya tablo dizisinde tutulur

Detaylı

5 SQL- Yapısal Sorgulama Dili. Veritabanı 1

5 SQL- Yapısal Sorgulama Dili. Veritabanı 1 5 SQL- Yapısal Sorgulama Dili Veritabanı 1 SQL- Yapısal Sorgulama Dili SQL ifadeleri yapısal olarak üç gruba ayrılır. Veri Tanımlama Dili (DDL - Data Definition Language) Veri İşleme Dili (DML - Data Manipulation

Detaylı

SQL Deyimleri. Öğr.Gör.Volkan ALTINTAŞ Volkanaltintas.com

SQL Deyimleri. Öğr.Gör.Volkan ALTINTAŞ Volkanaltintas.com SQL Deyimleri Öğr.Gör.Volkan ALTINTAŞ Volkanaltintas.com SQL NEDİR? SQL bir veri tabanıyla iletişim kurmak için kullanılır. ANSI standardına göre ilişkisel veri tabanı yönetim sistemlerinin standart dilidir.

Detaylı

Gömülü Sistem Tasarımı. Dr. Deniz TAŞKIN

Gömülü Sistem Tasarımı. Dr. Deniz TAŞKIN Gömülü Sistem Tasarımı Dr. Deniz TAŞKIN PAPATYA YAYINCILIK EĞİTİM Ekim 2012 Bilgisayar Sis. San. ve Tic. A.Ş. Ankara Caddesi, Prof. Fahreddin Kerim Gökay Vakfı İşhanı Girişi, No: 11/3, Cağaloğlu (Fatih)/İstanbul

Detaylı

2 Temel Kavramlar (Devam) Veritabanı 1

2 Temel Kavramlar (Devam) Veritabanı 1 2 Temel Kavramlar (Devam) Veritabanı 1 Veritabanı Kullanıcıları Veritabanı Yöneticisi (DBA-Database Administrator) Tasarım,oluşturma ve işletiminden sorumludur. Görevleri; Tasarımı Performans Analizi Erişim

Detaylı

SQL e Giriş. Uzm. Murat YAZICI

SQL e Giriş. Uzm. Murat YAZICI SQL e Giriş Uzm. Murat YAZICI SQL (Structured Query Language) - SQL Türkçe de Yapısal Sorgulama Dili anlamına gelmektedir ve ilişkisel veritabanlarında çok geniş bir kullanım alanına sahiptir. - SQL ile

Detaylı

Bu uygulamayı yapabilmek için SQL Server'da Query Analyzer kullanabilmekle beraber, ADO.NET bilgisine sahip olmanız gerekir.

Bu uygulamayı yapabilmek için SQL Server'da Query Analyzer kullanabilmekle beraber, ADO.NET bilgisine sahip olmanız gerekir. Ms SQL Server'da Image Veritürü Bölüm Programlama Yazar Öznur KARAKUŞOĞLU Yayın Tarihi 29.08.2005 Okunma Sayısı 1060 Tavsiye Edilen Önhazırlık Veritabanı kavramını öğrenmek. Hedefler Image veri türünün

Detaylı

Bölüm 1: Veritabanı Yönetim Sistemlerine Giriş

Bölüm 1: Veritabanı Yönetim Sistemlerine Giriş Bölüm 1: Veritabanı Yönetim Sistemlerine Giriş -1- Dr. Serkan DİŞLİTAŞ 1.1. Veri ve Bilgi (Data & Information) Hesaplama, saklama gibi çeşitli işlemler amacıyla bilgisayara verilen sayı, yazı, resim, ses,

Detaylı

Maltepe Üniversitesi Bilgisayar Mühendisliği Bölümü Veri Tabanı ve Yönetimi (BİL 301)

Maltepe Üniversitesi Bilgisayar Mühendisliği Bölümü Veri Tabanı ve Yönetimi (BİL 301) Maltepe Üniversitesi Bilgisayar Mühendisliği Bölümü Veri Tabanı ve Yönetimi (BİL 301) GENEL DERS BİLGİLERİ Öğretim Elemanı : Öğr. Gör. Erdal GÜVENOĞLU Ofis : MUH 313 Ofis Saatleri : Pazartesi: 10.00-12.00,

Detaylı

İngilizce'de Relational Database Management System (RDBMS) olarak ifade edilir.

İngilizce'de Relational Database Management System (RDBMS) olarak ifade edilir. İlişkisel Veritabanı Yaklaşımı: İngilizce'de Relational Database Management System (RDBMS) olarak ifade edilir. İlişkisel veri tabanı yönetim sistemi verilerin tablolarda satır ve sutunlar halinde tutulduğu

Detaylı

Cybersoft Bilişim Teknolojileri Sunucu Tarafı Programlaması Kursu Final soruları. Tarih: 27 Kasım 2010 Saat: 13:30 Süre: 3 saat

Cybersoft Bilişim Teknolojileri Sunucu Tarafı Programlaması Kursu Final soruları. Tarih: 27 Kasım 2010 Saat: 13:30 Süre: 3 saat Cybersoft Bilişim Teknolojileri Sunucu Tarafı Programlaması Kursu Final soruları. Tarih: 27 Kasım 2010 Saat: 13:30 Süre: 3 saat 1. Kısım Çoktan Seçmeli (48 puan) 1) Aşağıdaki JAVA kod parçası çalıştırıldığında

Detaylı

LINQ Language Integrated Query Dille Bütünleştirilmiş Sorgu Bir Veri Tabanı Tablosundan Veri Gösterme

LINQ Language Integrated Query Dille Bütünleştirilmiş Sorgu Bir Veri Tabanı Tablosundan Veri Gösterme LINQ Language Integrated Query Dille Bütünleştirilmiş Sorgu Bir Veri Tabanı Tablosundan Veri Gösterme Amaç: Bir veritabanındaki bilgileri LINQ aracılığı ile ekranda nasıl gösterebiliriz? Yazar: Oğuz Alpöge,

Detaylı

MOBİL UYGULAMA GELİŞTİRME

MOBİL UYGULAMA GELİŞTİRME MOBİL UYGULAMA GELİŞTİRME PELİN YILDIRIM FATMA BOZYİĞİT YZM 3214 Celal Bayar Üniversitesi Hasan Ferdi Turgutlu Teknoloji Fakültesi Bu Derste Veri Saklama 2 Veri Saklama Veri Saklama her appnin ihtiyaci

Detaylı

Ders Tanıtım Sunumu. Database Managegement II. Elbistan Meslek Yüksek Okulu Güz Yarıyılı. Öğr. Gör. Murat KEÇECĠOĞLU

Ders Tanıtım Sunumu. Database Managegement II. Elbistan Meslek Yüksek Okulu Güz Yarıyılı. Öğr. Gör. Murat KEÇECĠOĞLU Ders Tanıtım Sunumu Database Managegement II Elbistan Meslek Yüksek Okulu 2016 2017 Güz Yarıyılı Öğr. Gör. Murat KEÇECĠOĞLU 08 Eyl. 2016 SQL Server 2008 veritabanlarının grafiksel arayüzden yönetimi ve

Detaylı

Veritabanı sistemlerinde veri bütünlüğünü sağlayabilmek için CONSTRAINTS olarak adlandırılan bazı zorlayıcı ifadeler kullanılabilir.

Veritabanı sistemlerinde veri bütünlüğünü sağlayabilmek için CONSTRAINTS olarak adlandırılan bazı zorlayıcı ifadeler kullanılabilir. VERİ BÜTÜNLÜĞÜ VTYS lerde veri bütünlüğünü sağlamanın iki temel yolu vardır; Tanımlanabilir veri bütünlüğü ve prosedürel veri bütünlüğü. Tanımlanabilir veri bütünlüğü, tanımlanan nesnelerin kendi özellikleri

Detaylı

-- işareti tek satırlık açıklamalarda kullanılır. Açıklama olarak yazılan satırın önüne konulması yeterlidir.

-- işareti tek satırlık açıklamalarda kullanılır. Açıklama olarak yazılan satırın önüne konulması yeterlidir. T-SQL KODLARİ İÇERİSİNE AÇIKLAMA EKLEME Bir veya daha fazla satırın çalıştırılmasını Önlemek için veya /*... */" ifadeleri kullanılır. -- işareti tek satırlık açıklamalarda kullanılır. Açıklama olarak

Detaylı

SAKLI YORDAM (Stored Procedure) Sibel Somyürek

SAKLI YORDAM (Stored Procedure) Sibel Somyürek SAKLI YORDAM (Stored Procedure) Sibel Somyürek Saklı Yordam Saklı yordamlar veritabanı kataloğunda saklanan SQL kod bloklarının tanımlanmasıdır. Mesela, iki sayı alıp bunların toplamlarını hesaplayan bir

Detaylı

HASTANE OTOMASYONU VERİ TABANI YÖNETİM SİSTEMLERİ TEMEL VERİTABANI KAVRAMLARI

HASTANE OTOMASYONU VERİ TABANI YÖNETİM SİSTEMLERİ TEMEL VERİTABANI KAVRAMLARI VERİ TABANI YÖNETİM SİSTEMLERİ HASTANE OTOMASYONU Öğr. Gör. Handan ÇETİNKAYA İstanbul Gelişim Üniversitesi Günümüzde en basitinden en karmaşığına kadar pek çok veritabanı mevcuttur. En basiti Microsoft

Detaylı

VERİTABANI VERİTABANIN AVANTAJLARI ÖZET

VERİTABANI VERİTABANIN AVANTAJLARI ÖZET ÖZET NEDİR? İYİ BİR NIN ÖZELLİKLERİ NIN AVANTAJLARI VERİ TABANI TİPLERİ ÇEŞİTLERİ HANGİ NI KULLANMALIYIZ? NEDİR? Veritabanı düzenli bilgiler topluluğudur. Veritabanı basit olarak bilgi depolayan bir yazılımdır.

Detaylı

Veri Tabanı Tasarım ve Yönetimi

Veri Tabanı Tasarım ve Yönetimi SAKARYA ÜNİVERSİTESİ Veri Tabanı Tasarım ve Yönetimi Hafta 5 Prof. Dr. Ümit KOCABIÇAK Bu ders içeriğinin basım, yayım ve satış hakları Sakarya Üniversitesi ne aittir. "Uzaktan Öğretim" tekniğine uygun

Detaylı

Veritabanı Yönetim Sistemleri (Veritabanı Kavramı) Veri Modelleri

Veritabanı Yönetim Sistemleri (Veritabanı Kavramı) Veri Modelleri Veritabanı Yönetim Sistemleri (Veritabanı Kavramı) Veri Modelleri Konular Veritabanı Tasarım Aşamaları Veri Modeli Nedir? Veri Modeli Temel Bileşenleri İş Kuralları (Business Rules) İş Kurallarını Veri

Detaylı

2-Veritabanı Yönetim Sistemleri/ Temel Kavramlar

2-Veritabanı Yönetim Sistemleri/ Temel Kavramlar 2-Veritabanı Yönetim Sistemleri/ Temel Kavramlar Öğr. Gör. Saliha Kevser KAVUNCU Veritabanı neden kullanılır? Veritabanının amacı; insanların ve organizasyonların birşeyleri takip edebilmesine yardımcı

Detaylı

ASP.NET CLASS KULLANARAK VERİTABANI İŞLEMLERİ

ASP.NET CLASS KULLANARAK VERİTABANI İŞLEMLERİ ASP.NET CLASS KULLANARAK VERİTABANI İŞLEMLERİ Asp.NET mimarisinin temelini oluşturan CLASS yapısını kullanarak veri tabanı işlemlerini nasıl daha az kodla ve daha stabil yapabiliriz bunu göreceğiz. Mevzu

Detaylı

"SQL Server Management Studio" yazılımını yüklemek için alttaki resmi sitesinden 180 günlük deneme sürümünü indirebilirsiniz.

SQL Server Management Studio yazılımını yüklemek için alttaki resmi sitesinden 180 günlük deneme sürümünü indirebilirsiniz. Microsoft SQL Server 2008 R2 Kurulumu "SQL Server Management Studio" yazılımını yüklemek için alttaki resmi sitesinden 180 günlük deneme sürümünü indirebilirsiniz. http://www.microsoft.com/sqlserver/en/us/get-sql-server/try-it.aspx

Detaylı

ADO.NET nesne modeli iki ana bölümden oluşmaktadır.

ADO.NET nesne modeli iki ana bölümden oluşmaktadır. ADO.NET ADO (ActiveX Data Objects), farklı veri kaynaklarına hızlı ve güvenli erişim için Microsoft tarafından geliştirilen nesne modelidir. ADO.NET ise ADO teknolojisinin en yeni versiyonudur. ADO ile

Detaylı

SORGULAR VE ÇEŞİTLERİ II

SORGULAR VE ÇEŞİTLERİ II Ünite 8 SORGULAR VE ÇEŞİTLERİ II Öğr. Gör. Kemal ÖZCAN Bu ünitede, Sorgular ve Çeşitleri konusu ele alınacaktır. Yapılacaklar Burada yazılan SQL ifadeleri MS SQL SERVER, ORACLE ve MySQL veritabanı sistemlerinin

Detaylı

PROGRAMLAMAYA GİRİŞ. Öğr. Gör. Ayhan KOÇ. Kaynak: Algoritma Geliştirme ve Programlamaya Giriş, Dr. Fahri VATANSEVER, Seçkin Yay.

PROGRAMLAMAYA GİRİŞ. Öğr. Gör. Ayhan KOÇ. Kaynak: Algoritma Geliştirme ve Programlamaya Giriş, Dr. Fahri VATANSEVER, Seçkin Yay. PROGRAMLAMAYA GİRİŞ Öğr. Gör. Ayhan KOÇ Kaynak: Algoritma Geliştirme ve Programlamaya Giriş, Dr. Fahri VATANSEVER, Seçkin Yay., 2007 Algoritma ve Programlamaya Giriş, Ebubekir YAŞAR, Murathan Yay., 2011

Detaylı

DİL VE İLETİŞİM. Prof. Dr. V. Doğan GÜNAY

DİL VE İLETİŞİM. Prof. Dr. V. Doğan GÜNAY DİL VE İLETİŞİM Prof. Dr. V. Doğan GÜNAY DİL VE İLETİŞİM Prof. Dr. V. Doğan GÜNAY PAPATYA YAYINCILIK EĞİTİM Bilgisayar Sis. San. ve Tic. A.Ş. Ankara Caddesi, Prof. Fahreddin Kerim Gökay Vakfı İşhanı Girişi,

Detaylı

VERİ TABANI İŞLEMLERİ (NESNE TABANLI PROGRAMLAMA TEKNİĞİ İLE)

VERİ TABANI İŞLEMLERİ (NESNE TABANLI PROGRAMLAMA TEKNİĞİ İLE) VERİ TABANI İŞLEMLERİ (NESNE TABANLI PROGRAMLAMA TEKNİĞİ İLE) ACCESS VERİ TABANI (OLE DB - Object Linking and Embedding Data Base) Access veri tabanı kullanılarak oluşturulmuş uygulama; OLEDB çalışmak

Detaylı

Uzaktan Eğitim Uygulama ve Araştırma Merkezi

Uzaktan Eğitim Uygulama ve Araştırma Merkezi JAVA PROGRAMLAMA Öğr. Gör. Utku SOBUTAY İÇERİK 2 Java da Fonksiyon Tanımlamak Java da Döngüler Java da Şart İfadeleri Uygulamalar Java da Fonksiyon Tanımlamak JAVA DA FONKSİYON TANIMLAMAK 4 Fonksiyonlar;

Detaylı

Microsoft SQL Server Sorgulama

Microsoft SQL Server Sorgulama Microsoft SQL Server Sorgulama Eğitim Takvimi Tarih Eğitim Süresi Lokasyon 26 Kasım 2018 3 Gün Bilginç IT Academy Eğitim Detayları Eğitim Süresi : 3 Gün Kontenjan : 12 Ön Koşullar : Herhangi bir ön koşul

Detaylı

MAT213 BİLGİSAYAR PROGRAMLAMA I DERSİ Ders 1: Programlamaya Giriş

MAT213 BİLGİSAYAR PROGRAMLAMA I DERSİ Ders 1: Programlamaya Giriş MAT213 BİLGİSAYAR PROGRAMLAMA I DERSİ Ders 1: Programlamaya Giriş Yard. Doç. Dr. Alper Kürşat Uysal Bilgisayar Mühendisliği Bölümü akuysal@anadolu.edu.tr Ders Web Sayfası: http://ceng.anadolu.edu.tr/ders.aspx?dersid=101

Detaylı

AOSB 2017 EĞİTİM PROGRAMI

AOSB 2017 EĞİTİM PROGRAMI Eğitimin Konusu : Makro Excel Eğitim Tarihi : 04-05-10-11-12 Mayıs 2017 Eğitim Hedef Kitlesi : Excel kulllanıcıları arasında pratiklik ve hız kazanmış, Excel fonksiyonları, Veri Analizi araçlarını kullanma

Detaylı

OPC Data Access (DA) Temelleri

OPC Data Access (DA) Temelleri OPC Data Access (DA) Temelleri Hazırlayan Kepware Technologies Türkçe Meal Salih GÖK Anket Data Access nedir? Data Access in getirileri OPC DA e giriş (Data Access) OPC DA Özelliklerine bakış Hızlı bir

Detaylı

Masa üstünde vt34.mdb dosyası var, onu projemize eklemek için, App_Data ya sağ tıkla Add Existing Item vt34.mdb adlı dosyayı seç Add

Masa üstünde vt34.mdb dosyası var, onu projemize eklemek için, App_Data ya sağ tıkla Add Existing Item vt34.mdb adlı dosyayı seç Add 29 Aralık 2011 / Perşembe Visual studio dan veri tabanından veri çekmek için önce bir aspx dosyası açıp,,accessde yeni veri tabanı dosyası açıyoruz. Dikkat : Dosyanın uzantısı.mdb olacak. Masa üstünde

Detaylı

Veritabanı Tasarımı. Sütun Değerlerini Güncelleme ve Satırları Silme

Veritabanı Tasarımı. Sütun Değerlerini Güncelleme ve Satırları Silme Veritabanı Tasarımı Sütun Değerlerini Güncelleme ve Satırları Silme Konular UPDATE komutunu oluşturmak ve çalıştırmak DELETE komutunu oluşturmak ve çalıştırmak Tabloda güncelleme yapmak ya da veri silmek

Detaylı

Microsoft SQL Server 2008 Oracle Mysql (Ücretsiz) (Uygulamalarımızda bunu kullanacağız) Access

Microsoft SQL Server 2008 Oracle Mysql (Ücretsiz) (Uygulamalarımızda bunu kullanacağız) Access Programlamaya Giriş VERİ TABANI UYGULAMASI ÖN BİLGİ Veritabanları, verilere sistematik bir şekilde erişilebilmesine, depolanmasına ve güncellenmesine izin veren, yüksek boyutlu veriler için çeşitli optimizasyon

Detaylı

Modül 2: Veri Merkezli Uygulamalar ve ADO.NET e Giriş

Modül 2: Veri Merkezli Uygulamalar ve ADO.NET e Giriş 34 Modül 2: Veri Merkezli Uygulamalar ve ADO.NET e Giriş Bu modülde verilerin hangi ortamlarda depolandığını öğreneceksiniz. Ayrıca depolanan veriye erişmek için kullanılan yöntemleri öğrenecek ve ADO.NET

Detaylı

SQL'e Giriş. SELECT Deyimi. SQL Komutları. Yardımcı Deyimler

SQL'e Giriş. SELECT Deyimi. SQL Komutları. Yardımcı Deyimler SQL'e Giriş SQL komutları kullanılarak aşağıdaki işlemler yapılabilir: Veritabanı nesnelerinin oluşturulması ve bu nesnelerle ilgili işlemlerin yapılması Bilgilerin istenilen koşullara göre görüntülenmesi

Detaylı

Yazılım Nedir? 2. Yazılımın Tarihçesi 3. Yazılım Grupları 4 Sistem Yazılımları 4 Kullanıcı Yazılımları 5. Yazılımın Önemi 6

Yazılım Nedir? 2. Yazılımın Tarihçesi 3. Yazılım Grupları 4 Sistem Yazılımları 4 Kullanıcı Yazılımları 5. Yazılımın Önemi 6 ix Yazılım Nedir? 2 Yazılımın Tarihçesi 3 Yazılım Grupları 4 Sistem Yazılımları 4 Kullanıcı Yazılımları 5 Yazılımın Önemi 6 Yazılımcı (Programcı) Kimdir? 8 Yazılımcı Olmak 9 Adım Adım Yazılımcılık 9 Uzman

Detaylı

Veritabanı Tasarımı. Tablo Oluşturma

Veritabanı Tasarımı. Tablo Oluşturma Veritabanı Tasarımı Tablo Oluşturma Konular Ana veritabanı nesnelerini listeleme ve kategorize etme Bir tablo yapısını inceleme Şema nesnelerinin Oracle veritabanı tarafından nasıl kullanıldığını açıklama

Detaylı

Genel Kavramlar. Bilgisayar ortamında işlenebilecek durumda bulunan kayıtlar. Birbiri ile ilişkili veriler topluluğu ve veriler arası ilişkiler

Genel Kavramlar. Bilgisayar ortamında işlenebilecek durumda bulunan kayıtlar. Birbiri ile ilişkili veriler topluluğu ve veriler arası ilişkiler Genel Kavramlar Veri Nedir? Bilgisayar ortamında işlenebilecek durumda bulunan kayıtlar Veri Tabanı Nedir? Birbiri ile ilişkili veriler topluluğu ve veriler arası ilişkiler Veritabanı Yönetim Sistemi (DBMS)Nedir?

Detaylı

PAPERWORK TEKNİK MİMARİ

PAPERWORK TEKNİK MİMARİ PAPERWORK ECM TEKNİK MİMARİ 1. Şekilde (1) numara ile gösterilen Content Server adı verilen Uygulama Sunucusudur. Content Server tüm iş mantığını içerir. Veri Tabanına ve arşivlenen belgelere erişim yetkisi

Detaylı

1 PROGRAMLAMAYA GİRİŞ

1 PROGRAMLAMAYA GİRİŞ İÇİNDEKİLER IX İÇİNDEKİLER 1 PROGRAMLAMAYA GİRİŞ 1 Problem Çözme 1 Algoritma 1 Algoritmada Olması Gereken Özellikler 2 Programlama Dilleri 6 Programlama Dillerinin Tarihçesi 6 Fortran (Formula Translator)

Detaylı

Oracle da kullanılan veri tipleri:

Oracle da kullanılan veri tipleri: ORACLE A GİRİŞ Oracle ile SQL Server ı karşılaştıralım, 1 Oracle da veritabanı yerine kullanıcı oluşturulur. Kullanıcılar veritabanı gibi davranır. 2 Tablo oluşturma, yapısını değiştirme, silme kodları

Detaylı

BLG4134 Görsel Programlama III. Öğr. Grv. Aybike ŞİMŞEK

BLG4134 Görsel Programlama III. Öğr. Grv. Aybike ŞİMŞEK BLG4134 Görsel Programlama III Öğr. Grv. Aybike ŞİMŞEK CV_EKLE isimli bir veritabanı oluşturun. CV isimli tabloyu aşağıdaki şekilde oluşturun. Aşağıdaki kod ile bir stored procedure oluşturun. Bunun için

Detaylı

Veri Tabanı Hafta Dersi

Veri Tabanı Hafta Dersi Veri Tabanı - 1 7. Hafta Dersi Dersin Hedefleri SQL Yapısal Sorgulama Dili Veri Tanımlama Dili (DDL) Create Alert Drop Veri tanımlama dili verinin ne olduğundan çok verinin tipi ile ilgilenir. Veri tabanı

Detaylı

Yukarıdakilerden hangileri DML (Data Manipulation Language) ile gerçekleştirilir?

Yukarıdakilerden hangileri DML (Data Manipulation Language) ile gerçekleştirilir? 1) I. Tablo Oluşturma II. Veri Güncelleme III. Veri Silme IV. Veri Ekleme V. Tablo Silme Yukarıdakilerden hangileri DML (Data Manipulation Language) ile gerçekleştirilir? a) I, IV ve V b) II, III ve IV

Detaylı

Veri Tabanı Yönetim Sistemleri Bölüm - 3

Veri Tabanı Yönetim Sistemleri Bölüm - 3 Veri Tabanı Yönetim Sistemleri Bölüm - 3 İçerik Web Tabanlı Veri Tabanı Sistemleri.! MySQL.! PhpMyAdmin.! Web tabanlı bir veritabanı tasarımı. R. Orçun Madran!2 Web Tabanlı Veritabanı Yönetim Sistemleri

Detaylı

ENF102 TEMEL BİLGİSAYAR BİLİMLERİ VE C/ C++ PROGRAMLAMA DİLİ. Gazi Üniversitesi Mühendislik Fakültesi Bilgisayar Mühendisliği Bölümü

ENF102 TEMEL BİLGİSAYAR BİLİMLERİ VE C/ C++ PROGRAMLAMA DİLİ. Gazi Üniversitesi Mühendislik Fakültesi Bilgisayar Mühendisliği Bölümü ENF102 TEMEL BİLGİSAYAR BİLİMLERİ VE C/ C++ PROGRAMLAMA DİLİ Gazi Üniversitesi Mühendislik Fakültesi Bilgisayar Mühendisliği Bölümü Konular Giriş Data Hiyerarşisi Files (Dosyalar) ve Streams (Kaynaklar)

Detaylı

Bölüm 4: DDL Veri Tanımlama Dili

Bölüm 4: DDL Veri Tanımlama Dili Bölüm 4: DDL Veri Tanımlama Dili -43- Dr. Serkan DİŞLİTAŞ DDL (Data Definition Language Veri Tanımlama Dili : Bu kategorideki SQL komutları ile veritabanları, tablo, görünüm ve indekslerin yaratılması,

Detaylı

TESİ. indeks. söylenebilir?? bir ilişkidir d) Hiçbiri. veya somutlaştırılmış. düzeyidir? sağlayabilir? sına. d) Hepsi. olabilir? c) Verilerin d) Hepsi

TESİ. indeks. söylenebilir?? bir ilişkidir d) Hiçbiri. veya somutlaştırılmış. düzeyidir? sağlayabilir? sına. d) Hepsi. olabilir? c) Verilerin d) Hepsi 1. 2. 3. 4. 5. 6. Görünüm (view) için özellikle aşağıdakilerden hangisi söylenebilir?? a) Veritabanındaki kayıtlı verileri düzenlemek, yönetmek ve elde etmek için kullanılan bir dildir b) Bir ilişkinin

Detaylı

Eclipse, Nesneler ve Java 2 Java Nereden Çıktı? 2

Eclipse, Nesneler ve Java 2 Java Nereden Çıktı? 2 1 Eclipse, Nesneler ve Java 2 Java Nereden Çıktı? 2 Eclipse Mimarisi 4 Java Teknolojisine Genel Bir Bakış 6 Taşınabilirlik 6 Java Derleyicisi ve Bytecode 6 Java Sanal Makinası (Java Virtual Machine - JVM)

Detaylı

Rahman USTA Editör Altuğ B. ALTINTAŞ Papatya Yayıncılık Eğitim

Rahman USTA Editör Altuğ B. ALTINTAŞ Papatya Yayıncılık Eğitim JAVA Mimarisiyle Kurumsal Çözümler Kurumsal JAVA Rahman USTA Editör Altuğ B. ALTINTAŞ Papatya Yayıncılık Eğitim İstanbul, Ankara, İzmir, Adana PAPATYA YAYINCILIK EĞİTİM Ekim 2012 Bilgisayar Sis. San. ve

Detaylı

4. Bölüm Programlamaya Giriş

4. Bölüm Programlamaya Giriş 4. Bölüm Programlamaya Giriş Algoritma ve Programlamaya Giriş Dr. Serkan DİŞLİTAŞ 4.1. C# ile Program Geliştirme Net Framework, Microsoft firması tarafından açık internet protokolleri ve standartları

Detaylı

PAPATYA YAYINCILIK EĞİTİM Bilgisayar Sis. San. ve Tic. A.Ş. Veri Madenciliği Yöntemleri Dr. Yalçın ÖZKAN -II-

PAPATYA YAYINCILIK EĞİTİM Bilgisayar Sis. San. ve Tic. A.Ş. Veri Madenciliği Yöntemleri Dr. Yalçın ÖZKAN -II- Dr. Yalçın ÖZKAN Dr. Yalçın ÖZKAN PAPATYA YAYINCILIK EĞİTİM Bilgisayar Sis. San. ve Tic. A.Ş. Ankara Caddesi, Prof. Fahreddin Kerim Gökay Vakfı İşhanı Girişi, No: 11/3, Cağaloğlu (Fatih)/İstanbul Tel

Detaylı

VERİTABANI ORGANİZASYONU

VERİTABANI ORGANİZASYONU VERİTABANI ORGANİZASYONU Veri nedir? Olgu, kavram ya da komutların, iletişim, yorum ve işlem için elverişli biçimsel gösterimidir. Veriler ölçüm, sayım, deney, gözlem ya da araştırma yolu ile elde edilmektedir.

Detaylı