Makale Özeti

Bu makalemizde veritabanından alınan verileri uygulama ortamında sakladığımız DataTable nesnesi üzerinde filtreleme ve sıralama gibi işlemleri yapmamızı sağlayan iki önemli metodu derinlemesine incelemeye çalışacağız.

Makale

Geliştirdiğimiz uygulamalarda veritabanı ile ilgili işlemleri sıklıkla yapmaktayız. Veritabanına bağlanmak, verileri seçip uygulama ortamında aktarmak ve bu verileri uygulamayı kullanacak kişilerin istediğine göre sunmak, programcı olarak bizlerin en rutin işlemlerinden biridir. Tabi ki bu tip işlemleri yaparken esas alacağımız kurallardan biri de uygulamanın performanslı bir şekilde çalışmasını sağlamaktır. Bu yazımızda da veritabanından çektiğimiz verileri uygulama ortamında sakladığımız DataTable nesnesi üzerinde verileri filtreleme ve sıralama işlemlerini yapmamızı sağlayan Find ve Select isimli iki metodu detaylı bir şekilde irdeleyeceğiz.

Dilerseniz şöyle bir senaryo ile konumuza giriş yapalım. Veritabanında ürünlerimizin bulunduğu tablodan verileri getirerek uygulama içerisinde kullanıyoruz. Kullanıcı, form üzerinde bulunan kontroller aracılığıyla aynı veri üzerinde özel kriterler belirterek veri seçme işlemleri gerçekleştiriyor.(Sadece istenilen kriterdeki kayıtları getir, istenilen kriterdeki verileri istenilen sırada getir... gibi) Eğer kullanıcı form üzerinde bu tip işlemleri sıklıkla yapacak ise, her seferinde veritabanına bağlantı açmak, sorgu çalıştırmak ve sorgu sonuçlarını uygulamaya aktarmak elbetteki hem uygulamanın performansını azaltacaktır, hem de veritabanında daha fazla işlem yapılacağı için sunucuda yavaşlığa sebep olacaktır. İşte böyle bir senaryoda tablodan istenilen tüm verileri alarak uygulamada (yani DataTable nesnesi içerisinde) saklamak, daha sonra da bu veriler üzerinde filtreleme ve sıralama işlemleri yapmak uygulamamızın performansını olumlu yönde arttıracaktır.

ADO.NET içerisinde bu tip işlemleri yapmamızı sağlayan bazı yapılar mevcuttur. Bu yazımızda ise DataTable sınıfımız içerisinde bulunan bazı metotları kullanarak bu işlemleri nasıl gerçekleştireceğimizi göreceğiz. Yine DataTable nesnesi içerisindeki verileri okuyarak, bu verilerin farklı görünümlerini sunan DataView isimli bir nesne ile de bu tip işlemler yapılabilir. DataView nesnesini ilerleyen zamanlarda fırsat olursa bir makale ile sizlere sunmaya çalışacağım. Dilerseniz DataTable sınıfı içerisinde yer alan bu metotların kullanımını örnek bir uygulama ile inceleyelim. Bunun için bir Windows Application açarak aşağıdaki şekilde bir form tasarlayalım.


Şekil: Formumuzun tasarımı ve kontrollerin isimleri

Yine uygulamamızda kullanılacak verileri tutacağımız Urunler isimli tablonun tasarımını da şu şekilde gerçekleştirelim:


Şekil: MS Access'te hazırlayacağımız Urunler tablosu

İlerleyen kısımlarda daha düzgün bir şekilde ilerleyebilmek için gerekli olan nesnelerin tanımlamalarını ve Form_Load'da yapılacak işlemleri hazırlayalım.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Data.OleDb;  // Veritabanı işlemleri için gerekli isim alanını ekliyoruz.

namespace DataTable_SortFilter
{
     public partial class Form1 : Form
     {
          // Connection, DataAdapter ve DataTable nesnelerini field olarak tanımlıyoruz.
          OleDbConnection con;
          OleDbDataAdapter da;
          DataTable dtUrunler;

