Grafikus C# – Windows Forms tanfolyam › 3.6 lecke

Windows Forms · 3.6 lecke · adatok módosítása

INSERT, UPDATE és DELETE – adatok felvétele, módosítása és törlése

Eddig főleg olvastad az adatbázist. Egy valódi nyilvántartásban viszont új sorokat is felveszel, a meglévőket javítod, és a feleslegeseket törlöd. Ebben a leckében a három módosító SQL utasítást tanulod meg, és egy dolgozószerkesztő ablakot építesz, amelyben a táblázat egy sorára kattintva a mezők kitöltődnek, a gombok pedig mentenek, vagy törölnek.

  • kb. 110 perc
  • közepes szint
  • INSERT, UPDATE, DELETE
Diák C# programozást gyakorol egy tanulósarokban

A lecke végére

  • az INSERT, az UPDATE és a DELETE utasítást helyesen, WHERE feltétellel használod;
  • tudod, mi történik, ha a WHERE kimarad, és hogyan védekezz a DB Browserben (Revert Changes);
  • az ExecuteNonQuery visszatérési értékéből (az érintett sorok száma) megállapítod, sikerült-e a művelet;
  • az új sor azonosítóját a LastInsertRowId adja meg;
  • a hiányzó értéket DBNull.Value alakban mented (NULL);
  • a táblázat kijelölését az ablak mezőivel összekötöd, és a törlést megerősítéssel védi.

Mielőtt elkezded

Ehhez a leckéhez kell a 3.5 lecke projektje (DB.cs, az adatok tábla a 9 sorral), a paraméterezett lekérdezés (3.3 lecke), a DataGridView (3.4 lecke) és a bool visszatérésű metódus (1.11 lecke).

3.5 SELECT, WHERE, ORDER BY →

A három módosító utasítás

SQLSQLite
-- új sor felvétele
INSERT INTO adatok (vnev, knev, fizetes, cim)
VALUES ('Fekete', 'Dóra', 390000, 'Győr');

-- meglévő sor módosítása: a WHERE adja meg, melyiket
UPDATE adatok
SET fizetes = 420000, cim = 'Veszprém'
WHERE id = 3;

-- sor törlése: a WHERE adja meg, melyiket
DELETE FROM adatok
WHERE id = 3;
INSERT INTO tábla (oszlopok) VALUES (értékek)Új sort vesz fel. Az id-t az adatbázis adja (automatikus számozás).
UPDATE tábla SET oszlop = érték, ... WHERE feltételA megadott oszlopokat módosítja azokban a sorokban, amelyekre a feltétel igaz.
DELETE FROM tábla WHERE feltételTörli azokat a sorokat, amelyekre a feltétel igaz. Az oszlopokat nem kell megadni, mert sorokat töröl.
WHERE id = ...A módosításnál és a törlésnél az elsődleges kulcs (id) a legbiztonságosabb feltétel, mert egyetlen sort azonosít. A név nem jó, mert lehet két azonos nevű dolgozó.

Jósolj!

Az UPDATE adatok SET cim = 'Győr' WHERE id = 99 utasítást futtatod, de nincs 99-es azonosítójú sor. Hibát kapsz? Hány sor módosul?

Jóslat ellenőrzése

Nem kapsz hibát, és nulla sor módosul. Az SQL szempontjából az utasítás érvényes: a feltételnek egyetlen sor sem felel meg, ezért nincs mit módosítani. Ezért fontos, hogy a C# program ellenőrizze az érintett sorok számát (az ExecuteNonQuery visszatérési értékét), és ne higgye el, hogy a módosítás sikerült, csak mert nem keletkezett kivétel.

A WHERE védelme: mi történik nélküle?

WHERE nélkül minden sor érintett!

Az UPDATE adatok SET fizetes = 0 utasítás minden dolgozó fizetését nullára állítja, a DELETE FROM adatok pedig a teljes táblát kiüríti. Az SQLite nem kérdez, és nincs „Visszavonás” gomb. A hiba az egyik leggyakoribb és legdrágább.

Szerencsére a DB Browser védelmet ad: minden módosítás csak átmenetileg érvényes, amíg ki nem írod a változásokat (Write Changes, Ctrl+S). Ha elrontottál valamit, a Revert Changes gomb eldobja az utolsó írás óta történt összes módosítást. Próbáld ki biztonságosan:

  1. Futtasd az Execute SQL lapon: UPDATE adatok SET fizetes = 0;
  2. Nézd meg a Browse Data lapon az adatok táblát: mindenkinek nulla a fizetése.
  3. Kattints a Revert Changes gombra: a fizetések visszaállnak.

