Makale Özeti

Taban Sınıf & Factorylerin Kullanımı. Aykut TAŞDELEN

Makale

ADO .NET 2.0 İle Gelen Yenilikler -2

Taban Sınıflar ve Factorylerin Kullanımı

ADO .NET in selefi durumundaki veri erişim teknolojilerinde (ADO, RDO gibi) providerların tasarımı, arayüz kullanımına dayandırılmıştı. Böylece uygulama programcılarının, kütüphane içerisindeki belirli bir arayüzü kullanarak, veri tabanı üzerinde istedikleri işlemi yapabilmeleri mümkün oluyordu. Sözgelimi programcı, aynı Connection arayüzünü kullanarak, hem SQL Server hem de Oracle veri tabanına bağlanabiliyordu.   

ADO .NETin tasarımı ise, veri tabanına özgü yazılmış farklı managed providerlara ilişkin sınıfları öngörmektedir. Performans kazanımı elde etme amacı ile tasarlanmış bu yapı, beraberinde kaçınılmaz olarak sınıf isimlerindeki artışı da getirmiştir. Bu nedenle ADO .NET, (özellikle ADO kullanımına alışık programcılar tarafından) sıklıkla eleştirilmektedir. Zira programcı, SQL Servera erişmek istediğinde farklı isimdeki bir Connection sınıfını, Oraclea erişmek istediğinde ise yine farklı isimdeki bir Connection sınıfını kullanmak durumunda kalmaktadır. Bu rijidite, özellikle kullanılan veritabanının değişimi halinde yazılan kodlarda da zorunlu bir değişimi gerektirmektedir. Oysa çoğu zaman programcıların beklentisi; farklı durumlara kolay adapte olabilen, elastik yapıları kodlayabilecekleri araçlar istikametindedir.

Gerçekte ADO.NETteki managed providerlara ilişkin pek çok sınıf, yine birtakım ortak arayüzlerden implemente edilmiştir. Bu nedenle sözkonusu sınıflar bu arayüzlerden aldıkları ortak üyelerin yanı sıra, veri tabanına özgü bir takım üyeleri de içerebilmektedirler. Örneğin tüm Connection sınıfları aslında IDBConnection arayüzünden implemente edilmişlerdir. Gerek SqlConnection sınıfı gerekse de OracleConnection sınıfı IDBConnection arayüzündeki ConnectionString propertysini implemente etmektedir. Bunun yanı sıra SqlConnection sınıfında var olan Database ve ChangeDatabase() isimli üyeler, OracleConnection sınıfında bulunmamaktadır. Çünkü Oracle, MS Sql Server gibi aynı instance içerisinde birden fazla veri tabanı bulundurma özelliğini desteklememektedir. Benzer durumlar kütüphanedeki diğer sınıflar için de geçerlidir.   

Bu durum, jenerik kodlama yapmak isteyen programcılara alternatif bir hareket tarzını sunar niteliktedir.  Programcılar ADO.NETte jenerik kodlama amacı ile (System.Data namespaceindeki) arayüzleri kullanarak çalışabilirler. Ancak bu işlem için standardize edilmiş bir yöntem bulunmamaktadır.

Taban Sınıflar

ADO.NET kütüphanesine de zaman içerisinde birtakım eklentilerin yapılması kaçınılmaz olmuştur. Sözgelimi 1.1 versiyonuna, DataReader sınıfına daha önceden varolmayan HasRows isimli bir property eklenmiştir. Türetme mekanizması arayüzlerle sağlanan kütüphanelerde yapılacak bu tür eklentiler ya yeni arayüzlerin yaratılmasını ya da taban sınıfların kullanımını gerektirmektedir. Taban sınıf kullanımı (multiple inheritance özelliği olmaması nedeni ile) bazı kısıtlamalara neden olsa da, gelecekte çok fazla yeni özelliğin eklenmesinin muhtemel olduğu kütüphanelerde önerilen bir yöntemdir.