          public Form1()
          {
               InitializeComponent();
          }

          private void Form1_Load(object sender, EventArgs e)
          {
               // Gerekli bağlantı ve sorgu cümleleri ile nesneleri oluşturuyoruz.
               // DataAdapter'ın Fill metodu ile getirilen verileri DataTable nesnesine yüklüyoruz.

               con = new OleDbConnection("Provider=Microsoft.Jet.OleDb.4.0; Data Source=Sirket.mdb");
               da = new OleDbDataAdapter("SELECT * FROM Urunler", con);
               dtUrunler = new DataTable("Urunler");
               da.Fill(dtUrunler);
          }

          private void btnDoldur_Click(object sender, EventArgs e)
          {
               // btnDoldur isimli butona tıklandığında dgvUrunler isimli gridimizi verilerle dolduruyoruz.
               dgvUrunler.DataSource = dtUrunler;
          }
     }
}

DataTable nesnemizi verilerle doldurduk. Şimdi yavaş yavaş sıralama ve filtreleme işlemlerini nasıl gerçekleştirebileceğimize bakalım.


Find metodu ile Satır (DataRow) Arama

Bildiğiniz gibi, DataTable nesneleri veritabanındaki tablolara oldukça benzer nesnelerdir. Tıpkı tablolarda olduğu gibi DataTable nesnesinde tutulan sütunlardan (DataColumn) birini veya birden fazlasını, DataTable'ın PrimaryKey property'si ile primary key (birincil anahtar) olarak tanımlayabiliriz. Primary key olarak tanımlanan sütunda taşınan kayıtlar tek olmalı, yani birbirini tekrar etmemelidir. Bu aşamadan sonra artık Find metodunu kullanarak tablo içerisinde primary key sütundaki kayıtlar üzerinde arama yapılabilir. Find metodu parametre olarak object tipinden bir değer alır ki; bu değer primary key alanda taşınan değerlerden biri olmalıdır. dtUrunler adındaki DataTable nesnemizin primary key sütunu int tipinden değer alan UrunID sütunu olacaktır. UrunID'si 5 olan bir kaydın DataRow tipinden bilgilerine şu şekilde ulaşabiliriz.

DataRow satir = dtUrunler.Sort(5); // UrunID'si 5 olan kaydın bilgilerini DataRow tipinden getirecektir.

Hazırlamış olduğumuz Windows uygulamasındaki btnFind isimli butonun vazifesi txtID isimli textbox'a girilen rakamsal değerin bulunduğu kaydın bilgilerini getirmek olacaktır. btnFind isimli butonumuzun Click event'inde çalışacak olan metodu aşağıdaki gibi dolduralım.

private void btnFind_Click(object sender, EventArgs e)
{
     // Arama işlemlerini yapabilmek için DataTable nesnesinin Primary Key alanını
     // belirlememiz gereklidir. dtUrunler nesnesinin PrimaryKey property'si DataColumn tipinden
     // bir dizi isteyeceği için öncelikli olarak bu diziyi tanımlıyoruz. Burada unique kayıt
     // saklayan herhangi bir alan tablonun Primary key'i olarak belirlenip Find metodu ile bu
     // alan üzerinde arama yapılabilir.

     DataColumn[] dcPK = { dtUrunler.Columns["UrunID"] };
     dtUrunler.PrimaryKey = dcPK;

     // Hatalı bilgi veya olmayan bir ID girildiğinde uygulamanın düzgün çalışması için
     // işlemleri try-catch içerisinde ele alıyoruz.

     try
     {
          int urunID = Convert.ToInt32(txtID.Text);
          // Kullanıcıdan ID değerini aldık. Rows kolleksiyonunun Find metodu parametre olarak 
          // verilen değeri satırlar içerisinde arar ve bulduğu satırı DataRow tipinde geriye
          // döndürür. Böylece satir isimli DataRow nesnemizin indekleyicileri ([]) aracılığıyla
          // sütun isimlerini vererek kaydın bilgilerine ulaşabiliriz.

          DataRow satir = dtUrunler.Rows.Find(urunID);
          string satirBilgileri = satir["UrunID"].ToString() + " - " + satir["Ad"].ToString() + 
                                   " - " + satir["Fiyat"].ToString() + " YTL";
          MessageBox.Show(satirBilgileri, urunID + " ID'li ürünün bilgileri");
     }
     catch (Exception ex)
     {
          MessageBox.Show("Satır bulunamadı!");
     }
}