A jó szokás a próbálkozás sorrendje: a törlés vagy módosítás előtt futtasd ugyanazt a WHERE feltételt egy SELECT-tel, és nézd meg, mely sorokat érinti. Csak ha jó, írd át UPDATE-re vagy DELETE-re.

SQLSQLite
-- 1. előnézet: mely sorokat érintene?
SELECT * FROM adatok WHERE cim = 'Pécs' AND fizetes < 400000;

-- 2. ha jó, ugyanaz a feltétel a törléshez
DELETE FROM adatok WHERE cim = 'Pécs' AND fizetes < 400000;
📷 Képernyőkép helye

Mit tegyél ide: a DB Browser ablaka, amelyben a Write Changes és a Revert Changes gomb látszik (a változtatás után aktívak).Javasolt fájlnév: winforms-3-6-revert.webp · Alt szöveg: A DB Browser Write Changes és Revert Changes gombja

A módosító metódusok a DB osztályban

A három művelet a DB.cs osztályba kerül. A felhasználó adatai paraméterként mennek (3.3 lecke). Az ExecuteNonQuery az érintett sorok számát adja vissza, amelyből eldönthetjük, sikerült-e a művelet. A felvételnél a LastInsertRowId megadja az új sor azonosítóját:

DB.cs (a DB osztályba)C#
// üres cím helyett NULL kerül az adatbázisba
private static object CimErtek(string cim)
{
if (cim.Length == 0)
{
return DBNull.Value;
}

return cim;
}

public static long DolgozoFelvetel(string vnev, string knev, int fizetes, string cim)
{
using (SQLiteConnection kapcsolat = UjKapcsolat())
{
kapcsolat.Open();

string sql = "INSERT INTO adatok (vnev, knev, fizetes, cim) VALUES (@vnev, @knev, @fizetes, @cim)";

using (SQLiteCommand parancs = new SQLiteCommand(sql, kapcsolat))
{
parancs.Parameters.AddWithValue("@vnev", vnev);
parancs.Parameters.AddWithValue("@knev", knev);
parancs.Parameters.AddWithValue("@fizetes", fizetes);
parancs.Parameters.AddWithValue("@cim", CimErtek(cim));
parancs.ExecuteNonQuery();
}

return kapcsolat.LastInsertRowId;
}
}

public static bool DolgozoModositas(int id, string vnev, string knev, int fizetes, string cim)
{
using (SQLiteConnection kapcsolat = UjKapcsolat())
{
kapcsolat.Open();

string sql = "UPDATE adatok SET vnev = @vnev, knev = @knev, fizetes = @fizetes, cim = @cim WHERE id = @id";

using (SQLiteCommand parancs = new SQLiteCommand(sql, kapcsolat))
{
parancs.Parameters.AddWithValue("@vnev", vnev);
parancs.Parameters.AddWithValue("@knev", knev);
parancs.Parameters.AddWithValue("@fizetes", fizetes);
parancs.Parameters.AddWithValue("@cim", CimErtek(cim));
parancs.Parameters.AddWithValue("@id", id);

int erintett = parancs.ExecuteNonQuery();
return erintett == 1;
}
}
}

public static bool DolgozoTorles(int id)
{
using (SQLiteConnection kapcsolat = UjKapcsolat())
{
kapcsolat.Open();

using (SQLiteCommand parancs = new SQLiteCommand("DELETE FROM adatok WHERE id = @id", kapcsolat))
{
parancs.Parameters.AddWithValue("@id", id);

return parancs.ExecuteNonQuery() == 1;
}
}
}

