Grafikus C# – Windows Forms tanfolyam › 3.8 lecke

Windows Forms · 3.8 lecke · adatok a memóriában

DataAdapter, DataTable és DataSource – adatok a memóriában

Eddig minden szűrőváltozásnál újra lekérdeztük az adatbázist. Kisebb adatmennyiségnél van egy gyorsabb és sokszor kényelmesebb út is: egyszer betöltöd az adatokat egy DataTable-be, és ott szűrsz, rendezel, számolsz. Ebben a leckében a DataTable sorait és oszlopait, a DataView szűrését és rendezését, a kifejezés-oszlopokat és az összesítő Compute metódust ismered meg. Közben az ékezetes keresés problémájára is megoldást kapsz.

  • kb. 110 perc
  • közepes szint
  • DataTable és DataView
Anime stílusú fiú és lány egy C# programozási feladatot beszél meg

A lecke végére

  • érted, mit tárol a DataTable (oszlopok, sorok, értékek), és hogyan olvasod ki a sorait;
  • kezeled a hiányzó (DBNull) értékeket az IsNull és a DBNull.Value segítségével;
  • a DataView.RowFilter és a Sort tulajdonsággal szűrsz és rendezel az adatbázis újabb lekérdezése nélkül;
  • a szűrő szövegét helyesen védi (aposztróf, speciális karakterek);
  • kifejezés-oszlopot adsz a táblához (fizetes * 12, IIF), és összesítést kérsz a Compute metódussal;
  • döntesz arról, mikor érdemes a memóriában, és mikor az adatbázisban szűrni.

Mielőtt elkezded

Ehhez a leckéhez kell a 3.4 lecke (DataTable, DataGridView), a 3.5 lecke (a dolgozói adatok, LIKE) és a 3.7 lecke (a paraméterek és az ékezetes keresés problémája).

3.7 Paraméterek és SQL-biztonság →

A DataTable: oszlopok, sorok, értékek

A DataTable egy memóriában lévő táblázat. Az SQLiteDataAdapter.Fill az adatbázisból tölti fel: létrehozza az oszlopokat (az SQL oszlopneveivel), és a sorokat az értékekkel. A DataTable három fontos része:

ColumnsAz oszlopok gyűjteménye. Egy oszlopot névvel érsz el: tabla.Columns["vnev"]. Az oszlop DataType tulajdonsága az értékek típusa.
RowsA sorok gyűjteménye (DataRow objektumok). A Rows.Count a sorok száma.
sor["oszlop"]Egy sor adott oszlopának értéke, object típusban. Szöveggé a ToString(), számmá a Convert alakítja.
DBNull.ValueA hiányzó (NULL) érték. A sor.IsNull("oszlop") igaz, ha nincs érték.
Form1.csC#
DataTable tabla = DB.Osszes();   // a 3.6 leckéből: id, vnev, knev, fizetes, cim

long osszeg = 0;
int nincsFizetes = 0;

foreach (DataRow sor in tabla.Rows)
{
string nev = sor["vnev"].ToString() + " " + sor["knev"].ToString();

if (sor.IsNull("fizetes"))
{
nincsFizetes = nincsFizetes + 1;
}
else
{
osszeg = osszeg + Convert.ToInt64(sor["fizetes"]);
}
}

lblEredmeny.Text = $"{tabla.Rows.Count} dolgozó, a fizetések összege: {osszeg} Ft, fizetés nélkül: {nincsFizetes}";

Az SQLite egész számai Int64 típusúak

Az SQLite INTEGER oszlopa a DataTable-ben Int64 (long) típusú lesz, nem int. Ezért ajánlott a Convert.ToInt32 vagy a Convert.ToInt64 használata a közvetlen (int) típusátalakítás helyett: az (int)sor["fizetes"] egy object-ből kicsomagolás, amely hibát (InvalidCastException) dob, ha a belső típus long.

Jósolj!

A tabla.Rows.Count értéke 9, és az egyik dolgozó fizetése NULL. Mi történik a fenti ciklusban a Convert.ToInt64(sor["fizetes"]) hívással, ha nincs az IsNull ellenőrzés?

