Recursive metodla kategori listeleme

Son zamanlarda benden yazmamı istenen yazılardan birisi de iç içe sonsuz kategori mantığı ile listelemenin nasıl olacağı ile ilgili

Son zamanlarda benden yazmamı istenen yazılardan birisi de iç içe sonsuz kategori mantığı ile listelemenin nasıl olacağı ile ilgili.

Ben de bu konu ile ilgili ayrıntılı bir yazı yazayım istedim.

Recursive in Türkçe'deki karşılığı özyineleme. Ben recursive kelimesini kullanmayı tercih ediyorum. Programcıların anladığı dilden konuşmak lazım. Recursive / özyineleme, kendini çağıran metod demek.

Basit bir örnek ile faktöryel hesabı yapalım:

protected void Page_Load(object sender, EventArgs e)
{
  Response.Write(Faktoryel(5));
}

int Faktoryel(int sayi)
{
  if (sayi == 1) return 1;
  return sayi * Faktoryel(sayi - 1);
}

Metodun kendisini çağırdığını görüyorsunuz.

Şimdi gelelim esas konumuza.

Önce tablo yapısından bahsedelim. İç içe sonsuz kategori ekleyebilmek için tablo yapımız şu şekilde olmalıdır:

Değişik yollarla da yapabilirsiniz ama en basit olan yol sanırım bu şekilde olanıdır. Tabloyu inceleyecek olursanız oldukça basit bir mantık ile oluşturulduğunu göreceksiniz. Topu topu 3 tane alan ile tablomuzu hazırladık. "Kategori" alanı kategorimizin ve alt kategorilerimizin adını, "Id" alanı her kategori için unique değeri (Primary key), "UstKategoriId" de kategorinin hangi kategorinin alt kategorisi olduğunu belirttiğimiz alandır. Aklınıza gelen ilk soruyu tahmin edeyim: "peki ana kategorilerimiz için bu değer ne olacak?"  Bu size kalmış isterseniz 0 (sıfır) isterseniz NULL değerini verebilirsiniz. Ben programlarımda 0'ı (sıfır) tercih ediyorum.

Böylece mantığımız şu şekilde çalışıyor: UstKategoriId si 0 (sıfır) olan kategoriler ana kategori, UstKategoriId'si 0'dan farklı olan kategoriler, Id'si bu değere eşit olan kategorinin alt kategorisi olmuş oluyor.

Birkaç örnek veri göstereyim:

Gördüğüniz gibi, "Damızlık Hayvan", "Danışmanlık", "Ekipman", vb. kategoriler ana kategorilerdir. UstKategoriId'si 0 dan farklı olanlar da alt kategorilerdir.

Neden recursive metodlar diye soracak olursanız, cevabım: "Kaç tane ana kategori, bu ana kategorilerin kaç tane alt kategorisi olduğunu, bu alt kategorilerin kaç tane alt kategorisi olduğunu bilmiyoruz. Bu yüzden recursive metodları kullanıyoruz."

Şimdi sıra bu sistemi programımızda nasıl kullanabileceğimize geldi.

Programımızın bir noktasında (Page_Load bu iş için en uygunu, ama size kalmış tabii ki) bir kez bu recursive metodu çağıracağız, sonrasını bu metod kendi hallecek.

Bakalım nasıl oluyor:

Sayfamıza bir tane TreeView ekleyelim:

<asp:TreeView ID="tvKategoriler" runat="server" ShowLines="True" >
</asp:TreeView>

TreeView'a kategorilerimizi yükleyelim:

public partial class Default : System.Web.UI.Page
{
  DataTable dt = null;
  protected void Page_Load(object sender, EventArgs e)
  {
    if (!IsPostBack)
     {
       HelperDB helperDB = new HelperDB();
       string sql = "select * from kategoriler";
       dt = helperDB.ExecuteDataSet(sql).Tables[0];
       TreeNode root = new TreeNode("Kategoriler", "0");
       AgacOlustur(root, 0);
       tvKategoriler.Nodes.Add(root);
     }
  }