public static DataTable Osszes()
{
DataTable tabla = new DataTable();

using (SQLiteConnection kapcsolat = UjKapcsolat())
{
kapcsolat.Open();

using (SQLiteDataAdapter adapter = new SQLiteDataAdapter("SELECT id, vnev, knev, fizetes, cim FROM adatok ORDER BY vnev, knev", kapcsolat))
{
adapter.Fill(tabla);
}
}

return tabla;
}
ExecuteNonQuery() == 1Az érintett sorok száma egy, tehát a módosítás vagy törlés megtalálta a sort. Nulla azt jelenti, hogy nincs ilyen azonosító (például közben törölték).
kapcsolat.LastInsertRowIdAz utoljára felvett sor automatikus azonosítója. A long típusú szám, ezért a metódus is long értéket ad.
DBNull.ValueAz adatbázis NULL értékének C# megfelelője. Az üres szöveg és a NULL különbözik: üres cím helyett NULL kerül az adatbázisba, így később az IS NULL működik.
private static object CimErtek(...)Segédmetódus, amely a cím szövegéből vagy DBNull.Value értéket vagy a szöveget adja. A visszatérési típus object, mert a kétféle érték közös típusa az.

Jósolj!

Egy dolgozót a DolgozoTorles(5) hívással töröltél. Ismét meghívod a DolgozoTorles(5) metódust. Mit ad vissza másodszor? Dob-e kivételt?

Jóslat ellenőrzése

false értéket ad, és nem dob kivételt: a törlés végrehajtódik, de nulla sort érint, ezért ExecuteNonQuery() == 1 hamis. A hívó program így tudja, hogy nem történt valódi törlés, és érthető üzenetet adhat.

Az ablak: kijelölés és szerkesztőmezők

Az ablakon a táblázat mellett a szerkesztés mezői állnak:

dgvDolgozokA táblázat (ReadOnly: True, AllowUserToAddRows: False, MultiSelect: False, SelectionMode: FullRowSelect).
txtVnev, txtKnev, txtCimA név és a város szövegmezője.
nudFizetesA fizetés (Maximum: 10000000, Increment: 10000).
btnUj, btnMent, btnTorolÚj dolgozó, Mentés, Törlés. A btnMent felirata a helyzethez igazodik.
lblUzenetA visszajelző felirat.

A programnak két helyzetet kell megkülönböztetnie: új dolgozót veszünk fel (INSERT), vagy egy meglévőt módosítunk (UPDATE). Ezt egy kijeloltId mező mondja meg: 0 az új dolgozót, a nullánál nagyobb szám a szerkesztett sor azonosítóját jelenti.

Form1.csC#
private int kijeloltId = 0;
private bool frissites = false;

private void Uzenet(string szoveg, bool hiba)
{
lblUzenet.Text = szoveg;
lblUzenet.ForeColor = hiba ? Color.Firebrick : Color.SeaGreen;
}

private void Uj()
{
kijeloltId = 0;
txtVnev.Clear();
txtKnev.Clear();
txtCim.Clear();
nudFizetes.Value = 0;
btnMent.Text = "Új dolgozó felvétele";
txtVnev.Focus();
}

private void MezokTolt()
{
if (dgvDolgozok.CurrentRow == null)
{
return;
}

DataGridViewRow sor = dgvDolgozok.CurrentRow;

kijeloltId = Convert.ToInt32(sor.Cells["id"].Value);
txtVnev.Text = Convert.ToString(sor.Cells["vnev"].Value);
txtKnev.Text = Convert.ToString(sor.Cells["knev"].Value);
txtCim.Text = Convert.ToString(sor.Cells["cim"].Value);

object fizetes = sor.Cells["fizetes"].Value;
nudFizetes.Value = fizetes == DBNull.Value ? 0 : Convert.ToDecimal(fizetes);

btnMent.Text = "Módosítás mentése";
}

private void dgvDolgozok_SelectionChanged(object sender, EventArgs e)
{
if (!frissites)
{
MezokTolt();
}
}

private void SorKijelolese(int id)
{
foreach (DataGridViewRow sor in dgvDolgozok.Rows)
{
if (Convert.ToInt32(sor.Cells["id"].Value) == id)
{
dgvDolgozok.CurrentCell = sor.Cells["vnev"];
sor.Selected = true;
return;
}
}
}

private void Frissit(int kijelolendoId)
{
frissites = true;

try
{
dgvDolgozok.DataSource = DB.Osszes();
dgvDolgozok.Columns["vnev"].HeaderText = "Vezetéknév";
dgvDolgozok.Columns["knev"].HeaderText = "Keresztnév";
dgvDolgozok.Columns["fizetes"].HeaderText = "Fizetés (Ft)";
dgvDolgozok.Columns["cim"].HeaderText = "Város";

SorKijelolese(kijelolendoId);
}
catch (Exception ex)
{
MessageBox.Show("Nem sikerült betölteni a listát: " + ex.Message);
}
finally
{
frissites = false;
}
}

