SqlDataAdapter a zobrazení stromové struktury Repeaterem v ASP.NET
Kdysi, v dřevních dobách ASP a jiných skriptovacích jazyků, se zobrazení stromové struktury z tabulky v databázi provádělo prostým rekurzivním voláním funkce, která pro každé podřízené položky provedla nový dotaz do databáze. Moderní ASP.NET nám dává k dispozici modernější postup, který nezatěžuje zdroj dat opakovanými dotazy – celou strukturu načteme najednou.
Nejprve by se patřilo připomenout, co že máme onou stromovou strukturou na mysli. Jde tedy o takový způsob uložení záznamů, kdy každý záznam má v sobě obsažen také identifikátor nadřízeného záznamu. Kořenové položky mohou mít identifikátor nadřízeného záznamu roven nule nebo roven vlastnímu identifikátoru. V našem příkladu uvažujeme, že kořenové položky mají jako rodiče uveden záznam „0“. Pokud někdo potřebuje důkladněji osvěžit teorii stromové struktury, odkazuji jej na článek Stromové menu na stránce.
V rychlosti si jen osvěžíme dřívější možné řešení pomocí rekurzivního volání VBScript funkce v ASP – vidíme, že se zde několikrát (v cyklu) provádí dotaz do datového zdroje:
Function BrowseTree(Id)
Dim TreeQ
Set TreeQ = myADODBConnection.Execute(„SELECT Id, ItemText FROM Tree WHERE ParentId = “ & Id)
If Not TreeQ.EOF Then
Response.Write(„<ul>“)
Do While Not TreeQ.EOF
Response.Write(„<li>“ & TreeQ(„ItemText“))
BrowseTree TreeQ(„Id“) ‚rekurze
Response.Write(„</li>“)
TreeQ.MoveNext
Loop
Response.Write(„</ul>“)
End If
Set TreeQ = Nothing
End Function
Nehledě na archaické použití Response.Write
pro výstup stránky, je vůbec postup několikanásobného přístupu do datového zdroje pro pár záznamů dost nevhodný. V našem příkladu využijeme SqlDataAdapter, pomocí něj si naplníme DataTable a tu si projdeme sami – nevyužijeme ovšem rekurzi, podřízené položky dohledáme při obsluze události ItemCreated prvku Repeater. Jakmile je v Repeateru
vytvořena nějaká položka, zkusíme dohledat podřízené položky. Pokud nějaké existují, dynamicky vygenerujeme vnořený Repeater
a podřízenými položkami jej naplníme. Tento postup pak probíhá, dokud nejsou všechny podřízené položky dohledány a vykresleny. Výslednou stránku nebo control cachujeme, abychom přístupy do datového zdroje omezili na minimum.
Základem pro výstup stromové struktury je Repeater
:
<%@ Page Language=“C#“ EnableViewState=“False“ Debug=“False“ AutoEventWireUp=“False“ CodeBehind=“ServerMap.aspx.cs“ Inherits=“Interval.CZ.ServerMap“ %>
<%@ OutputCache Duration=“7200″ VaryByParam=“none“ %>
<html>
<body>
<asp:Repeater Id=“Repeater1″ EnableViewState=“False“ RunAt=“Server“ />
</body>
</html>
Tento Repeater
jakoby nemá šablonu, zavádíme ji totiž z externích souborů. Mohli bychom sice šablonu klidně vepsat přímo jako vnořené položky Repeateru
v designu stránky, záměrně jsem se ale rozhodl využít tento článek k ukázce, jak dynamicky načíst šablonu. To se hodí v případě, že chceme uživateli dát k dispozici různé možnosti přizpůsobení nebo generování odlišných podob stránky pro různá zařízení. Vidíme také, že třídu naší stránky ServerMap
, definovanou v CodeBehind ServerMap.aspx.cz
jsme zařadili do prostoru názvů Interval.cz
.
Šablony záhlaví, zápatí a položky máme definovány v souborech s příponou .ascx podobně, jako user control:
ServerMapHeaderTemplate.ascx:
<%@ Control Language=“C#“ %><ul>
ServerMapFooterTemplate.ascx
<%@ Control Language=“C#“ %></ul>
ServerMapItemTemplate.ascx
<%@ Control Language=“C#“ %>
<li>
<asp:HyperLink Text='<%# ((System.Data.DataRow)((RepeaterItem) Container).DataItem)[2] %>‘ NavigateUrl='<%# (String) ((System.Data.DataRow)((RepeaterItem) Container).DataItem)[3] %>‘ RunAt=“Server“ />
</li>
Výše uvedené jsou soubory definující šablony pro záhlaví HeaderTemplate, zápatí FooterTemplate a výstup jednotlivých položek ItemTemplate v Repeateru
. Všimněte si také, že jsme nepoužili obvyklého DataBinder.Eval() v bindovacím výrazu – chceme zde vysoký výkon aplikace a proto použijeme přímo přetypování na položku očekávaného typu. Obsah položky Container přetypujeme na RepeaterItem. Tuto pak můžeme přetypovat na DataRow, protože zdrojem dat je DataTable, která se skládá z řádků typu DataRow
. Na řádek pak uplatníme číselný indexer (Int32
), čímž získáme daný sloupec. Tento postup pak poskytuje nejvyšší výkon.
Pokud bychom chtěli odlišit sudé a liché položky výstupu, bylo by potřeba nadefinovat a zavést ještě šablonu pro AlternatingItemTemplate. V příkladu jsem toto vynechal nejen pro přehlednost, ale také proto, že u číslovaného seznamu takové rozlišení nemá obvykle smysl.
V obslužném kódu stránky deklarujeme její prvky a přiřadíme obsluhy událostí:
namespace Interval.CZ.WebCMS
{
using System;
using System.Data;
using System.Data.SqlClient;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
public abstract class ServerMap : System.Web.UI.Page
{
protected Repeater Repeater1;
protected DataTable treeDataTable = new DataTable();
override protected void OnInit(EventArgs e)
{
InitializeComponent();
base.OnInit(e);
}
private void InitializeComponent()
{
this.Load += new System.EventHandler(this.Page_Load);
Repeater1.ItemCreated += new RepeaterItemEventHandler(this.MenuItemCreated);
}
}
}
Deklarovány jako protected zde máme dvě hlavní součásti – použitý Repeater
a již zmíněnou DataTable
. Procedurou InitializeComponent()
přiřadíme obsluhy událostí Page
(stránky) i Repeateru
.
Získání zdroje dat a naplnění definovaného Repeateru
provedeme v obsluze události Page_Load:
private void Page_Load(object sender, System.EventArgs e)
{
SqlCommand myCommand = new SqlCommand(„dbo.GetServerMap“,new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings[„ConnectionString“]));
myCommand.CommandType = CommandType.StoredProcedure;
try
{
SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter(myCommand);
mySqlDataAdapter.Fill(treeDataTable);
mySqlDataAdapter.Dispose();
treeDataTable.Columns[„ItemID“].Unique = true;
treeDataTable.PrimaryKey = new DataColumn[] {treeDataTable.Columns[„ItemID“]};
}
catch { }
if (treeDataTable.Rows.Count > 0)
{
Repeater1.ItemTemplate = Page.LoadTemplate(„~/Controls/ServerMapItemTemplate.ascx“);
Repeater1.HeaderTemplate = Page.LoadTemplate(„~/Controls/ServerMapHeaderTemplate.ascx“);
Repeater1.FooterTemplate = Page.LoadTemplate(„~/Controls/ServerMapFooterTemplate.ascx“);
Repeater1.DataSource = treeDataTable.Select(„ParentItemID = 0“);
Repeater1.DataBind();
}
treeDataTable.Dispose();
}
Narozdíl od článku Generujeme RSS pomocí Repeateru, kde byl použit jako zdroj dat SqlDataReader, zde použijeme SqlDataAdapter, pomocí kterého elegantně a jednoduše naplníme DataTable
. Z této potom postupně metodou Select() budeme vybírat záznamy odpovídající dané úrovni stromu. Jako první úroveň vybereme záznamy, kde identifikátor rodiče má hodnotu „0“, a nastavíme je jako zdroj DataSource Repeateru
umístěného ve stránce. Všimněte si, že údaje pro připojení k databázi získáváme jako konfigurační hodnoty ze souboru Web.config
.
V příkladu také vidíme, že se nestaráme o otevření a zavření připojení k datovému zdroji – SqlDataAdapter
si spojení otevře sám dle potřeby a po použití jej zase uzavře (SqlDataAdapter
zanechá spojení ve stavu, v jakém bylo před jeho použitím – pokud by tedy bylo otevřeno předem, SqlDataAdapter
ho nebude zavírat).
Do tabulky nastavujeme sloupec ItemID
jako unikátní a jako primární klíč prostřednictvím vlastnosti tabulky DataTable
PrimaryKey a nastavením vlastnosti sloupce DataColumn Unique v kolekci Columns na „true“. Toto velmi zrychlí vykreslení stromové struktury, protože metoda Select()
pak může vybírat řádky v DataTable
mnohem rychleji.
Nakonec ověříme, že v DataTable
jsou nějaké řádky, a pokud ano, pak načteme metodou LoadTemplate šablony Repeateru
z externích souborů (mají příponu .ascx), nastavíme zdroj Repeateru
na řádky nulté úrovně stromu výběrem z DataTable
prostřednictvím Select("ParentItemID = 0")
a provedeme metodu DataBind(). Tím se začnou v Repeateru
tvořit nové položky, což je, jak jsme již uvedli, spojeno s voláním obslužné metody této události – projdou se tak podřízené položky, aby bylo možno je také vykreslit.
Hlavní díl práce při zobrazení podřízených položek je svěřen proceduře MenuItemCreated()
, která obsluhuje popisovanou událost vytvoření položky v Repeateru
:
private void MenuItemCreated(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
DataRow myRow = (DataRow) e.Item.DataItem;
DataRow[] subItem = treeDataTable.Select(„ParentItemID = “ + myRow[0].ToString());
if (subItem.Length > 0)
{
Repeater parentRepeater = (Repeater) sender;
Repeater subitemRepeater = new Repeater();
subitemRepeater.EnableViewState = false;
subitemRepeater.ItemTemplate = parentRepeater.ItemTemplate;
subitemRepeater.HeaderTemplate = parentRepeater.HeaderTemplate;
subitemRepeater.FooterTemplate = parentRepeater.FooterTemplate;
subitemRepeater.ItemCreated += new RepeaterItemEventHandler(this.MenuItemCreated);
subitemRepeater.DataSource = subItem;
subitemRepeater.DataBind();
e.Item.Controls.Add(subitemRepeater);
}
}
}
V obsluze události otestujeme, že jde o položku (ListItemType.Item) nebo alternativní položku (ListItemType.AlternatingItem). Pokud ano, „vydlabeme“ si z předaného parametru typu RepeaterItemEvenArgs zdroj dané položky. Jelikož je v našem případě datovým zdrojem Repeateru
tabulka DataTable
, tak zdrojem položky je řádek tabulky, tedy DataRow
– ten budeme potřebovat pro získání aktuálního id položky, tedy ItemID
. Tuto hodnotu pak použijeme v metodě Select()
, kterou uplatníme opět na naši již dříve naplněnou DataTable
, čímž získáme podřízené položky. Pokud jsme nějaké nalezli (Length
kolekce řádků je větší než „0“), pak provedeme posloupnost povelů, které zajistí vykreslení všech podřízených položek.
Vytvoříme si nový Repeater
pomocí konstruktoru Repeater() a nastavíme mu všechny potřebné vlastnosti – většinu jich převezmeme z rodičovského Repeateru
předaného v parametru „sender“. Abychom ušetřili prostředky serveru, vypínáme ukládání stavu zobrazení pomocí EnableViewState="False"
. Přiřadíme taktéž obsluhu události při vytvoření položky jako nový RepeaterItemEventHandler – a „rekurze“ je na světě, dokud budou existovat nějaké podřízené položky záznamu, budou vznikat další a další podřízené Repeatery
.
Uvedený postup se může samozřejmě hodit i pro různé nevizuální aplikace postavené na XML, například VoiceXML. Nebude složité upravit aplikaci pro použití jiného typu databázového zdroje, například nahrazením SqlDataAdapteru
za OleDbAdapter.
Popisované soubory si můžete stáhnout (zdrojový kód), pro praktické použití je třeba je přizpůsobit vlastnímu zdroj dat, nejlépe vlastní uloženou proceduru. Je vhodné se ubezpečit, že struktura dodávaných dat je korektní, protože stačí jediná položka, která má nevhodné ItemID
a ParentItemID
a můžeme si přivodit nepěkné zacyklení s vyčerpáním prostředků serveru, z kterého se pak aplikace může vzpamatovávat i několik desítek sekund.
Starší komentáře ke článku
Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.
Mohlo by vás také zajímat
-
Chcete jedinečnou doménu? Objevte koncovky FOOD, MEME a MUSIC!
7. listopadu 2024 -
AI v programování: Jak používat GitHub Copilot (část 2)
19. února 2024 -
Globální výpadek IT systémů: Může za to jediná aktualizace
19. července 2024 -
Monitory OLED: klíčové pojmy a funkce
13. května 2024
Nejnovější
-
Výkonný a kompaktní: ASOME Max Studio s výjimečným poměrem cena/výkon
11. listopadu 2024 -
Šokující data od Microsoftu: Kyberútoky rostou o stovky procent!
8. listopadu 2024 -
Chcete jedinečnou doménu? Objevte koncovky FOOD, MEME a MUSIC!
7. listopadu 2024 -
OpenAI představilo novou funkci ChatGPT Search
6. listopadu 2024