Uygulamayı çalıştırdığımızda gridi doldurup varolan bir kaydın UrunID bilgisini txtID isimli textboxa yazıp butona tıklatırsak o ürün ile ilgili UrunID, Ad ve Fiyat gibi bilgileri mesaj kutusu içerisinde görebiliriz.


Şekil: Find metodu ile ID bilgisi verilen kaydın bilgilerine ulaşmak

Böylece Find metodunu kullanarak primary key sütunumuzdaki bir bilgiye göre ilgili kaydın değerlerine nasıl ulaşabileceğimizi görmüş olduk. Find metodunun kullanımını inceleyecek olursak bir de aşırı yüklenmiş halinin olduğunu görebiliriz. Metodun ikinci versiyonu ise object tipinden bir dizi ister. Eğer tablomuzun primary key alanını birden fazla sütun oluşturuyorsa, Find metodunun bu versiyonunu kullanarak birden fazla kriter belirterek arama işlemi yapabiliriz. Örneğin dtUrunler tablomuzun primary key alanının sadece UrunID sütunundan değilde, UrunID ve Ad sütunlarının birleşiminden oluştuğunu varsayalım. Böyle bir durumda arama kriterini sadece UrunID alanı üzerinden değil, hem UrunID hem de Ad alanı üzerinden verebiliriz. Aslında Find(object key) versiyonu primary key alan üzerinden WHERE UrunID=5 gibi bir arama yaparken, Find(object[] keys) versiyonu ise UrunID ve Ad sütunlarının primary key olduğu bir durumda WHERE UrunID=5 AND Ad='Çikolata-Fındıklı' şeklinde bir arama yaparak sonucu getirir. Find metonun aşırı yüklenmiş halinin kullanımını aşağıdaki örnekte görebilirsiniz.

// UrunID ve Ad sütunlarını bir DataColumn dizisi içerisinde primary key olarak
// tanımlıyor ve dtUrunler tablomuzun PrimaryKey property'sine set ediyoruz.

DataColumn[] dcPK = { dtUrunler.Columns["UrunID"], dtUrunler.Columns["Ad"] };
dtUrunler.PrimaryKey = dcPK;


// Object tipindeki dizimizin ilk değer UrunID, ikinci değeri Ad sütunu için.
object[] urunID_Ad = {5, "Çikolata-Fındıklı"};
DataRow satir = dtUrunler.Rows.Find(urunID_Ad);


Select Metodu ile Filtreleme ve Sıralama İşlemleri

Find metodu primary key alan üzerinden tek bir satır ile bilgi getirir, yani bir nevi SELECT * FROM Urunler WHERE UrunID=5 gibi bir sorgu çalıştırmamızı sağlar.  Bazı durumlarda ise sadece bir kaydın değilde belirli kriterlere uyan kayıtların bulunmasını isteyebiliriz.(SELECT * FROM Urunler WHERE Fiyat>1 AND Fiyat<2 gibi) Dikkat edecek olursanız Find metodunda yapacağımız aramalar sadece primary key alanı ile kısıtlı idi. Burada bahsettiğimiz sorguda ise sadece primary key alan üzerinden değil de, tüm alanlarla ilgili kriterler belirtme isteğinden söz ediyoruz. Yine verdiğimiz kriterler sonucunda getirilen kayıtların belirli bir alana göre sıralanmasını da isteyebiliriz. (SELECT * FROM Urunler WHERE Fiyat>1 AND Fiyat<2 ORDER BY Ad gibi) Bu tip durumlarda veritabanına tekrar SELECT sorguları göndermeden DataTable nesnesi üzerinden de sorgulama işlemleri gerçekleştirebiliriz. DataTable sınıfı içerisinde yer alan Select metodu bu görevi üstlenmektedir. Verilen krtier ve sıralama ifadesine göre DataTable içerisindeki veriler üzerinde kriterlere uyan karakterleri DataRow dizisi olarak geriye döndürür. Select metodunun versiyonlarını inceleyecek olursak;