private void Form1_Load(object sender, EventArgs e)
{
Uj();
Frissit(0);
}
kijeloltId0: új dolgozó; más szám: a szerkesztett sor azonosítója. Ez dönti el a mentésnél, hogy INSERT vagy UPDATE kell.
Convert.ToString(...)A DBNull értéket üres szöveggé alakítja, így a NULL című sor nem okoz hibát. A Convert.ToDecimal(DBNull.Value) viszont hibát dobna, ezért a fizetésnél külön ellenőrzünk.
frissites jelzőA DataSource beállítása közben a táblázat kijelölése többször változik, és minden változás kiváltaná a SelectionChanged eseményt. A jelző ilyenkor leállítja a felesleges mezőtöltést (2.11 lecke).
finallyA jelzőt hiba esetén is visszaállítja, különben a kijelölés végleg „lefagyna”.
SorKijelolese(id)Az azonosító alapján keresi meg a sort. Mentés után az új vagy módosított sor így kijelölve marad, akkor is, ha a rendezés miatt máshová került.

Mentés és törlés a gombokból

Form1.csC#
private void btnUj_Click(object sender, EventArgs e)
{
dgvDolgozok.ClearSelection();
Uj();
Uzenet("", false);
}

private void btnMent_Click(object sender, EventArgs e)
{
string vnev = txtVnev.Text.Trim();
string knev = txtKnev.Text.Trim();
string cim = txtCim.Text.Trim();
int fizetes = (int)nudFizetes.Value;

if (vnev.Length == 0 || knev.Length == 0)
{
Uzenet("A vezeték- és a keresztnév kötelező!", true);
return;
}

try
{
if (kijeloltId == 0)
{
long ujId = DB.DolgozoFelvetel(vnev, knev, fizetes, cim);

Uzenet("Az új dolgozó felvéve.", false);
Frissit((int)ujId);
MezokTolt();
}
else
{
if (DB.DolgozoModositas(kijeloltId, vnev, knev, fizetes, cim))
{
Uzenet("A módosítás mentve.", false);
Frissit(kijeloltId);
}
else
{
Uzenet("A dolgozó nem található (közben törölték?).", true);
Frissit(0);
}
}
}
catch (Exception ex)
{
MessageBox.Show("Adatbázis-hiba: " + ex.Message);
}
}

private void btnTorol_Click(object sender, EventArgs e)
{
if (kijeloltId == 0)
{
Uzenet("Előbb válassz ki egy dolgozót a táblázatból!", true);
return;
}

DialogResult valasz = MessageBox.Show(
$"Biztosan törlöd: {txtVnev.Text} {txtKnev.Text}? A művelet nem vonható vissza.",
"Törlés",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning);

if (valasz != DialogResult.Yes)
{
return;
}

try
{
if (DB.DolgozoTorles(kijeloltId))
{
Uzenet("A dolgozó törölve.", false);
}
else
{
Uzenet("A dolgozó már nem létezett.", true);
}

Frissit(0);
Uj();
}
catch (Exception ex)
{
MessageBox.Show("Adatbázis-hiba: " + ex.Message);
}
}
kijeloltId == 0Új dolgozó: INSERT. Egyébként UPDATE a kijelölt azonosítóval.
Frissit((int)ujId)Az új sor azonosítóját (long) int típusra alakítjuk, hogy a SorKijelolese megtalálja. Az azonosítók ebben a kis táblában belefér az int tartományába.
Megerősítés törlés előttA törlés nem visszavonható, ezért a MessageBox kérdése megvédi a felhasználót. A felirat tartalmazza a törlendő nevet, így látja, mit töröl.
A visszajelzés három ágaSiker (zöld üzenet), „nincs ilyen sor” (piros üzenet, a frissítés mindenképp megtörténik), és váratlan hiba (MessageBox).

Jósolj!

Rákattintasz egy dolgozóra, majd a Új dolgozó gombra, és beírsz egy nevet. A Mentés gombbal mentesz. INSERT vagy UPDATE fut le? Mi lenne, ha az Uj() metódus nem nullázná a kijeloltId mezőt?

Jóslat ellenőrzése