Jóslat ellenőrzése

A Convert.ToInt64(DBNull.Value) InvalidCastException kivételt dob, mert a DBNull nem alakítható számmá. A hiba éppen a NULL fizetésű sornál jelentkezne, ezért a program látszólag jól működik, amíg olyan adat nem kerül a táblába. Az IsNull ellenőrzés a megoldás.

DataView: szűrés és rendezés újabb lekérdezés nélkül

A DataView a DataTable egy nézete: ugyanazokat a sorokat mutatja, de szűrve és rendezve. Az eredeti tábla nem változik. A táblázat (DataGridView) adatforrása lehet a nézet is:

Form1.csC#
private DataTable tabla;
private DataView nezet;

private void Form1_Load(object sender, EventArgs e)
{
tabla = DB.Osszes(); // egyetlen lekérdezés az adatbázisból

nezet = new DataView(tabla);
dgvDolgozok.DataSource = nezet; // a táblázat a nézetet mutatja
}

// példák a szűrőre és a rendezésre:
nezet.RowFilter = "cim = 'Győr'";
nezet.RowFilter = "fizetes >= 400000 AND cim = 'Pécs'";
nezet.RowFilter = "vnev LIKE 'K%'";
nezet.RowFilter = ""; // nincs szűrő: minden sor látszik

nezet.Sort = "fizetes DESC";
nezet.Sort = "cim, fizetes DESC";

lblDarab.Text = nezet.Count + " találat";
new DataView(tabla)Nézetet hoz létre a táblához. A nézet a táblát figyeli, nem másolja.
RowFilterFeltételszöveg, hasonló az SQL WHERE részéhez (=, <>, >, AND, OR, LIKE, IN). A szöveges értéket aposztróf közé kell tenni. Ez nem SQL parancs: az adatbázishoz nem fordul, a memóriában szűr.
SortAz oszlopnevek vesszővel elválasztva, DESC a csökkenőhöz. Az üres szöveg az eredeti sorrendet adja.
nezet.CountA szűrés utáni sorok száma. A tabla.Rows.Count mindig az összes sort mutatja.

Jósolj!

A nézetre RowFilter = "cim = 'Győr'" szűrőt állítasz. Hány sora van ezután a tabla.Rows gyűjteménynek, és hány a nezet-nek? Törlődtek-e a többi város sorai?

Jóslat ellenőrzése

A tabla.Rows továbbra is az összes sort tartalmazza (például 9-et), a nezet csak a győrieket (például 3-at). A szűrő nem töröl semmit: a többi sor a táblában marad, csak a nézetben nem látszik. A szűrő kikapcsolása (RowFilter = "") azonnal visszahozza őket, adatbázis-hívás nélkül.

A kis- és nagybetű, és az ékezetek

A DataTable alapból a kis- és nagybetű különbségét figyelmen kívül hagyja a szűrésnél (CaseSensitive: False), és ezt az aktuális nyelvi beállítás szerint teszi. Ezért a vnev LIKE 'á%' a memóriában megtalálja az Árpád nevet is, szemben az SQLite LIKE operátorával (3.7 lecke). Az ékezet nélküli keresést (arpad → Árpád) viszont ez sem oldja meg: ehhez a gyakorlótárban látsz megoldást.

A szűrőszöveg védelme

