19 Kasım 2017 Pazar
Twitter

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.

Değerlendirme

Yazım dilinin sadeliği ve anlaşılabilirliği Puan: 3,71 (4 oy)
Yazıdaki kodların kalitesi Puan: 2,84 (4 oy)
İhtiyaca cevap verme Puan: 3,50 (4 oy)
Tavsiye edilebilirlik Puan: 3,45 (3 oy)
Genel değerlendirme Puan: 2,70 (4 oy)
Puanlamalar 5 üzerindendir.
  • Site Yorumlarý
  • Facebook Yorumlarý Facebook Yorumlarý
Yeni yorum yaz Toplam: 15, Onay bekleyen: 1.
  1. Mesut ILICA

    hocam şöyle bir sorum olacaktı bu şekilde oluşturulan kategori sisteminde alt kategorilerdeki ürünleri ana kategoriye tıklanınca nasıl gösterebiliriz ? Yani sitede ana kategori sayfasına tıklandığında bu ana kategoriye ait alt kategorileri ve bu alt kategorilere ait ürünleri bir data table ye yükleyip onu da bir data listte göstermem lazım çözemedim yardımcı olur musunuz ?

  2. Murat

    yapabilen arkadaşlar bi gönderseniz çok dua ederim.

    • murat

      Merhaba hocam kişisel bilgimi paylaşmışım lütfen silebilir misiniz. yorumu
      teşekkürler...

  3. Devrim Altınkurt

    hayır yanlışın var. `kategoriler` metodun parametresinden geliyor. kategoriler db den bir kez okunuyor. sonra bir daha db den okuma olmuyor.

  4. faruk asyalı

    LINQ kodundadaki `var altKategoriler = from k in kategoriler` bu satır `var altKategoriler = from k in db.kategoriler` şeklinde olması gerekir. Kodu çalıştırmak biraz uğraştırdı.

  5. omer

    linq olanında herzaman 0 olan kategoriler cıkıyor..altkategoriler yok iyicene kontrol ettim ama sorun nerde anlıyamadım...