Az INSERT fut le, mert az Uj() a kijeloltId értékét nullára állította. Ha ez kimaradna, a program a korábban kijelölt dolgozó azonosítójával UPDATE utasítást futtatna, és az új név felülírná a meglévő dolgozó adatait. Ezért kell az Uj() metódusnak mindig nullázni a jelzőt.

📷 Képernyőkép helye

Mit tegyél ide: a futó szerkesztő: a táblázatban egy kijelölt sor, a mezők kitöltve, a „Módosítás mentése” és a Törlés gomb, a zöld „A módosítás mentve.” üzenet.Javasolt fájlnév: winforms-3-6-szerkeszto.webp · Alt szöveg: A dolgozószerkesztő egy kijelölt dolgozóval

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. Mindenkinek ugyanaz lett a város

UPDATE adatok SET cim = @cim
Hiba és javítás

Hiányzik a WHERE id = @id, ezért az utasítás minden sort módosít. Ha ez véletlenül megtörtént, az éles adatbázisban nincs visszaút, a DB Browserben a Revert Changes még segít, ha nem írtad ki a változtatást. Mindig tedd a WHERE feltételt az UPDATE és a DELETE végére, és előbb próbáld ki SELECT-tel.

2. A mentés új sort hoz létre a módosítás helyett

Egy meglévő dolgozó nevét átírod, mentesz, és a táblázatban két sor jelenik meg.

Ok és javítás

A program INSERT utasítást futtat az UPDATE helyett. Ellenőrizd, hogy a kijeloltId a sor kijelölésekor beállítódik-e (a MezokTolt() metódusban), és hogy a SelectionChanged esemény a táblázathoz van-e kötve a tervezőben (Events nézet).

3. A módosítás után más sor lesz kijelölve

Mentés után a táblázat első sora kerül kijelölésre, és a mezők a másik dolgozó adatait mutatják.

Ok és javítás

A Frissit() újra betölti a táblázatot, és a kijelölés elveszik. A megoldás: az azonosítót tovább kell adni a Frissit(kijeloltId) hívásnak, amely a SorKijelolese(id) segítségével az azonosító alapján újra kijelöli a sort.

4. InvalidCastException a NULL címnél

nudFizetes.Value = (decimal)sor.Cells["fizetes"].Value;
Hiba és javítás

Ha a fizetés NULL az adatbázisban, a cella értéke DBNull.Value, amelyet nem lehet decimal típusra alakítani. Előbb vizsgáld meg: fizetes == DBNull.Value ? 0 : Convert.ToDecimal(fizetes). A szövegmezőknél a Convert.ToString elintézi a DBNull értéket.

5. A rossz sor törlődik

DB.DolgozoTorles(dgvDolgozok.CurrentRow.Index + 1);
Hiba és javítás

A sor sorszáma a táblázatban nem azonos az adatbázis azonosítójával: a rendezés, a törlések és a szűrés után eltérnek. A törlésnél mindig az azonosítót (az id oszlop értékét) használd: DB.DolgozoTorles(kijeloltId).

6. A módosítás nem látszik az adatbázisban

A programban a módosítás látszik, de a DB Browserben nem.

Ok és javítás

A DB Browser a fájl régi állapotát mutatja. Zárd be és nyisd meg újra az adatbázist, vagy kattints a Refresh gombra a Browse Data lapon. Ha a DB Browserben van ki nem írt változtatás (Write Changes hiányzik), a C# program database is locked hibát kaphat. Írd ki a változtatásokat (Ctrl+S), vagy zárd be a DB Browsert, amíg a programot futtatod.

Mini projekt: dolgozó-nyilvántartó

Állítsd össze a teljes nyilvántartót:

  1. Az adatok tábla a 3.5 leckéből (9 sorral), a DB.cs osztály a három módosító metódussal és az Osszes() metódussal.
  2. Az ablak a táblázattal, a négy mezővel, az Új, Mentés és Törlés gombbal és az üzenet-felirattal.
  3. A kijelölés betölti a mezőket, a Mentés dönt az INSERT és az UPDATE között, a Törlés megerősítést kér.
  4. Új követelmény: a fizetés ne lehessen negatív, és a nevek legfeljebb 40 karakteresek legyenek (MaxLength). Hiba esetén a program ne mentsen, és jelezzen.
  5. Tesztelj: új sor üres címmel (NULL), a cím törlése egy meglévő soron, módosítás utáni kijelölés, törlés és újratöltés, a program újraindítása.