  void AgacOlustur(TreeNode root, int ustKategoriId)
  {
    DataRow[] altKategoriler = dt.Select("UstKategoriId=" + ustKategoriId);
    if (altKategoriler.Length == 0) return;
    foreach (DataRow dr in altKategoriler)
    {
      int id = Convert.ToInt32(dr["Id"].ToString());
      TreeNode node = new TreeNode(dr["Kategori"].ToString(), id.ToString());
      AgacOlustur(node, id);
      root.ChildNodes.Add(node);
    }
  }
}

Kategorileri bir kereliğine DataTable a yükledik ve  alt kategorileri bulmak için de DataTable'ın Select() metodunu kullandık.

Kodu çalıştırdığımızda şu şekilde bir çıktı alıyoruz:

Aynı işlemi LINQ ile yapmak istersek:

public partial class Default : System.Web.UI.Page
{
  protected void Page_Load(object sender, EventArgs e)
  {
    if (!IsPostBack)
      {
        using (var db = new MyEntities())
        {
          var kategoriler = from k in db.kategoriler
                            where k.UstKategoriId == 0
                            select k;

          TreeNode root = new TreeNode("Kategoriler", "0");
          AgacOlustur(kategoriler, root, 0);
          tvKategoriler.Nodes.Add(root);
        }
      }
  }

  void AgacOlustur(IQueryable<kategoriler> kategoriler, TreeNode root, int ustKategoriId)
  {
    using (var db = new MyEntities())
    {
      var altKategoriler = from k in kategoriler
                           where k.UstKategoriId == ustKategoriId
                           select k;

      if (altKategoriler.Count() == 0) return;

      foreach (var k in altKategoriler)
        {
          TreeNode node = new TreeNode(k.Kategori, k.Id.ToString());
          AgacOlustur(altKategoriler, node, k.Id);
          root.ChildNodes.Add(node);
        }
    }
  }
}

Entity Framework'ta kullanırken dikkat etmeniz gereken bir husus var, bu konuyu http://dalt.in/Ck42N adresindeki yazımda bahsetmiştim.

Eğer TreeView ile kullanmak istemiyorsanız muhtemelen <ul><li>..</li></ul> ler ile kullanmak isteyebilirsiniz. Malum, pekçok DHTMLjquery menü scriptleri bu şekilde çalışmaktadır.

O zaman kodlarımızı buna göre değiştirelim.

Sayfamıza bir tane Literal ekleyelim:

<asp:Literal ID="litKategoriler" runat="server" />

Programımızda güzel bir string oluşturacağız ve bu string i literalimize yerleştireceğiz:

public partial class Default : System.Web.UI.Page
{
  StringBuilder kategoriStr = new StringBuilder();
  DataTable dt = null;
  protected void Page_Load(object sender, EventArgs e)
  {
    if (!IsPostBack)
    {
      HelperDB helperDB = new HelperDB();
      string sql = "select * from kategoriler";
      dt = helperDB.ExecuteDataSet(sql).Tables[0];
      AgacOlustur(0);
      litKategoriler.Text = kategoriStr.ToString();
    }
  }
  
  void AgacOlustur(int ustKategoriId)
  {
    DataRow[] altKategoriler = dt.Select("UstKategoriId=" + ustKategoriId);
    if (altKategoriler.Length == 0) return;
    kategoriStr.Append("<ul>");
    foreach (DataRow dr in altKategoriler)
    {
      int id = Convert.ToInt32(dr["Id"].ToString());
      kategoriStr.AppendFormat(
          @"<li><a href=""Kategori.aspx?Id={0}"">{1}</a>",
            dr["Id"].ToString(), 
            dr["Kategori"].ToString());
      AgacOlustur(id);
      kategoriStr.Append("</li>");
    }
    kategoriStr.Append("</ul>");
  }
}

Kodu çalıştırdığımızda yine aynı çıktıyı elde ettiğimizi göreceksiniz. Sayfanın kaynak kodunu incelemenizi tavsiye ediyorum.

Umarım faydalı bir yazı olmuştur.

Herkese kolaylıklar diliyorum.