public DataRow[] Select();
DataTable içerisindeki tüm satırları getirir.

public DataRow[] Select(string filterExpression);
DataTable içerisinden sadece filtrelenen kayıtları getirir.

public DataRow[] Select(string filterExpression, string sort);
DataTable içerisinden sadece filtrelenen kayıtları, belirtilen sıralamada getirir.

public DataRow[] Select(string filterExpression, string sort, DataViewRowState recordStates);
DataTable içerisinden DataRowViewState'i belirtilen kayıtlar içerisinden filtrelenen kayıtları, belirtilen sıralamada getirir.

Metot içerisinde kullanılan parametreler ile ilgili olarak;

- filterExpression: Filtreleme ifadesi. WHERE ile kullanılacak ifade. Örneğin; "Fiyat>1 AND Fiyat<5" gibi.
- sort: Sıralama ifadesi. ORDER BY ile kullanılacak ifade. Örneğin Fiyat DESC gibi.
- recordStates: Bildiğiniz gibi DataTable içerisindeki veriler üzerinde çalışma zamanında değişiklikler yapabiliyoruz. recordStates parametresi DataTable içerisindeki kayıtların durumuna göre seçme işleminin hangi kayıtlar arasından yapılacağını belirler. Silinmiş kayıtlar arasından seç, değiştirilmeyen kayıtlar arasından seç gibi... DataViewRowState enum'ı (numaralandırıcı) tipinden değer alır. Bu değerler ve anlamlarını aşağıdaki listede bulabilirsiniz.

Added: Sadece eklenen kayıtlarda
CurrentRows: Tabloda o an varolan kayıtlarda
Deleted: Sadece silinen kayıtlarda
ModifiedCurrent: Değişen kayıtlarda (o an varolan değerleri ile birlikte)
ModifiedOriginal: Değişen kayıtlarda (orjinal değerleri ile birlikte)
None: Hiçbir kayıtta
OriginalRows: Orjinal kayıtlarda (silinen ve değişen kayıtlar dahil)
Unchanged: Değişmeyen kayıtlarda
Tablo: DataViewRowState enum tipinin alabileceği değerler ve anlamları

Select metodunun örnek kullanımlarını aşağıda bulabilirsiniz.

// Tablodaki tüm kayıtları getirir.
DataRow[] satirlar = dtUrunler.Select();

// Tablodaki kayıtlarda UrunID bilgisi 5 ve 10 arasında olanları getirir.
DataRow[] satirlar = dtUrunler.Select("UrunID>5 AND UrunID<10");

// Tablodaki kayıtlarda UrunID bilgisi 5 ve 10 arasındaki kayıtları Fiyat değerleri azalan şekilde getirir.
DataRow[] satirlar = dtUrunler.Select("UrunID>5 AND UrunID<10", "Fiyat DESC");

// Tabloda o an varolan kayıtlar içerisinde, UrunID bilgisi 5 ve 10 arasındaki kayıtları, Fiyat değerleri azalan şekilde getirir.
DataRow[] satirlar = dtUrunler.Select("UrunID>5 AND UrunID<10", "Fiyat DESC", DataViewRowState.CurrentRows);

Yukarıdaki ifadelerin tamamından dönen sonuç DataRow dizisi şeklindedir. Dizi olarak gelen bu kayıtları bir foreach döngüsü içerisinde uygulama ortamına aktarabiliriz. Getirilen sonuçların label1 isimli bir Label kontrolüne yazımının örnek kodları aşağıda yer almaktadır.

foreach (DataRow dr in satirlar)
{
     label1.Text += dr["UrunID"] + " - " + dr["Ad"] + " - " + dr["Fiyat"] + "\n";
}