Megoldás megnyitása (próbáld ki előbb önállóan!)

A teljes kód a leckében szerepel (DB.cs, Form1.cs). A negatív fizetést a NumericUpDown Minimum tulajdonsága (0) kizárja, ezért külön kódot sem kell írni. A hosszkorlátot a MaxLength tulajdonság teszi lehetővé mindhárom szövegmezőn. Ha ezeket kihagynád a tervezőből, az ellenőrzés a btnMent_Click elején történne:

Form1.csC#
if (vnev.Length > 40 || knev.Length > 40 || cim.Length > 40)
{
Uzenet("A nevek és a város legfeljebb 40 karakteresek lehetnek!", true);
return;
}

if (fizetes < 0)
{
Uzenet("A fizetés nem lehet negatív!", true);
return;
}
📷 Képernyőkép helye

Mit tegyél ide: a kész nyilvántartó, amelyben a táblázat az összes dolgozót mutatja, az egyik sor kijelölve, és a mezők kitöltve.Javasolt fájlnév: winforms-3-6-kesz.webp · Alt szöveg: A kész dolgozó-nyilvántartó

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. Fizetésemelés mindenkinek

Egy nudSzazalek (1–50) mezőben megadott százalékkal emeld meg az összes dolgozó fizetését egy gombnyomásra. A művelet előtt kérj megerősítést, utána írd ki, hány dolgozót érintett.

Egy lehetséges megoldás

Itt szándékosan nincs WHERE, mert mindenkit érint, ezért a megerősítés kötelező. Az egész számok osztása a tizedest levágja (SQLite: egész osztás), ezért a szorzást az osztás előtt végezzük. Az ExecuteNonQuery visszaadja az érintett sorok számát.

DB.cs és Form1.csC#
// DB.cs
public static int FizetesEmeles(int szazalek)
{
using (SQLiteConnection kapcsolat = UjKapcsolat())
{
kapcsolat.Open();

using (SQLiteCommand parancs = new SQLiteCommand("UPDATE adatok SET fizetes = fizetes * (100 + @szazalek) / 100 WHERE fizetes IS NOT NULL", kapcsolat))
{
parancs.Parameters.AddWithValue("@szazalek", szazalek);
return parancs.ExecuteNonQuery();
}
}
}

// Form1.cs
private void btnEmeles_Click(object sender, EventArgs e)
{
int szazalek = (int)nudSzazalek.Value;

DialogResult valasz = MessageBox.Show($"Biztosan emeled mindenki fizetését {szazalek} %-kal?", "Fizetésemelés", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);

if (valasz == DialogResult.Yes)
{
int db = DB.FizetesEmeles(szazalek);
Uzenet($"{db} dolgozó fizetése emelkedett.", false);
Frissit(kijeloltId);
}
}

2. Az összes törlése beírással

Egy Összes törlése gomb csak akkor töröljön mindent, ha a felhasználó egy felugró ablakban begépeli a TÖRLÉS szót. (Gondold végig: miért nehezebb véletlenül rákattintani, mint egy egyszerű Igen–Nem kérdésre?)

Egy lehetséges megoldás

A veszélyes, nem visszavonható műveleteknél a begépeléssel való megerősítés lassítja a felhasználót, és megakadályozza a véletlen kattintást. A beírás bekéréséhez a Windows Formsban nincs kész egyszerű ablak, ezért egy kis, külön Form-ot használunk, vagy egyszerűbb megoldásként egy txtMegerosit mező és egy gomb teszi lehetővé a törlést, amíg a mezőben a TÖRLÉS szó áll.

Form1.csC#
private void txtMegerosit_TextChanged(object sender, EventArgs e)
{
btnOsszesTorol.Enabled = txtMegerosit.Text == "TÖRLÉS";
}

private void btnOsszesTorol_Click(object sender, EventArgs e)
{
using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
{
kapcsolat.Open();

using (SQLiteCommand parancs = new SQLiteCommand("DELETE FROM adatok", kapcsolat))
{
int db = parancs.ExecuteNonQuery();
Uzenet($"{db} dolgozó törölve.", false);
}
}

txtMegerosit.Clear();
Frissit(0);
Uj();
}

3. Dolgozó másolása