A RowFilter nem SQL, nincs mögötte adatbázis, ezért nincs értelme SQL-befecskendezésről beszélni. Ennek ellenére a felhasználó szövegét itt sem szabad nyersen beírni a szűrő szövegébe: egy aposztróf (O'Hara) vagy egy csillag a szűrőt szintaktikailag hibássá teszi (SyntaxErrorException), vagy másképp működik, mint várnád. A RowFilter-hez nincs paraméter, ezért a védelmet a szöveg előkészítése adja:

Form1.csC#
// a RowFilter LIKE mintájában a [ ] * % jeleket szögletes zárójelbe kell tenni,
// az aposztrófot pedig meg kell kettőzni
private static string FilterVedelem(string szoveg)
{
string eredmeny = "";

foreach (char c in szoveg)
{
if (c == '[' || c == ']' || c == '*' || c == '%')
{
eredmeny = eredmeny + "[" + c + "]";
}
else if (c.ToString() == "'")
{
eredmeny = eredmeny + "''";
}
else
{
eredmeny = eredmeny + c;
}
}

return eredmeny;
}

private void SzuroFrissit()
{
string szoveg = txtSzuro.Text.Trim();

if (szoveg.Length == 0)
{
nezet.RowFilter = "";
}
else
{
string v = FilterVedelem(szoveg);
nezet.RowFilter = "vnev LIKE '%" + v + "%' OR knev LIKE '%" + v + "%'";
}

lblDarab.Text = nezet.Count + " találat";
}
[ ] * %A RowFilter LIKE mintájában a * és a % helyettesítő karakter. Szó szerinti használatukhoz szögletes zárójelbe kell tenni őket ([*]).
Aposztróf megkettőzéseA szöveges érték aposztrófok között áll, ezért a szövegben szereplő aposztrófot kettőzni kell (O''Hara).
Miért karakterenként?Ha a szögletes zárójeleket egymás után cserélnéd (Replace), a második csere az elsőnek a saját zárójeleit is átírná. A ciklus minden karaktert egyszer dolgoz fel.
c.ToString() == "'"Az aposztróf karakterét szövegként vizsgáljuk, hogy a kód olvasható maradjon.

Jósolj!

A felhasználó a O'Hara szöveget írja a szűrőmezőbe, de a program FilterVedelem nélkül, egyszerű összefűzéssel építi a RowFilter szövegét. Mi történik?

Jóslat ellenőrzése

A szűrő szövege vnev LIKE '%O'Hara%' lesz: az aposztróf lezárja a szöveges értéket, a maradék szintaktikailag hibás, ezért a RowFilter értékadásakor SyntaxErrorException keletkezik, és a program gépelés közben összeomlik, ha nincs kezelve. A FilterVedelem a szöveget O''Hara alakra cseréli, és a szűrő hibátlan.

Kifejezés-oszlop és Compute

A DataTable számított oszlopokat is tud: az oszlop értékét egy kifejezés adja meg a többi oszlopból, és a program magától frissíti. Az Compute metódus pedig a sorokból összesítést számol (összeg, átlag, darabszám, legnagyobb), a RowFilter-hez hasonló szűrő mellett:

Form1.csC#
tabla = DB.Osszes();

// számított oszlopok
tabla.Columns.Add("evesFizetes", typeof(long), "fizetes * 12");
tabla.Columns.Add("besorolas", typeof(string), "IIF(fizetes >= 400000, 'magas', 'átlagos')");

nezet = new DataView(tabla);
dgvDolgozok.DataSource = nezet;

// összesítés a nézet aktuális szűrőjével
object osszeg = tabla.Compute("SUM(fizetes)", nezet.RowFilter);
object atlag = tabla.Compute("AVG(fizetes)", nezet.RowFilter);

if (osszeg == DBNull.Value)
{
lblOsszesites.Text = "Nincs adat.";
}
else
{
lblOsszesites.Text = $"Összeg: {Convert.ToInt64(osszeg):N0} Ft, átlag: {Convert.ToDouble(atlag):N0} Ft";
}
Columns.Add(név, típus, kifejezés)Számított oszlopot hoz létre. A typeof(long) az érték típusa, a harmadik paraméter a kifejezés.
IIF(feltétel, igaz, hamis)Feltételes kifejezés (mint a C# ?: operátora). A szövegeket itt is aposztróf közé kell tenni.
Compute(kifejezés, szűrő)Összesítést számol a szűrő által átengedett sorokra. A SUM, AVG, COUNT, MIN, MAX használható. Ha nincs egyetlen sor sem, az eredmény DBNull.Value.
{érték:N0}A szöveginterpoláció formátuma: ezres tagolású, tizedes nélküli szám (1.12 lecke).

A számított oszlop NULL-ra NULL

Ha egy dolgozó fizetése NULL, akkor a fizetes * 12 kifejezés is NULL lesz. Az IIF(fizetes >= 400000, ...) ilyenkor a hamis ágat választja (az összehasonlítás nem igaz), ezért a hiányzó fizetésű dolgozó átlagos besorolású lenne. Ha ez nem kívánatos, a kifejezésbe külön ágat kell írni (IIF(fizetes IS NULL, 'nincs adat', ...)).

📷 Képernyőkép helye

Mit tegyél ide: a futó elemző: a táblázatban az éves fizetés és a besorolás oszlop is látszik, alatta az összeg és az átlag felirata.Javasolt fájlnév: winforms-3-8-elemzo.webp · Alt szöveg: A dolgozó-elemző számított oszlopokkal és összesítéssel

Memóriában vagy az adatbázisban szűrjünk?

SzempontSzűrés a memóriában (DataView)Szűrés az adatbázisban (WHERE)
Sebesség gépeléskorAzonnali, nincs adatbázis-hívás.Minden változás új lekérdezés.
AdatmennyiségKis és közepes (néhány ezer sor).Nagy adatmennyiségnél is jó.
Friss adatCsak a betöltés pillanatának állapotát látja.Mindig az aktuális állapotot adja.
Kis- és nagybetű, ékezetA nyelvi beállítás szerint kezeli a kis- és nagybetűt.Az SQLite LIKE csak az ASCII betűknél.
BiztonságNincs SQL, de a szűrőszöveget védeni kell.Paraméterek (3.3, 3.7 lecke).

Ökölszabály

Egy kisebb nyilvántartásnál (néhány ezer sor) töltsd be egyszer az adatokat, és szűrj a memóriában: gyors és kényelmes. Ha az adatok nagyok vagy sokan módosítják őket, a szűrést bízd az adatbázisra. A betöltés után mindig érdemes egy Frissítés gombot adni, amely újra lekérdezi az adatokat.

A táblázatban szerkesztett érték nem kerül az adatbázisba

Ha a DataGridView szerkeszthető, és a felhasználó átír egy cellát, az csak a memóriában lévő DataTable-t módosítja. Az adatbázis változatlan marad, és az adat a program újraindításakor elveszik. Az adatbázis módosításához UPDATE utasítás kell (3.6 lecke), vagy az adapter visszaíró funkciója, amelyet a 3.9 leckében nézünk meg.

Hibakeresés: te vagy a hibadetektív

Mi a baj, és hogyan javítod?

Gondold végig az okot, és csak utána nyisd le a megoldást.

1. A szűrés nem látszik a táblázatban

dgvDolgozok.DataSource = tabla;
nezet.RowFilter = "cim = 'Győr'";
Hiba és javítás

A táblázat a tabla objektumot mutatja, a szűrő viszont a nezet nézeten van, így a táblázatban nem látszik semmi változás. A DataSource a nézet legyen: dgvDolgozok.DataSource = nezet;

2. Hibás oszlopnév a szűrőben

nezet.RowFilter = "fizetés > 400000";
// EvaluateException: Cannot find column [fizetés].
Hiba és javítás

Az oszlop neve fizetes (ékezet nélkül), a szűrőben viszont ékezettel szerepel. A RowFilter és a Sort az oszlop pontos nevét várja. Ellenőrizd a tabla.Columns neveit, vagy írd ki őket ciklussal.

3. Szám összehasonlítása szöveggel

nezet.RowFilter = "fizetes > '400000'";
// EvaluateException: Cannot perform '>' operation on System.Int64 and System.String.
Hiba és javítás

A számot nem szabad aposztróf közé tenni: az aposztróf szöveget jelent. A helyes alak: fizetes > 400000. A szöveges oszlopok értékét viszont aposztróf közé kell írni.

4. InvalidCastException a NULL értéknél

long fizetes = (long)sor["fizetes"];
Hiba és javítás

Ha az érték DBNull.Value, a kicsomagolás hibát dob. Előbb ellenőrizd: sor.IsNull("fizetes"), és használd a Convert.ToInt64 metódust.

5. Az átírt cella nincs az adatbázisban

A táblázatban átírod egy dolgozó nevét, de a program újraindítása után a régi név látszik.

Ok és javítás

A DataGridView csak a memóriában lévő DataTable-t módosította. Az adatbázishoz mentés kell: UPDATE utasítás (3.6 lecke) vagy az adapter visszaírása (3.9 lecke). Ha csak megtekintésre szánod a táblázatot, állítsd a ReadOnly tulajdonságot True értékre.

6. SyntaxErrorException gépelés közben

A szűrőmezőbe aposztrófot gépelve a program leáll.

Ok és javítás

A felhasználó szövege nyersen került a RowFilter szövegébe. A szöveg előkészítése (FilterVedelem) kettőzi az aposztrófot és védi a speciális karaktereket. A RowFilter értékadását érdemes try–catch blokkal is védeni, és hiba esetén a szűrőt üresre állítani.

Mini projekt: dolgozó-elemző

Készíts elemző ablakot a dolgozók adataihoz:

  1. Betöltés indításkor egyetlen lekérdezéssel (DB.Osszes()), két számított oszloppal: evesFizetes és besorolas.
  2. Egy txtSzuro mező élő szűrése a névre a FilterVedelem segítségével.
  3. Egy cmbRendezes lista a rendezéshez (név, fizetés növekvő és csökkenő, város szerint), rögzített szövegekből választva.
  4. Egy lblOsszesites felirat a látható sorok darabszámával, összegével és átlagával; nulla találatnál Nincs adat.
  5. Egy btnFrissit gomb, amely újra betölti az adatokat az adatbázisból, és megtartja a szűrőt és a rendezést.
  6. Teszteld: O'Hara, [, *, % a keresőben, üres szűrő, NULL fizetésű sor.
Megoldás megnyitása (próbáld ki előbb önállóan!)

A szűrés és az összesítés közös metódusban (NezetFrissit()) fut, amelyet minden vezérlő eseménye hív. A btnFrissit csak a tabla adatait cseréli (új Fill), a nézet a szűrőt és a rendezést megtartja, ha ugyanazt a DataView objektumot használod.

Form1.csC#
private DataTable tabla;
private DataView nezet;
private bool betoltve = false;

private void Betoltes()
{
tabla = DB.Osszes();
tabla.Columns.Add("evesFizetes", typeof(long), "fizetes * 12");
tabla.Columns.Add("besorolas", typeof(string), "IIF(fizetes >= 400000, 'magas', 'átlagos')");

nezet = new DataView(tabla);
dgvDolgozok.DataSource = nezet;
}

private void Form1_Load(object sender, EventArgs e)
{
cmbRendezes.Items.Add("Név szerint");
cmbRendezes.Items.Add("Fizetés szerint, csökkenő");
cmbRendezes.Items.Add("Fizetés szerint, növekvő");
cmbRendezes.Items.Add("Város szerint");
cmbRendezes.SelectedIndex = 0;

try
{
Betoltes();
}
catch (Exception ex)
{
DB.Naploz(ex);
MessageBox.Show("Az adatok betöltése nem sikerült.");
return;
}

betoltve = true;
NezetFrissit();
}

private void NezetFrissit()
{
if (!betoltve)
{
return;
}

string szoveg = txtSzuro.Text.Trim();

if (szoveg.Length == 0)
{
nezet.RowFilter = "";
}
else
{
string v = FilterVedelem(szoveg);
nezet.RowFilter = "vnev LIKE '%" + v + "%' OR knev LIKE '%" + v + "%'";
}

string sorrend = "vnev, knev";

if (cmbRendezes.SelectedIndex == 1)
{
sorrend = "fizetes DESC";
}
else if (cmbRendezes.SelectedIndex == 2)
{
sorrend = "fizetes ASC";
}
else if (cmbRendezes.SelectedIndex == 3)
{
sorrend = "cim, vnev";
}

nezet.Sort = sorrend;

object osszeg = tabla.Compute("SUM(fizetes)", nezet.RowFilter);
object atlag = tabla.Compute("AVG(fizetes)", nezet.RowFilter);

if (nezet.Count == 0 || osszeg == DBNull.Value)
{
lblOsszesites.Text = "Nincs adat.";
}
else
{
lblOsszesites.Text = $"{nezet.Count} dolgozó | összeg: {Convert.ToInt64(osszeg):N0} Ft | átlag: {Convert.ToDouble(atlag):N0} Ft";
}
}

private void SzuroValtozott(object sender, EventArgs e)
{
NezetFrissit();
}

private void btnFrissit_Click(object sender, EventArgs e)
{
try
{
DataTable uj = DB.Osszes();

tabla.Clear();

foreach (DataRow sor in uj.Rows)
{
tabla.ImportRow(sor);
}

NezetFrissit();
}
catch (Exception ex)
{
DB.Naploz(ex);
MessageBox.Show("A frissítés nem sikerült.");
}
}
📷 Képernyőkép helye

Mit tegyél ide: a kész elemző a szűrővel és az összesítéssel: „4 dolgozó | összeg: 1 660 000 Ft | átlag: 415 000 Ft”.Javasolt fájlnév: winforms-3-8-kesz.webp · Alt szöveg: A kész dolgozó-elemző szűréssel és összesítéssel

Három önálló gyakorlófeladat

Egyszerre csak egyet csinálj: tervezd meg, írd meg, futtasd, ellenőrizd, és csak utána nyisd ki a megoldást.

1. A legjobban fizetettek listája

Egy gomb írja az lstTop ListBoxba azok nevét, akiknek a fizetése 400 000 Ft felett van, fizetés szerint csökkenően (Varga Dávid: 450 000 Ft).

Egy lehetséges megoldás

A DataTable.Select(szűrő, rendezés) egy DataRow tömböt ad vissza, amelyen a foreach végigmegy. A Select a DataView-hoz hasonló szűrő- és rendezési szintaxist használ, de nem változtatja meg a nézetet.

Form1.csC#
private void btnTop_Click(object sender, EventArgs e)
{
lstTop.Items.Clear();

DataRow[] sorok = tabla.Select("fizetes > 400000", "fizetes DESC");

foreach (DataRow sor in sorok)
{
lstTop.Items.Add(sor["vnev"] + " " + sor["knev"] + ": " + Convert.ToInt64(sor["fizetes"]).ToString("N0") + " Ft");
}
}

2. Városonkénti átlag

A cmbVaros kiválasztásakor a lblVarosAtlag mutassa a város átlagfizetését. A város neve aposztrófot is tartalmazhat.

Egy lehetséges megoldás

A város nevét a Compute szűrőjébe kerüléskor védeni kell (aposztróf megkettőzése). Ha a városnak nincs adata, az eredmény DBNull.Value.

Form1.csC#
private void cmbVaros_SelectedIndexChanged(object sender, EventArgs e)
{
if (cmbVaros.SelectedItem == null)
{
return;
}

string varos = cmbVaros.SelectedItem.ToString().Replace("'", "''");
object atlag = tabla.Compute("AVG(fizetes)", "cim = '" + varos + "'");

if (atlag == DBNull.Value)
{
lblVarosAtlag.Text = "Nincs adat.";
}
else
{
lblVarosAtlag.Text = "Átlagfizetés: " + Convert.ToDouble(atlag).ToString("N0") + " Ft";
}
}

3. Kategória oszlop színezéssel

Adj a táblához egy kategoria kifejezés-oszlopot három értékkel: alacsony (300 000 alatt), közepes (300 000 és 400 000 között), magas (400 000 fölött). A magas sorok hátterét színezd zöldre.

Egy lehetséges megoldás

Az IIF egymásba ágyazható. A színezést a DataGridView.DataBindingComplete eseménye végzi, amely az adatok megjelenítése után fut le. A cellák értékét a Cells["kategoria"].Value adja.

Form1.csC#
tabla.Columns.Add("kategoria", typeof(string), "IIF(fizetes < 300000, 'alacsony', IIF(fizetes <= 400000, 'közepes', 'magas'))");

private void dgvDolgozok_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
foreach (DataGridViewRow sor in dgvDolgozok.Rows)
{
if (Convert.ToString(sor.Cells["kategoria"].Value) == "magas")
{
sor.DefaultCellStyle.BackColor = Color.Honeydew;
}
}
}

Gyakorlótár: 6 további feladat

Három szinten gyakorolhatsz: alap, közepes és kihívás. A dolgozó-elemző projektje kell hozzá (a tabla és a nezet mező). Csak a próbálkozás után nyisd le a megoldást.

Alap szint

1. Az oszlopok kiírása

Egy gomb írja ki az lstOszlopok ListBoxba a tabla oszlopainak nevét és típusát (fizetes: Int64).

Megoldás megnyitása

Az oszlopok a Columns gyűjteményben vannak, a ColumnName a nevét, a DataType.Name a típus nevét adja. Figyeld meg, hogy az SQLite INTEGER oszlopa Int64.

Form1.csC#
lstOszlopok.Items.Clear();

foreach (DataColumn oszlop in tabla.Columns)
{
lstOszlopok.Items.Add(oszlop.ColumnName + ": " + oszlop.DataType.Name);
}

2. Hány dolgozónak nincs városa?

Számold meg a sorok között azokat, amelyeknek a cim oszlopa üres (NULL), és írd ki az eredményt.

Megoldás megnyitása

A sor.IsNull("cim") igaz a hiányzó értéknél. Ugyanez a szűrővel is megoldható: tabla.Select("cim IS NULL").Length.

Form1.csC#
int db = 0;

foreach (DataRow sor in tabla.Rows)
{
if (sor.IsNull("cim"))
{
db = db + 1;
}
}

lblEredmeny.Text = $"Város nélküli dolgozók: {db}";

// ugyanez a szűrővel:
int db2 = tabla.Select("cim IS NULL").Length;

Közepes szint

3. Rendezés két szempont szerint

A nézet legyen rendezve városok szerint, azon belül fizetés szerint csökkenően, és a táblázat is így jelenjen meg.

Megoldás megnyitása

A Sort több oszlopot is elfogad, vesszővel elválasztva, oszloponként külön DESC megadásával.

Form1.csC#
nezet.Sort = "cim ASC, fizetes DESC";

4. A legjobban fizetett dolgozó

Írd ki a legjobban fizetett dolgozó nevét. Mit teszel, ha a tábla üres?

Megoldás megnyitása

A Select eredménye tömb: az első elem a legjobban fizetett. Üres tábla esetén a tömb hossza nulla, és a [0] hibát (IndexOutOfRangeException) okozna, ezért előbb ellenőrizzük a hosszt. A NULL fizetésűeket csökkenő rendezésnél a végére teszi.

Form1.csC#
DataRow[] sorok = tabla.Select("fizetes IS NOT NULL", "fizetes DESC");

if (sorok.Length == 0)
{
lblEredmeny.Text = "Nincs adat.";
}
else
{
lblEredmeny.Text = "Legjobban fizetett: " + sorok[0]["vnev"] + " " + sorok[0]["knev"];
}

Kihívás szint

5. Saját DataTable adatbázis nélkül

Készíts kódból egy bevásárlókosár-táblát (termek, db, ar, és egy kifejezés-oszlop: osszeg = db * ar), vegyél fel három sort, és jelenítsd meg a táblázatban. Egy lblVegosszeg mutassa az összesített végösszeget.

Megoldás megnyitása

A DataTable adatbázis nélkül is használható: az oszlopokat és a sorokat magad adod meg. A Rows.Add az értékeket az oszlopok sorrendjében várja. A kifejezés-oszlopnak nem adsz értéket, magától számolódik.

Form1.csC#
DataTable kosar = new DataTable();

kosar.Columns.Add("termek", typeof(string));
kosar.Columns.Add("db", typeof(int));
kosar.Columns.Add("ar", typeof(int));
kosar.Columns.Add("osszeg", typeof(int), "db * ar");

kosar.Rows.Add("alma", 3, 300);
kosar.Rows.Add("kenyér", 1, 500);
kosar.Rows.Add("tej", 2, 400);

dgvKosar.DataSource = kosar;

object vegosszeg = kosar.Compute("SUM(osszeg)", "");
lblVegosszeg.Text = "Végösszeg: " + vegosszeg + " Ft";

6. Ékezet nélküli keresés

A kereső találja meg az Árpád nevet az arpad, a Kovács nevet pedig a kovacs beírásakor is. Hogyan oldod meg a DataTable-ben?

Megoldás megnyitása

Készítünk egy keresőoszlopot, amelybe a név ékezet nélküli, kisbetűs változata kerül, és a keresőszöveget is ugyanígy alakítjuk át. Az ékezetek eltávolítása a normalizálás segítségével történik: a Normalize(NormalizationForm.FormD) az ékezetes betűt alapbetűre és külön ékezetjelre bontja (például á → a és egy ékezetjel), a ciklus pedig az ékezetjeleket kihagyja. A CharUnicodeInfo a System.Globalization, a NormalizationForm a System.Text névtérben van.

Form1.cs (using System.Globalization; using System.Text;)C#
private static string Ekezettelen(string szoveg)
{
string felbontott = szoveg.Normalize(NormalizationForm.FormD);
string eredmeny = "";

foreach (char c in felbontott)
{
if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
{
eredmeny = eredmeny + c;
}
}

return eredmeny.Normalize(NormalizationForm.FormC).ToLower();
}

// a betöltés után: keresőoszlop minden sorhoz
tabla.Columns.Add("keresheto", typeof(string));

foreach (DataRow sor in tabla.Rows)
{
sor["keresheto"] = Ekezettelen(sor["vnev"] + " " + sor["knev"]);
}

// a szűrésnél:
string v = FilterVedelem(Ekezettelen(txtSzuro.Text.Trim()));
nezet.RowFilter = "keresheto LIKE '%" + v + "%'";

Gyors önellenőrzés

Válaszolj fejben, és csak utána nyisd le a választ.

1. Mi a különbség a DataTable és a DataView között?

A DataTable az adatokat tárolja, a DataView annak szűrt és rendezett nézete. A nézet nem módosítja a táblát.

2. Mit jelent a RowFilter, és hol hajtódik végre?

Szűrőkifejezés, amely a memóriában szűri a nézet sorait. Nem SQL, az adatbázishoz nem fordul.

3. Mi a baj az (int)sor["fizetes"] kifejezéssel?

Az SQLite egész számai Int64 típusúak, ezért a kicsomagolás InvalidCastException hibát dob. A Convert.ToInt32 vagy Convert.ToInt64 megoldja. A NULL értéknél előbb az IsNull vizsgálat kell.

4. Miért kell védeni a RowFilter szövegét?

Mert az aposztróf és a speciális karakterek ([ ] * %) szintaktikai hibát okozhatnak, vagy másként működnek, mint várod. Az aposztrófot kettőzni kell, a többit szögletes zárójelbe tenni.

5. Mire való a kifejezés-oszlop?

A többi oszlopból számolt értéket mutat (például fizetes * 12), és automatikusan frissül.

6. Mikor szűrj a memóriában, és mikor az adatbázisban?

Kis és közepes adatmennyiségnél a memóriában gyorsabb és kényelmesebb. Nagy adatnál vagy többfelhasználós használatnál az adatbázisban, hogy mindig friss adatot kapj.

7. A táblázatban átírtam egy cellát. Bekerült az adatbázisba?

Nem. Csak a memóriában lévő DataTable módosult. Az adatbázis módosításához UPDATE vagy az adapter visszaírása kell.

Mit tudsz már?

  • Olvasod a DataTable sorait, és kezeled a NULL értékeket.
  • A DataView segítségével szűrsz és rendezel újabb lekérdezés nélkül.
  • Védeni tudod a szűrőszöveget.
  • Kifejezés-oszlopot adsz, és összesítést kérsz a Compute segítségével.
  • Eldöntöd, hol érdemes szűrni: a memóriában vagy az adatbázisban.

Következő lecke: űrlap és CRUD műveletek

A DataTable már a kezedben van. A következő leckében a teljes CRUD ciklust (létrehozás, olvasás, módosítás, törlés) egy rendezett űrlapban fogod össze.