ADO.NETin önceki versiyonlarında DataAdapter gibi birkaç istisna haricindeki diğer sınıflar için türetme mekanizması, taban sınıflarla değil arayüzlerle gerçekleştirilmektedir. Ancak ADO.NET 2.0 da türetme mekanizması tümüyle taban sınıf kullanımına dayandırılmıştır. Sözü edilen taban sınıflar System.Data.Common namespacei içerisinde bulunmaktadır.

Dikkat : ADO.NET 2.0 da da geriye uyumluluğun olabilmesi için ortak arayüzler hala kullanılmaktadır. Bu nedenle yazılım geliştiricilerin geriye uyumluluk noktasında, herhangi bir çekinceleri olmamalıdır !

Bu sınıflarla ilgili vurgulanması gereken önemli bir husus ta; tümünün soyut (abstract) sınıf olması nedeni ile kendilerinden doğrudan nesne yaratılamayışıdır. Bu duruma, uygulama programcısının perspektifinden bakılacak olursa, soyut taban sınıfların, "kullanışlılık açısından" yeterince iyi olmadığı eleştirisi yapılabilir. 

Provider Factoryler

Arayüz tabanlı yaklaşımın beraberinde gelen komplikasyonlardan birisi de, beklentisi spesifik bir nesne örneği yaratmak programcının arayüz üzerinden bir başlangıç fonksiyonu (constructor) çağıramamasıdır. Bu sorunun ADO teknolojisindeki çözümü; registrydeki herbir providera ilişkin ProgID değerinin kullanımına dayanır. ADO.NET 2.0 da ise; machine.config dosyası, sistemdeki providerlara ilişkin çeşitli bilgileri barındırmaktadır. Örneğin :

<system.data>
 <DbProviderFactories>
<add name="SqlClient Data Provider" 
 invariant="System.Data.SqlClient" 
 support="FF" 
 description=".Net Framework Data Provider for SqlServer"   
 type="System.Data.SqlClient.SqlClientFactory, System.Data,  
  Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
  </DbProviderFactories>
</system.data>

Programcı hangi providerı kullanmak istediğini bu dosyadaki bilgileri kullanarak belirtebilir. System.Data.Common namespacei içerisinde bulunan,
DbProviderFactories isimli sınıfın içerdiği statik fonksiyonlar; bu dosyadaki bilgileri alma amacı ile kullanılmaktadır. Örneğin GetFactoryClasses()
isimli statik metot; machine.config içerisindeki provider bilgilerini, bir DataTable referansı biçiminde döndürür.

Keza GetFactory() isimli metot ise; parametresine aldığı, (provider ismine ilişkin) invariant name bilgisine uygun, DbProviderFactory referansı
döndürür. Programcı bu referans değeri üzerinden CreateConnection() metodunu çağırmak sureti ile, daha sonra "provider bağımsız tasarlanmış" DbConnection sınıfı türünde nesne yaratabilir. Örneğin :

DbProviderFactory pf = DbProviderFactories.GetFactory("System.Data.SqlClient");
DbConnection cnn = pf.CreateConnection();
cnn.ConnectionString = "data source=localhost; initial catalog=northwind; integrated security=true";
cnn.Open();

DbCommand cmd = cnn.CreateCommand(); 
cmd.CommandText = "Select * from orders";
DbDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection); 
DataTable tbl = new DataTable ("Orders"); 
tbl.Load(dr);
dataGridView1.DataSource = tbl;

GetFactory() metoduna geçilen parametre; SqlClient providerına ilişkin "invariant name" bilgisidir. Metodun döndürdüğü değer; DbProviderFactory
türündeki nesne referansıdır. Bu referans değeri, pf isimli değişkende saklanmış ve CreateConnection() ile provider bağımsız tasarlanmış olan
DbConnection sınıfı türünde bir nesne yaratılabilmiştir.

Hatırlatma : machine.config içerisindeki bilgiler, uygulamaya özgü konfigürasyon dosyalarında override edilebilir. 


Aykut TAŞDELEN

VB.NET MVP

Kaynaklar : .NET Framework SDK 2.0
                  msdn.microsoft.com