Egy Másolás gomb készítsen a kijelölt dolgozóról másolatot (új azonosítóval), és jelölje ki az új sort.

Egy lehetséges megoldás

Az INSERT ... SELECT egy lekérdezés eredményét veszi fel új sorként. Az id oszlopot nem másoljuk, így az adatbázis új azonosítót ad.

DB.cs és Form1.csC#
// DB.cs
public static long DolgozoMasolas(int id)
{
using (SQLiteConnection kapcsolat = UjKapcsolat())
{
kapcsolat.Open();

string sql = "INSERT INTO adatok (vnev, knev, fizetes, cim) SELECT vnev, knev, fizetes, cim FROM adatok WHERE id = @id";

using (SQLiteCommand parancs = new SQLiteCommand(sql, kapcsolat))
{
parancs.Parameters.AddWithValue("@id", id);
parancs.ExecuteNonQuery();
}

return kapcsolat.LastInsertRowId;
}
}

// Form1.cs
private void btnMasol_Click(object sender, EventArgs e)
{
if (kijeloltId == 0)
{
Uzenet("Előbb válassz ki egy dolgozót!", true);
return;
}

long ujId = DB.DolgozoMasolas(kijeloltId);
Frissit((int)ujId);
MezokTolt();
}

Gyakorlótár: 6 további feladat

Három szinten gyakorolhatsz: alap, közepes és kihívás. Az első négyet a DB Browser Execute SQL lapján oldd meg (előbb próbáld ki, utána vond vissza a Revert Changes gombbal, ha nem szeretnéd megtartani), az utolsó kettőhöz a dolgozó-nyilvántartó projektje kell. Csak a próbálkozás után nyisd le a megoldást.

Alap szint

1. Két új dolgozó

Vegyél fel két új dolgozót egyetlen futtatással, majd ellenőrizd a SELECT utasítással.

Megoldás megnyitása

Az Execute SQL lapon több utasítást is futtathatsz, ha pontosvesszővel zárod őket. A végén a SELECT az eredményt mutatja.

SQLSQLite
INSERT INTO adatok (vnev, knev, fizetes, cim) VALUES ('Orbán', 'Réka', 400000, 'Pécs');
INSERT INTO adatok (vnev, knev, fizetes, cim) VALUES ('Sipos', 'Tamás', 355000, 'Veszprém');

SELECT * FROM adatok ORDER BY id DESC LIMIT 2;

2. Egy sor javítása

Javítsd ki Kiss Márta (győri dolgozó) fizetését 395 000-re. Hogyan biztosítod, hogy csak őt érintse?

Megoldás megnyitása

Előbb keresd meg az azonosítóját egy SELECT segítségével, majd az id alapján módosíts. A név alapján módosítani kockázatos, mert több azonos nevű dolgozó is lehet.

SQLSQLite
SELECT id, vnev, knev FROM adatok WHERE vnev = 'Kiss' AND knev = 'Márta';

UPDATE adatok SET fizetes = 395000 WHERE id = 5; -- az előbb megtalált azonosító (nálad más is lehet)

Közepes szint

3. Fizetésemelés egy városban

A győri dolgozók fizetését emeld 5%-kal (egész forintra kerekítve). Előbb nézd meg, kiket érint.

Megoldás megnyitása

A ROUND kerekít. A szorzás előtt kell a százalékot tizedes számmá alakítani (1.05), különben az egész osztás levágná az eredményt. Az előnézet ugyanazt a WHERE feltételt használja.

SQLSQLite
SELECT vnev, knev, fizetes, ROUND(fizetes * 1.05) AS uj_fizetes
FROM adatok WHERE cim = 'Győr';

UPDATE adatok SET fizetes = ROUND(fizetes * 1.05) WHERE cim = 'Győr';

4. Biztonságos törlés

Töröld azokat a dolgozókat, akiknek nincs megadva a címük. Mutasd meg, hogyan győződsz meg előbb arról, hogy csak őket érinti.

Megoldás megnyitása

A hiányzó értéket az IS NULL vizsgálja (nem az = NULL). Először a SELECT mutatja a törlendő sorokat, utána ugyanaz a feltétel a DELETE-ben.

SQLSQLite
SELECT * FROM adatok WHERE cim IS NULL;

DELETE FROM adatok WHERE cim IS NULL;

Kihívás szint

5. Hányat érintett a művelet?

