Grafikus C# – Windows Forms tanfolyam › 3.11 lecke Windows Forms · 3.11 lecke · záró projekt A 3. fejezet záró projektjében mindent összeteszel, amit az adatbázisokról tanultál: három tábla és kapcsolataik, biztonságos paraméteres lekérdezések, JOIN, szűrés és keresés, rendezett szerkezet, tranzakció és hibakezelés. A téma egy iskolai könyvtár: könyvek, olvasók és kölcsönzések, késések kimutatásával. Ha inkább felhasználói nyilvántartást építenél (például eszköz- vagy tagnyilvántartást), a lépések ugyanazok: más a téma, ugyanaz a szerkezet.Mini projekt: iskolai könyvtár – nyilvántartás, kölcsönzés, késések

A projekt végére
- megtervezel és létrehozol egy három táblás adatbázist kulcsokkal;
- a könyvek kezelését (felvétel, módosítás, törlés) rétegekre bontva, párbeszédablakkal építed fel;
- a kölcsönzést tranzakcióban, szabályok ellenőrzésével valósítod meg;
- JOIN lekérdezésekkel listázod az aktuális kölcsönzéseket, a késéseket és a szabad példányokat;
- kereső és statisztika készül;
- tesztlistával és önértékeléssel ellenőrzöd a munkádat.
Mielőtt elkezded
Ehhez a projekthez a 3. fejezet összes leckéje kell, különösen a 3.5 (szűrés), 3.7 (paraméterek, dátumok), 3.8 (DataTable), 3.9 (rétegek, párbeszédablak) és 3.10 (kulcsok, tranzakció). Ha egy rész nem megy, a hozzá tartozó lecke részletesen elmagyarázza. A megoldásokat lenyitható részekben találod.
Terv: mit tudjon a program?
Egy projektet mindig a követelményekkel kezdünk. Ezeket pontozással ellenőrizheted a végén:
| Rész | Követelmény | Pont |
|---|---|---|
| Adatbázis (15) | Három tábla (konyvek, olvasok, kolcsonzesek) elsődleges és idegen kulcsokkal, a kulcsellenőrzés bekapcsolva. | 10 |
Mintaadatok, a .db fájl a program mellé másolódik. | 5 | |
| Könyvek (20) | Könyv felvétele, módosítása, törlése Konyv osztállyal, KonyvTar adatkezelővel és párbeszédablakkal (3.9 lecke), ellenőrzéssel. | 14 |
| A könyvet nem lehet törölni, ha van kölcsönzése: érthető üzenet. | 6 | |
| Kölcsönzés (25) | Kölcsönzés rögzítése: szabad példány és az olvasó legfeljebb 3 aktív kölcsönzése szabály, tranzakcióban. | 13 |
| Visszahozás rögzítése; kétszeri visszahozás nem lehetséges. | 7 | |
| A határidő a kölcsönzéskor számolódik (alapból 14 nap). | 5 | |
| Listák (20) | Könyvlista a szabad példányok számával (JOIN, GROUP BY). | 7 |
| Aktuális kölcsönzések listája; a késésben lévők pirosan, a késés napjaival. | 8 | |
| Kereső (cím vagy szerző részletére), biztonságosan. | 5 | |
| Hibakezelés és biztonság (10) | Minden SQL paraméterezett; a váratlan hibák naplózása, általános üzenet a felhasználónak. | 6 |
| Üres vagy hibás bevitel kezelése, megerősítés törlés előtt. | 4 | |
| Kód és szerkezet (10) | Rétegekre bontott kód, beszédes nevek, nincs ismétlődő SQL az ablakban. | 10 |
Összesen: 100 pont. Mielőtt kódolnál, rajzold le (papíron) az ablak elrendezését, és gondold végig a három tábla kapcsolatát:
A Mit tegyél ide: egy papírra rajzolt terv a főablakról: bal oldalt a könyvek táblázata a keresővel, jobb oldalt az aktuális kölcsönzések, alul a kölcsönzés és visszahozás gombjai és az olvasó választó listája.Javasolt fájlnév: winforms-3-11-terv.webp · Alt szöveg: A könyvtár programjának tervrajzakolcsonzesek tábla a két másik közötti kapcsolótábla: egy sora azt mondja, hogy ez az olvasó ezt a könyvet ekkor kölcsönözte, eddig kell visszahoznia, és ekkor hozta vissza (a visszahozva üres, amíg a könyv kint van).
Az adatbázis
Hozd létre a konyvtar.db adatbázist a DB Browserben, és futtasd az Execute SQL lapon (majd Ctrl+S). A dátumokat ISO szövegként tároljuk (3.7 lecke). Az utolsó rész mintakölcsönzéseket vesz fel, köztük két késésben lévőt (a 2026. október 25-i dátumhoz képest):
CREATE TABLE konyvek (
id INTEGER PRIMARY KEY AUTOINCREMENT,
cim TEXT NOT NULL,
szerzo TEXT NOT NULL,
ev INTEGER,
peldany INTEGER NOT NULL DEFAULT 1 CHECK (peldany >= 0)
);
CREATE TABLE olvasok (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nev TEXT NOT NULL,
osztaly TEXT
);
CREATE TABLE kolcsonzesek (
id INTEGER PRIMARY KEY AUTOINCREMENT,
konyvid INTEGER NOT NULL,
olvasoid INTEGER NOT NULL,
kolcsonzes TEXT NOT NULL, -- yyyy-MM-dd
hatarido TEXT NOT NULL, -- yyyy-MM-dd
visszahozva TEXT, -- NULL, amíg a könyv kint van
FOREIGN KEY (konyvid) REFERENCES konyvek (id),
FOREIGN KEY (olvasoid) REFERENCES olvasok (id)
);
INSERT INTO konyvek (cim, szerzo, ev, peldany) VALUES
('Egri csillagok', 'Gárdonyi Géza', 1901, 2),
('A Pál utcai fiúk', 'Molnár Ferenc', 1906, 1),
('Légy jó mindhalálig', 'Móricz Zsigmond', 1920, 1),
('Az arany ember', 'Jókai Mór', 1872, 1),
('Tüskevár', 'Fekete István', 1957, 2);
INSERT INTO olvasok (nev, osztaly) VALUES
('Kovács Anna', '10.A'), ('Nagy Péter', '10.A'), ('Szabó Kata', '10.B'), ('Tóth Bence', '11.A');
INSERT INTO kolcsonzesek (konyvid, olvasoid, kolcsonzes, hatarido, visszahozva) VALUES
(1, 1, '2026-09-20', '2026-10-04', NULL), -- késésben
(2, 2, '2026-10-10', '2026-10-24', NULL), -- késésben
(3, 3, '2026-10-20', '2026-11-03', NULL), -- rendben
(1, 3, '2026-09-01', '2026-09-15', '2026-09-12'), -- visszahozva
(4, 1, '2026-10-22', '2026-11-05', NULL); -- rendbenEllenőrizd a Foreign Keys pragma beállítását a DB Browserben (Edit Pragmas), majd hozz létre egy új Windows Forms projektet. Telepítsd a System.Data.SQLite csomagot (3.2 lecke), másold a konyvtar.db fájlt a projektbe (Copy if newer), és írd meg a DB.cs osztályt a 3.2, 3.7 és 3.10 leckék szerint: UjKapcsolat() (Foreign Keys=True, a konyvtar.db fájlnévvel és a File.Exists ellenőrzéssel), IsoDatum() és Naploz().Megoldás megnyitása (próbáld ki előbb önállóan!)
using System;
using System.Data;
using System.Data.SQLite;
using System.Globalization;
using System.IO;
using System.Windows.Forms;
internal static class DB
{
public static readonly string AdatbazisUt = Path.Combine(Application.StartupPath, "konyvtar.db");
public static SQLiteConnection UjKapcsolat()
{
if (!File.Exists(AdatbazisUt))
{
throw new FileNotFoundException("Nem található az adatbázis: " + AdatbazisUt);
}
return new SQLiteConnection("Data Source=" + AdatbazisUt + ";Version=3;Foreign Keys=True;");
}
public static string IsoDatum(DateTime datum)
{
return datum.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
}
public static string LikeVedelem(string szoveg)
{
return szoveg.Replace("^", "^^").Replace("%", "^%").Replace("_", "^_");
}
public static void Naploz(Exception ex)
{
try
{
string utvonal = Path.Combine(Application.StartupPath, "hibanaplo.txt");
File.AppendAllText(utvonal, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " | " + ex.GetType().Name + " | " + ex.Message + Environment.NewLine);
}
catch (Exception)
{
}
}
}
Könyvek kezelése: ugyanaz a szerkezet, mint a 3.9 leckében
A könyvek felvétele, módosítása és törlése a 3.9 leckében látott három rétegben készül: Konyv osztály (adatok és Hiba() ellenőrzés), KonyvTar adatkezelő (Betolt, Mentes, Torles), KonyvUrlap párbeszédablak (ShowDialog, új objektummal tér vissza). Az új elem a törlés védelme: egy könyvet nem szabad törölni, ha van kölcsönzése (az idegen kulcs ezt megakadályozza). A felhasználónak ne a technikai FOREIGN KEY hibát mutasd, hanem érthető üzenetet.
InvalidOperationException kivételt dobja, ha a sor nem létezik. Az idegen kulcs megsértése SQLiteException lesz, amelyet az ablak ismer fel.txtCim, txtSzerzo, nudEv, nudPeldany), lblHiba, Mentés (DialogResult: None) és Mégse. Új objektummal tér vissza.Megoldás megnyitása (próbáld ki előbb önállóan!)
A szerkezet a Dolgozo megoldás mintáját követi a konyvek táblára átírva. Az új rész a törlés védelme és az ellenőrzés:
public class Konyv
{
public int Id { get; set; }
public string Cim { get; set; } = "";
public string Szerzo { get; set; } = "";
public int Ev { get; set; } // 0: ismeretlen
public int Peldany { get; set; } = 1;
public string Hiba()
{
if (Cim.Length == 0 || Szerzo.Length == 0)
{
return "A cím és a szerző kötelező.";
}
if (Cim.Length > 80 || Szerzo.Length > 80)
{
return "A cím és a szerző legfeljebb 80 karakteres lehet.";
}
if (Ev != 0 && (Ev < 1000 || Ev > 2100))
{
return "Az év 1000 és 2100 között legyen (vagy 0, ha ismeretlen).";
}
if (Peldany < 1)
{
return "A példányszám legalább 1.";
}
return "";
}
}
// Form1.cs: a törlés gombjában
try
{
KonyvTar.Torles(id);
Uzenet("A könyv törölve.", false);
}
catch (SQLiteException ex)
{
if (ex.Message.Contains("FOREIGN KEY"))
{
Uzenet("A könyvet nem lehet törölni, mert kölcsönzések tartoznak hozzá.", true);
}
else
{
DB.Naploz(ex);
MessageBox.Show("A törlés most nem sikerült.");
}
}
catch (InvalidOperationException ex)
{
Uzenet(ex.Message, true);
}A kölcsönzés: szabályok és tranzakció
A kölcsönzés a projekt legfontosabb része. Egy kölcsönzés rögzítése előtt két szabályt kell ellenőrizni, és csak ha mindkettő teljesül, kerülhet az új sor az adatbázisba. Az ellenőrzés és a beszúrás egy tranzakcióban történik, hogy a két lépés egy egységet alkosson:
| Szabály | Hogyan ellenőrzöd? |
|---|---|
| Van szabad példány a könyvből. | peldany értéke > a visszahozva IS NULL kölcsönzések száma az adott könyvre. |
| Az olvasónál legfeljebb 3 könyv lehet egyszerre. | Az olvasó visszahozva IS NULL kölcsönzéseinek száma < 3. |
public static class KolcsonzesTar
{
private const int MaxAktiv = 3;
// egyetlen számot kérdez le, paraméterezve (a tranzakció részeként)
private static int Szam(SQLiteConnection kapcsolat, SQLiteTransaction tranzakcio, string sql, int id)
{
using (SQLiteCommand parancs = new SQLiteCommand(sql, kapcsolat, tranzakcio))
{
parancs.Parameters.AddWithValue("@id", id);
return Convert.ToInt32(parancs.ExecuteScalar());
}
}
public static void Kolcsonoz(int konyvId, int olvasoId, int napok)
{
using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
{
kapcsolat.Open();
using (SQLiteTransaction tranzakcio = kapcsolat.BeginTransaction())
{
int peldany = Szam(kapcsolat, tranzakcio, "SELECT COALESCE(MAX(peldany), 0) FROM konyvek WHERE id = @id", konyvId);
int kint = Szam(kapcsolat, tranzakcio, "SELECT COUNT(*) FROM kolcsonzesek WHERE konyvid = @id AND visszahozva IS NULL", konyvId);
int olvasoKint = Szam(kapcsolat, tranzakcio, "SELECT COUNT(*) FROM kolcsonzesek WHERE olvasoid = @id AND visszahozva IS NULL", olvasoId);
if (peldany == 0)
{
throw new InvalidOperationException("A könyv nem létezik.");
}
if (kint >= peldany)
{
throw new InvalidOperationException("Nincs szabad példány ebből a könyvből.");
}
if (olvasoKint >= MaxAktiv)
{
throw new InvalidOperationException($"Az olvasónál már {MaxAktiv} könyv van, többet nem kölcsönözhet.");
}
using (SQLiteCommand beszur = new SQLiteCommand("INSERT INTO kolcsonzesek (konyvid, olvasoid, kolcsonzes, hatarido) VALUES (@konyv, @olvaso, @ma, @hatarido)", kapcsolat, tranzakcio))
{
beszur.Parameters.AddWithValue("@konyv", konyvId);
beszur.Parameters.AddWithValue("@olvaso", olvasoId);
beszur.Parameters.AddWithValue("@ma", DB.IsoDatum(DateTime.Today));
beszur.Parameters.AddWithValue("@hatarido", DB.IsoDatum(DateTime.Today.AddDays(napok)));
beszur.ExecuteNonQuery();
}
tranzakcio.Commit();
}
}
}
public static void Visszahoz(int kolcsonzesId)
{
using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
{
kapcsolat.Open();
string sql = "UPDATE kolcsonzesek SET visszahozva = @ma WHERE id = @id AND visszahozva IS NULL";
using (SQLiteCommand parancs = new SQLiteCommand(sql, kapcsolat))
{
parancs.Parameters.AddWithValue("@ma", DB.IsoDatum(DateTime.Today));
parancs.Parameters.AddWithValue("@id", kolcsonzesId);
if (parancs.ExecuteNonQuery() != 1)
{
throw new InvalidOperationException("Ezt a kölcsönzést már visszahozták, vagy nem létezik.");
}
}
}
}
}MAX eredménye NULL. A COALESCE az első nem üres értéket adja: így 0 lesz, amelyet külön fel tudunk ismerni.InvalidOperationException kilép a using blokkból, a tranzakció visszavonódik, és az ablak a kivétel szövegét mutatja a felhasználónak. A szabály nem teljesülése várt helyzet, nem programhiba.UPDATE nulla sort érint, a kivétel jelzi.Jósolj!
A mintaadatokkal a Légy jó mindhalálig könyvből egy példány van, és azt Szabó Kata már kölcsönözte. Mi történik, ha Tóth Bence is kölcsönözni szeretné? És ha Kovács Anna (nála már 2 kint lévő könyv van: Egri csillagok és Az arany ember) a Tüskevár könyvből próbál meg kölcsönözni egy harmadikat, majd egy negyediket?
Jóslat ellenőrzése
Tóth Bence kölcsönzése elutasítódik: Nincs szabad példány ebből a könyvből. (1 példány, 1 kint). Kovács Anna harmadik kölcsönzése sikerül (2 kint lévő könyve volt, a Tüskevár pedig szabad), a negyedik viszont elutasítódik: Az olvasónál már 3 könyv van. Mindkét elutasításnál a tranzakció visszavonódik, az adatbázisban nem történik változás.
Mit tegyél ide: a főablak, amelyben a kölcsönzés elutasítása után piros üzenet látszik („Nincs szabad példány ebből a könyvből.”).Javasolt fájlnév: winforms-3-11-kolcsonzes.webp · Alt szöveg: A kölcsönzés elutasítása szabálysértés miatt
Listák és késések
A főablakban két lista látszik: a könyvek a szabad példányszámmal, és az aktuális kölcsönzések (a késésben lévők kiemelve). Mindkettő JOIN lekérdezés. Két fontos részlet van bennük:
// KonyvTar.cs: könyvek a szabad példányszámmal, keresővel
public static DataTable Lista(string kereso)
{
string sql = "SELECT b.id, b.cim, b.szerzo, b.ev, b.peldany, b.peldany - COUNT(k.id) AS szabad "
+ "FROM konyvek b "
+ "LEFT JOIN kolcsonzesek k ON k.konyvid = b.id AND k.visszahozva IS NULL "
+ "WHERE b.cim LIKE @minta ESCAPE '^' OR b.szerzo LIKE @minta ESCAPE '^' "
+ "GROUP BY b.id, b.cim, b.szerzo, b.ev, b.peldany "
+ "ORDER BY b.cim";
DataTable tabla = new DataTable();
using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
{
kapcsolat.Open();
using (SQLiteCommand parancs = new SQLiteCommand(sql, kapcsolat))
{
parancs.Parameters.AddWithValue("@minta", "%" + DB.LikeVedelem(kereso) + "%");
using (SQLiteDataAdapter adapter = new SQLiteDataAdapter(parancs))
{
adapter.Fill(tabla);
}
}
}
return tabla;
}
// KolcsonzesTar.cs: aktuális kölcsönzések a késés jelzésével
public static DataTable Aktualis()
{
string sql = "SELECT k.id, b.cim, o.nev AS olvaso, k.kolcsonzes, k.hatarido, "
+ "CASE WHEN k.hatarido < @ma THEN 'késésben' ELSE 'rendben' END AS allapot "
+ "FROM kolcsonzesek k "
+ "JOIN konyvek b ON b.id = k.konyvid "
+ "JOIN olvasok o ON o.id = k.olvasoid "
+ "WHERE k.visszahozva IS NULL ORDER BY k.hatarido";
DataTable tabla = new DataTable();
using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
{
kapcsolat.Open();
using (SQLiteCommand parancs = new SQLiteCommand(sql, kapcsolat))
{
parancs.Parameters.AddWithValue("@ma", DB.IsoDatum(DateTime.Today));
using (SQLiteDataAdapter adapter = new SQLiteDataAdapter(parancs))
{
adapter.Fill(tabla);
}
}
}
return tabla;
}LEFT JOIN kolcsonzesek k ON k.konyvid = b.id AND k.visszahozva IS NULL feltétel az ON részben van. Ha a visszahozva IS NULL a WHERE-be kerülne, azok a könyvek kiesnének a listából, amelyeknek csak visszahozott kölcsönzésük van: a párosított soruk visszahozva értéke nem NULL, ezért a WHERE kiszűrné őket, pedig minden példányuk szabad. Az ON csak a párosítást szűri, a könyv megmarad (a nem kölcsönzött könyvnél az összes oszlop NULL).COUNT(k.id) a NULL sorokat (nincs kint lévő kölcsönzés) 0-nak veszi, a COUNT(*) 1-nek venné.@ma értéket: így a viselkedés tesztelhető (például egy másik napra állítva).JOIN kapcsolja a három táblát. (A kapcsolótábla minden sora pontosan egy könyvre és egy olvasóra mutat, ezért itt INNER JOIN elég.)A késésben lévő sorok piros színezése és a késés napjainak kiírása a táblázat DataBindingComplete eseményében történik (3.8 lecke), a mai és a határidő különbségéből:
private void dgvKolcsonzesek_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
int keso = 0;
foreach (DataGridViewRow sor in dgvKolcsonzesek.Rows)
{
if (Convert.ToString(sor.Cells["allapot"].Value) == "késésben")
{
sor.DefaultCellStyle.BackColor = Color.MistyRose;
sor.DefaultCellStyle.ForeColor = Color.Firebrick;
keso = keso + 1;
DateTime hatarido = DateTime.Parse(Convert.ToString(sor.Cells["hatarido"].Value), System.Globalization.CultureInfo.InvariantCulture);
int napok = (DateTime.Today - hatarido).Days;
sor.Cells["allapot"].Value = $"késésben ({napok} nap)";
}
}
lblKesesek.Text = keso == 0 ? "Nincs késésben lévő könyv." : $"Késésben lévő könyvek: {keso}";
}Jósolj!
2026. október 25-én a mintaadatokkal hány sor kerül az aktuális kölcsönzések listájába, és hány lesz piros? Hány napot késik a A Pál utcai fiúk és az Egri csillagok könyvet kölcsönző olvasó?
Jóslat ellenőrzése
Az öt kölcsönzésből négy van kint (a visszahozott nem látszik): ez 4 sor. Ebből 2 piros: az Egri csillagok határideje 2026-10-04 (21 napos késés), A Pál utcai fiúk határideje 2026-10-24 (1 napos késés). A másik két kölcsönzés határideje novemberben van, ezek rendben vannak. (Más napon indítva a program más számokat mutat: a mai dátum a program indításának napja.)
Kereső és statisztika
A könyvlista keresője a KonyvTar.Lista(kereso) metódust hívja minden gépeléskor (a txtKereso.TextChanged esemény), a betoltve jelzővel védve (3.5 lecke). A statisztikához egy külön kis lekérdezés tartozik: a három legtöbbet kölcsönzött könyv:
SELECT b.cim, COUNT(k.id) AS kolcsonzesek
FROM konyvek b
JOIN kolcsonzesek k ON k.konyvid = b.id
GROUP BY b.id, b.cim
ORDER BY kolcsonzesek DESC, b.cim
LIMIT 3;INNER JOIN a helyes.LIMIT csak rendezéssel együtt értelmes (3.5 lecke).lblStat felirat mutassa, vagy egy kis ListBox (1. Egri csillagok (2 kölcsönzés)).Tesztlista és önértékelés
Mielőtt kész állapotúnak nyilvánítod, próbáld ki sorban:
| # | Teszt | Várt eredmény |
|---|---|---|
| 1 | A program indul, a könyvlista és a kölcsönzések listája megjelenik. | Minden sor látszik, a szabad példányok száma helyes. |
| 2 | Új könyv felvétele, majd módosítása. | A lista frissül, a módosított sor kijelölve marad. |
| 3 | Könyv felvétele üres címmel, majd Mégse. | Hibaüzenet; a Mégse után nem történik semmi. |
| 4 | Kölcsönzés szabad példányra. | Az új sor megjelenik a kölcsönzések között, a szabad példányszám eggyel csökken, a határidő 14 nap. |
| 5 | Kölcsönzés, ha nincs szabad példány. | Érthető üzenet, nem változik semmi. |
| 6 | Olvasó negyedik kölcsönzése. | Elutasítás a 3 könyves korlát miatt. |
| 7 | Visszahozás. | A sor eltűnik az aktuális listából, a szabad példányszám nő. |
| 8 | Ugyanannak a kölcsönzésnek a második visszahozása (két ablakból, vagy a DB Browserben előkészítve). | Érthető üzenet, a visszahozás dátuma nem íródik felül. |
| 9 | A késésben lévő kölcsönzések. | Pirosak, a késés napjaival. |
| 10 | Kölcsönzött könyv törlése. | Érthető üzenet, a könyv megmarad. |
| 11 | Kölcsönzés nélküli könyv törlése megerősítéssel. | Megerősítés után eltűnik. |
| 12 | Keresés: ékezetes, nagybetűs, %, _ és ' jellel. | Hiba nélkül, a speciális karakterek szó szerint értendők. |
| 13 | Az adatbázisfájl átnevezése, majd indítás. | Érthető hibaüzenet, a naplóban a részletek. |
| 14 | A program újraindítása. | Az adatok megmaradtak. |
Önértékelés
ORDER BY csak rögzített szöveg); nincs belső hibaüzenet a felhasználónak; a napló írja a részleteket.Hiba() és az adatkezelő osztályokban vannak; nincs kétszer megírt kód.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. Eltűnnek a könyvek, amelyeket már visszahoztak
FROM konyvek b LEFT JOIN kolcsonzesek k ON k.konyvid = b.id
WHERE k.visszahozva IS NULLHiba és javítás
A WHERE a LEFT JOIN után szűr. Az a könyv, amelynek csak visszahozott kölcsönzése van, csak olyan sorral párosul, ahol a visszahozva nem NULL, ezért a WHERE kiszűri, és a könyv teljesen eltűnik a listából, pedig minden példánya szabad. A szűrést a JOIN ... ON ... AND k.visszahozva IS NULL feltételbe tedd: ott csak a párosítást korlátozza, a könyv sora megmarad.
2. Negatív szabad példányszám
Egy könyvnél a szabad példányszám 0 helyett 1 vagy negatív szám. Ha Ok és javítás
COUNT(*)-ot használsz, a LEFT JOIN által adott egyetlen NULL sort is 1-nek számolja. A COUNT(k.id) a NULL értéket kihagyja. Másik ok lehet, hogy a kölcsönzés ellenőrzése tranzakció nélkül történt, és két kölcsönzés egyszerre túllépte a példányszámot.
3. A késés rosszul számolódik
A határidő Valószínűleg nem ISO formátumban tárolod a dátumot (például 2026-10-04, de a program nem tekinti késésben lévőnek a 2026-10-24 napot.Ok és javítás
04.10.2026), vagy nem ISO formátumban adod át a mai dátumot. A szöveges összehasonlítás csak yyyy-MM-dd alakban egyezik az időrenddel (3.7 lecke). Ellenőrizd a DB.IsoDatum használatát, és hogy a határidő is ISO alakban került az adatbázisba.
4. A visszahozás dátuma felülíródik
UPDATE kolcsonzesek SET visszahozva = @ma WHERE id = @idHiba és javítás
Hiányzik az AND visszahozva IS NULL feltétel, ezért egy már visszahozott kölcsönzés dátuma is újra felülíródik, és a program nem veszi észre a kétszeri visszahozást. A feltétellel nulla sort érint az utasítás, és a metódus kivétellel jelzi.
5. A kölcsönzés fele megmaradt
Hiba után az adatbázisban egy kölcsönzés félig bekerült adatokkal maradt. Az ellenőrzés és a beszúrás nem tranzakcióban történt, vagy a Ok és javítás
Commit kimaradt, vagy a parancsok nem a tranzakcióhoz kötött kapcsolaton futottak (3.10 lecke). Minden összetartozó lépést egyetlen kapcsolaton, egyetlen tranzakcióban futtass, és a végén hívd a Commit metódust.
6. A könyv törlésekor technikai hibaüzenet
A felhasználó a FOREIGN KEY constraint failed üzenetet látja. Az Ok és javítás
SQLiteException üzenetét közvetlenül kiírtad. Ismerd fel a kulcsszót (ex.Message.Contains("FOREIGN KEY")), és adj érthető üzenetet. A többi hibát naplózd, és általános szöveget mutass.
Három önálló bővítés
Ha kész a projekt, fejleszd tovább. Egyszerre csak egyet csinálj: tervezd meg, írd meg, futtasd, ellenőrizd, és csak utána nyisd ki a megoldást.
1. Meghosszabbítás
Egy Meghosszabbítás gomb 7 nappal hosszabbítsa a kijelölt kölcsönzés határidejét, de csak egyszer, és csak akkor, ha még nincs késésben. Az „egyszer” szabályhoz új oszlop kell a táblába (Egy lehetséges megoldás
meghosszabbitva, 0 vagy 1). Az ALTER TABLE utasítás oszlopot ad egy meglévő táblához. Az UPDATE feltételei adják a szabályt: nincs visszahozva, még nem volt meghosszabbítva, és a határidő nem múlt el. Az érintett sorok száma jelzi, teljesültek-e a szabályok.-- egyszer, a DB Browserben:
ALTER TABLE kolcsonzesek ADD COLUMN meghosszabbitva INTEGER NOT NULL DEFAULT 0;
// KolcsonzesTar.cs
public static void Meghosszabbit(int kolcsonzesId, string ujHatarido)
{
using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
{
kapcsolat.Open();
string sql = "UPDATE kolcsonzesek SET hatarido = @uj, meghosszabbitva = 1 "
+ "WHERE id = @id AND visszahozva IS NULL AND meghosszabbitva = 0 AND hatarido >= @ma";
using (SQLiteCommand parancs = new SQLiteCommand(sql, kapcsolat))
{
parancs.Parameters.AddWithValue("@uj", ujHatarido);
parancs.Parameters.AddWithValue("@id", kolcsonzesId);
parancs.Parameters.AddWithValue("@ma", DB.IsoDatum(DateTime.Today));
if (parancs.ExecuteNonQuery() != 1)
{
throw new InvalidOperationException("A kölcsönzés nem hosszabbítható (már volt hosszabbítva, késésben van, vagy visszahozták).");
}
}
}
}
2. Olvasó története
Egy olvasó kiválasztásakor egy külön táblázat mutassa az összes eddigi kölcsönzését (visszahozottakat is): könyv címe, kölcsönzés, határidő, visszahozás dátuma (vagy kint van). A lekérdezés az olvasó azonosítójára szűr, és Egy lehetséges megoldás
COALESCE segítségével szöveget ad a hiányzó visszahozási dátum helyett.SELECT b.cim, k.kolcsonzes, k.hatarido,
COALESCE(k.visszahozva, 'kint van') AS visszahozva
FROM kolcsonzesek k
JOIN konyvek b ON b.id = k.konyvid
WHERE k.olvasoid = @olvaso
ORDER BY k.kolcsonzes DESC;
3. Késések exportálása
Egy gomb mentse a késésben lévő kölcsönzéseket CSV fájlba (olvasó neve, könyv címe, határidő, késés napjai), hogy a könyvtáros kinyomtathassa. Az Egy lehetséges megoldás
Aktualis() eredményéből a DataTable.Select("allapot = 'késésben'") szűri a késéseket (a 3.8 lecke szerint). A StreamWriter soronként írja a fájlt, pontosvesszővel elválasztva. Az idézőjelet és a pontosvesszőt tartalmazó értékeket (például egy cím) idézőjelek közé kell tenni, ezt a példa egyszerűsítve kezeli.DataTable tabla = KolcsonzesTar.Aktualis();
DataRow[] kesok = tabla.Select("allapot = 'késésben'");
using (System.IO.StreamWriter iro = new System.IO.StreamWriter(sfdExport.FileName))
{
iro.WriteLine("olvaso;konyv;hatarido;keses_napok");
foreach (DataRow sor in kesok)
{
DateTime hatarido = DateTime.Parse(sor["hatarido"].ToString(), System.Globalization.CultureInfo.InvariantCulture);
int napok = (DateTime.Today - hatarido).Days;
iro.WriteLine(sor["olvaso"] + ";" + sor["cim"] + ";" + sor["hatarido"] + ";" + napok);
}
}
Gyakorlótár: 6 további feladat
Három szinten gyakorolhatsz: alap, közepes és kihívás. Az első négy a DB Browser Execute SQL lapján oldható meg a konyvtar.db adatbázissal. Csak a próbálkozás után nyisd le a megoldást.
Alap szint
1. Hány könyv van kint?
Add meg, hány könyv van jelenleg kint (nincs visszahozva). A hiányzó értéket az Megoldás megnyitása
IS NULL vizsgálja, nem az = NULL (3.5 lecke).SELECT COUNT(*) AS kint_levo FROM kolcsonzesek WHERE visszahozva IS NULL;
2. A késésben lévők
Írd ki a késésben lévő kölcsönzések olvasóját, a könyv címét és a határidőt, ha ma 2026. november 1. (a dátum legyen a lekérdezésben). Az ISO szöveges dátumok összehasonlítása időrendben működik. Ugyanezt a program paraméterként kapja a mai dátummal.Megoldás megnyitása
SELECT o.nev AS olvaso, b.cim, k.hatarido
FROM kolcsonzesek k
JOIN konyvek b ON b.id = k.konyvid
JOIN olvasok o ON o.id = k.olvasoid
WHERE k.visszahozva IS NULL AND k.hatarido < '2026-11-01'
ORDER BY k.hatarido;
Közepes szint
3. Olvasók és kölcsönzéseik száma
Írd ki minden olvasó nevét és azt, hány könyvet kölcsönzött összesen (azokat is, akik még nem kölcsönöztek: nulla). A Megoldás megnyitása
LEFT JOIN megtartja a kölcsönzés nélküli olvasókat, a COUNT(k.id) pedig a NULL sorokat nem számolja (nulla lesz).SELECT o.nev, COUNT(k.id) AS kolcsonzesek
FROM olvasok o
LEFT JOIN kolcsonzesek k ON k.olvasoid = o.id
GROUP BY o.id, o.nev
ORDER BY kolcsonzesek DESC, o.nev;
4. Soha nem kölcsönzött könyvek
Add meg azokat a könyveket, amelyeket még senki nem kölcsönzött ki. A Megoldás megnyitása
LEFT JOIN után azokat a sorokat tartjuk meg, ahol a kölcsönzés azonosítója NULL (nem találtunk párt). A NOT EXISTS ugyanezt al-lekérdezéssel fejezi ki.SELECT b.cim
FROM konyvek b
LEFT JOIN kolcsonzesek k ON k.konyvid = b.id
WHERE k.id IS NULL;
SELECT cim FROM konyvek b
WHERE NOT EXISTS (SELECT 1 FROM kolcsonzesek k WHERE k.konyvid = b.id);
Kihívás szint
5. Állítható kölcsönzési idő és korlát
A 14 napos időtartam és a 3 könyves korlát legyen beállítható egy A nudNapok és egy nudMax mezővel, anélkül, hogy a KolcsonzesTar osztály ismerné az ablakot. Hogyan adod át az értékeket?Megoldás megnyitása
Kolcsonoz metódus paramétereket kap (napok, maxAktiv). Az MaxAktiv állandó helyett paraméter lesz. Az ablak olvassa a két mezőt, és adja át. Így az adatkezelő nem függ az ablaktól.// KolcsonzesTar.cs
public static void Kolcsonoz(int konyvId, int olvasoId, int napok, int maxAktiv)
{
// ... a korlát ellenőrzése: if (olvasoKint >= maxAktiv) ...
}
// Form1.cs
KolcsonzesTar.Kolcsonoz(konyvId, olvasoId, (int)nudNapok.Value, (int)nudMax.Value);
6. Biztonsági mentés
Egy Mentés gomb másolja az adatbázis fájlját a felhasználó által megadott mappába, az időponttal ellátott néven (konyvtar_20261025_1930.db). Mire kell vigyázni? A Megoldás megnyitása
File.Copy a fájlt másolja. A másolás előtt nem szabad nyitva lennie az adatbázis-kapcsolatnak, ezért a using blokkok használata fontos (a program nem tart folyamatosan nyitott kapcsolatot). A mappát egy FolderBrowserDialog kéri. A másolás hibáit (nincs jogosultság, nincs hely) try–catch kezeli.private void btnMentes_Click(object sender, EventArgs e)
{
if (fbdMappa.ShowDialog() != DialogResult.OK)
{
return;
}
try
{
string nev = "konyvtar_" + DateTime.Now.ToString("yyyyMMdd_HHmm") + ".db";
string cel = System.IO.Path.Combine(fbdMappa.SelectedPath, nev);
System.IO.File.Copy(DB.AdatbazisUt, cel);
Uzenet("A biztonsági mentés elkészült: " + nev, false);
}
catch (Exception ex)
{
DB.Naploz(ex);
Uzenet("A mentés nem sikerült.", true);
}
}
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. Mert egy olvasó több könyvet kölcsönözhet, és egy könyvet több olvasó is kölcsönöz az idők során (sok-a-többhöz kapcsolat). A kapcsolótábla minden sora egy kölcsönzést ír le. A könyv még kint van: nincs visszahozási dátum. A kint lévő kölcsönzéseket az Hogy az ellenőrzés és a beszúrás egy oszthatatlan egységet alkosson. Szabálysértés vagy hiba esetén a tranzakció visszavonódik, és az adatbázisban nem marad félkész állapot. Hogy a bal oldali tábla sorai (a nem kölcsönzött könyvek) megmaradjanak. A A Hogy a viselkedés tesztelhető legyen, és a program kontrollálja az időt. Az ISO formátum biztosítja a helyes szöveges összehasonlítást. Az SQL az adatkezelő osztályokban (1. Miért van szükség kapcsolótáblára a könyvek és az olvasók között?
2. Mit jelent, hogy a visszahozva oszlop NULL?
IS NULL feltétel szűri.3. Miért tranzakcióban ellenőrizzük a szabályokat és szúrjuk be a kölcsönzést?
4. Miért kerül a szűrés a LEFT JOIN ON részébe?
WHERE a párosítás után szűrne, és a párt nem találó sorokat is kiszűrné.5. Mi a különbség a COUNT(*) és a COUNT(k.id) között LEFT JOIN után?
COUNT(*) a pár nélküli sort is 1-nek számolja, a COUNT(k.id) csak a létező párokat (a NULL értéket kihagyja).6. Miért kell a mai dátumot paraméterként átadni?
7. Mit jelent az, hogy az ablakban nincs SQL?
KonyvTar, KolcsonzesTar) van, az ablak csak hívja őket. Így egy változtatás egy helyen történik, és az ablak a felületre koncentrál.
Mit tudsz már? (a 3. fejezet végén)
- Adatbázist tervezel kulcsokkal és kapcsolótáblával.
- Biztonságos, paraméteres lekérdezéseket és JOIN-okat írsz.
- Rétegekre bontott, adatbázisos Windows Forms alkalmazást építesz.
- Tranzakcióval védesz összetartozó műveleteket, és szabályokat érvényesítesz.
- Teszteled és értékeled a saját projektedet.
Következő: önálló feladatok
A 3. fejezet kész. A következő oldalakon önálló, időre megoldható feladatok várnak a tanultakból: vezérlőelemek, szövegszerkesztő és adatbázis.