Uygulamamızın btnSelect butonuna tıklandığında txtWhereCumle textbox'ından, cmdOrderBy combobox'ından ve cbDesc checkbox'ından alacağı değerlere göre filtrelenen ve sıralanan kayıtları DataGridView kontrolüne aşağıdaki gibi doldurabiliriz.

private void btnSelect_Click(object sender, EventArgs e)
{
     string whereIfade, orderByIfade;
     whereIfade = txtWhereCumle.Text;  // WHERE'den sonra gelecek ifade
     orderByIfade = cmbOrderBy.SelectedItem.ToString(); // ORDER BY'dan sonra gelecek ifade
     if (cbDesc.Checked)
          orderByIfade += " DESC";
// Tersten sıralama yapılacaksa ifademize DESC ekliyoruz

     DataRow[] satirlar = dtUrunler.Select(whereIfade, orderByIfade);

     // Seçilen satırlar dtUrunler isimli tabloya ait olduğu için başka yapıya sahip
     // bir DataTable nesnesine yüklenemez! Buradaki sorunu aşabilmek için yeni
     // bir DataTable nesnesini dtUrunler isimli tablonun Copy metodu ile oluşturuyoruz.
     // Yani dtGecici isimli tablomuz dtUrunler tablosu ile aynı yapıya sahip oluyor.
     // Fakat bize sadece o an seçilen veriler gerekli(satirlar dizisi). Bu nedenle de
     // dtGecici'deki row'ları Clear metodu ile siliyoruz. Select metodunun kullanımında
     // benzer durumlarda sıkıntı çıkabilmektedir. Burada ele almadığımız DataView nesnesi
     // bu problemleri çözebilecek şekilde dizayn edilmiştir.

     DataTable dtGecici = dtUrunler.Copy();
     dtGecici.Clear();

     foreach (DataRow dr in satirlar)
     {
          // satirlar nesnesindeki row'lar hala dtUrunler tablosuna ait olduğu için
          // ImportRow metodu ile satırları kopyalıyoruz

          dtGecici.ImportRow(dr);
     }
     dgvUrunler.DataSource = dtGecici; 
// Verileri gride yüklüyoruz.
}

txtWhereCumle isimli textbox'a arama kriterlerimizi, cmbOrderBy combobox'ına sıralanacak alanın girişini yaparak, sıralamayı tersten yapmak istiyorsak cbDesc checkbox'ını seçerek istenilen verileri grid içerisine doldurabiliriz.


Şekil: Filtreleme ve Sıralama işlemlerinin yapılması ve sonuçların elde edilmesi

Bu yazımızda DataTable üzerinde filtreleme ve sıralama işlemlerini gerçekleştirmemizi sağlayan Find ve Select metotlarını derinlemesine incelemeye çalıştık. Veritabanından getirilen aynı veriler üzerinde sıklıkla sorgulama yapacağımız durumlarda (özellikle raporlama, listeleme vb. işlemlerde) veritabanına sürekli bağlanarak aynı veriler üzerinde sorgu çalıştırmak performans ve zaman açısından olumsuz sonuçlar doğurabilir. Bu tip durumlarda DataTable sınıfı içerisinde yer alan Select metodu ile birden fazla satır üzerinde seçme işlemleri, yine DataTable içerisinde bulunanan Rows kolleksiyonuna ait Find metodu ile de tek kaydı seçme işlemlerini gerçekleştirebiliriz. Bu işlemleri yaparken sorgulamalar veritabanı üzerinde değil de, DataTable nesnemiz üzerinde gerçekleşeceği için bilgisayarın belleğinde tutulan bir veri üzerinde gerçekleşen bu işlemler daha hızlı olacaktır. Bu noktada bu işlemler yapılırken veritabanı üzerinde yapılacak değişikliklerin uygulamaya yansımayacağını da unutmamamız gerekir.

Bir başka makalede görüşmek dileğiyle...

Uğur UMUTLUOĞLU
www.umutluoglu.com
www.nedirtv.com