A mentés és a törlés után a lblUzenet írja ki, hány sort érintett a művelet (például 1 sor módosult). Mit tegyél, ha az ExecuteNonQuery 0-t ad?

Megoldás megnyitása

A DB metódus int értéket ad vissza az érintett sorok számáról, és az ablak ez alapján írja ki az üzenetet. Nulla találat esetén figyelmeztetés kell, mert nem történt módosítás.

DB.cs és Form1.csC#
// DB.cs: a DolgozoModositas visszatérési típusa int lesz
public static int DolgozoModositas(int id, string vnev, string knev, int fizetes, string cim)
{
// ... ugyanaz ...
return parancs.ExecuteNonQuery();
}

// Form1.cs
int erintett = DB.DolgozoModositas(kijeloltId, vnev, knev, fizetes, cim);

if (erintett == 0)
{
Uzenet("Nem történt módosítás: a dolgozó nem található.", true);
}
else
{
Uzenet($"{erintett} sor módosult.", false);
}

6. Az utolsó törlés visszavonása

Törlés után egy Visszavonás gomb vegye fel újra a legutóbb törölt dolgozót (új azonosítóval). A törölt adatokat a program tárolja a törlés előtt.

Megoldás megnyitása

A törlés előtt elmentjük a mezők tartalmát négy mezőben. A visszavonás ezekből egy új INSERT-et futtat. Ez nem az eredeti sor visszaállítása (az azonosító új lesz), csak az adatok helyreállítása. A gomb csak akkor aktív, ha van mit visszavonni.

Form1.csC#
private string toroltVnev = "";
private string toroltKnev = "";
private string toroltCim = "";
private int toroltFizetes = 0;

// a törlés előtt (a megerősítés után):
toroltVnev = txtVnev.Text;
toroltKnev = txtKnev.Text;
toroltCim = txtCim.Text;
toroltFizetes = (int)nudFizetes.Value;
btnVisszavon.Enabled = true;

private void btnVisszavon_Click(object sender, EventArgs e)
{
long ujId = DB.DolgozoFelvetel(toroltVnev, toroltKnev, toroltFizetes, toroltCim);

Frissit((int)ujId);
MezokTolt();
btnVisszavon.Enabled = false;
Uzenet("A törölt dolgozó visszaállítva.", false);
}

Gyors önellenőrzés

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

1. Mi történik, ha az UPDATE vagy a DELETE végéről kimarad a WHERE?

Az utasítás a tábla összes sorát érinti: mindenkit módosít, illetve kiüríti a táblát.

2. Miért az id alapján módosítunk és törlünk?

Mert az elsődleges kulcs minden sort egyedileg azonosít. A név vagy a sorszám nem megbízható.

3. Mit ad vissza az ExecuteNonQuery?

Az érintett sorok számát. Nulla azt jelenti, hogy a feltételnek nem felelt meg egyetlen sor sem.

4. Hogyan szerzed meg az újonnan felvett sor azonosítóját?

A kapcsolat.LastInsertRowId tulajdonságból (long típus).

5. Mi a különbség az üres szöveg és a NULL között?

Az üres szöveg egy érték (nulla karakter), a NULL hiányzó, ismeretlen érték. A C# kódban a DBNull.Value felel meg neki, az SQL-ben az IS NULL vizsgálja.

6. Mire való a Revert Changes a DB Browserben?

Eldobja az utolsó Write Changes óta történt összes módosítást, így egy elrontott UPDATE vagy DELETE kipróbálás után visszaállítható az adatbázis.

7. Miért kell megerősítés törlés előtt?

Mert a törlés nem vonható vissza, és a véletlen kattintás adatvesztést okozna.

Mit tudsz már?

  • Az INSERT, UPDATE és DELETE utasítást WHERE id = ... feltétellel használod.
  • Az érintett sorok számából ellenőrzöd a művelet sikerét.
  • Az új sor azonosítóját a LastInsertRowId adja.
  • A hiányzó értéket DBNull.Value alakban kezeled.
  • Táblázat-kijelölést és szerkesztőmezőket kötsz össze, a törlést megerősítéssel védi.

Következő lecke: paraméterek és SQL-biztonság

A módosító utasítások kész. A következő leckében a paraméterezett lekérdezéseket mélyítjük: több paraméter, dátumok, NULL és a leggyakoribb biztonsági hibák.