DATA
CZAS
IMIENINY
FAZA KSIĘŻYCA
STAFLOTA ⊕ SEKTOR ALFA · BITEDU STATION

Czyli jak połączyć okno, logikę programu i prawdziwą bazę danych.


1. Dlaczego w aplikacjach potrzebna jest baza danych?

W praktycznie każdej aplikacji trudno obyć się bez przechowywania danych.
Na przykład:

Gdyby program trzymał wszystko w pamięci RAM, po zamknięciu aplikacji wszystko by znikało.
Dlatego używa się baz danych trwałych zapisów na dysku.

Na egzaminie INF.04 świetnie sprawdza się SQLite, bo:

Dzisiaj zobaczysz pełną aplikację: dodawanie, edycję, usuwanie, wyświetlanie i wyszukiwanie danych.


2. Zanim zaczniemy pisać — jak to w ogóle działa?

Najpierw warto zrozumieć cały „łańcuch”, bo wtedy kod stanie się prosty:

[UI – XAML] → [Logika okna – XAML.cs] → [Logika bazy – BazaDanych.cs] → [plik sklep.db]

Każda część ma swoją funkcję:

XAML

Tu jest wygląd aplikacji: pola tekstowe, przyciski, tabela.
Nic nie liczy — tylko pokazuje.

XAML.cs

Tu jest logika zachowania przycisków:

XAML.cs nie zna SQL – tylko wywołuje metody z klasy BazaDanych.

BazaDanych.cs

Tu jest prawdziwa praca:

sklep.db

Po prostu plik na dysku, który przechowuje dane.


3. Podstawowe pojęcia (tak, żeby rozumieć kod SQL)

PojęcieWyjaśnienie
Baza danychPlik, w którym zapisane są informacje.
TabelaPodział danych na grupy (Produkty, Klienci…).
RekordJeden wiersz tabeli, np. jeden produkt.
Pole (kolumna)nazwa, cena, id…
PRIMARY KEYunikalne ID każdego rekordu.
AUTOINCREMENTsamodzielne nadawanie ID.

4. Najważniejsze polecenia SQL — te trzeba znać

1. SELECT – pobieranie danych
SELECT * FROM Produkty;
SELECT nazwa, cena FROM Produkty;
SELECT * FROM Produkty WHERE cena > 5;
SELECT * FROM Produkty WHERE cena < 10;
SELECT * FROM Produkty WHERE cena BETWEEN 4 AND 8;

-- wyszukiwanie po tekście
SELECT * FROM Produkty WHERE nazwa LIKE '%cola%';  
SELECT * FROM Produkty WHERE nazwa LIKE 'C%';      -- zaczyna się na C
SELECT * FROM Produkty WHERE nazwa LIKE '%a';      -- kończy się na a
SELECT * FROM Produkty WHERE nazwa LIKE '%or%';    -- zawiera „or”

-- sortowanie
SELECT * FROM Produkty ORDER BY cena ASC;
SELECT * FROM Produkty ORDER BY cena DESC;
SELECT * FROM Produkty ORDER BY cena ASC, nazwa ASC;

-- warunki z AND/OR
SELECT * FROM Produkty WHERE cena > 5 AND nazwa LIKE 'C%';
SELECT * FROM Produkty WHERE cena = 4 OR cena = 6;

-- statystyki
SELECT COUNT(*) FROM Produkty;
SELECT AVG(cena) FROM Produkty;
SELECT MIN(cena) FROM Produkty;
SELECT MAX(cena) FROM Produkty;
SELECT SUM(cena) FROM Produkty;

-- dodatkowe
SELECT ROUND(cena, 2) FROM Produkty;
SELECT UPPER(nazwa) FROM Produkty;
SELECT LOWER(nazwa) FROM Produkty;

-- informacje o tabeli
PRAGMA table_info(Produkty);
SELECT name FROM sqlite_master WHERE type='table';

2. INSERT – dodawanie danych
INSERT INTO Produkty(nazwa, cena) VALUES ('Chleb', 4.50);

3. UPDATE – edycja danych
UPDATE Produkty SET cena = 3.00 WHERE id = 1;
UPDATE Produkty SET cena = cena + 1;
UPDATE Produkty SET cena = cena - 0.5 WHERE id = 3;

4. DELETE – usuwanie danych
DELETE FROM Produkty WHERE id = 5;
DELETE FROM Produkty WHERE cena < 1;

5. Tworzenie projektu WPF

  1. Otwórz Visual Studio
  2. Nowy projekt → WPF App (.NET)
  3. Nazwa projektu: BazaDanychWPF

6. Dodanie pliku bazy danych sklep.db

Na egzaminie nie można ściągać nic z internetu, więc baza musi być dodana ręcznie.

  1. Pobierz plik sklep.db lub utwórz wcześniej swoją wersję
  2. Wklej go do projektu:
    Dodaj → Istniejący element → sklep.db
  3. Ustaw właściwości pliku:

Dzięki temu plik trafi do folderu aplikacji i program go znajdzie.

Dodajemy paczkę obsługi SQLite z paczka pakietów NuGet->
System.Data.SQLite.Core


7. Budujemy interfejs — MainWindow.xaml

Najpierw wytłumaczenie:


PEŁNY KOD MainWindow.xaml

<Window x:Class="BazaDanychWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Baza Danych WPF - Produkty" Height="500" Width="750">

    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!-- Górny panel -->
        <StackPanel Orientation="Horizontal" Margin="0,0,0,10">

            <!-- Panel danych -->
            <StackPanel Width="220">
                <TextBlock Text="Nazwa:"/>
                <TextBox x:Name="txtNazwa"/>

                <TextBlock Text="Cena:" Margin="0,10,0,0"/>
                <TextBox x:Name="txtCena"/>

                <TextBlock Text="ID (edycja/usuwanie):" Margin="0,10,0,0"/>
                <TextBox x:Name="txtID"/>

                <Button Content="Dodaj" Margin="0,10,0,0" Click="btnDodaj_Click"/>
                <Button Content="Zapisz zmiany" Click="btnZapisz_Click"/>
                <Button Content="Usuń" Click="btnUsun_Click"/>
            </StackPanel>

            <!-- Panel wyszukiwarki -->
            <StackPanel Margin="20,0,0,0" Width="200">
                <TextBlock Text="Szukaj nazwy:"/>
                <TextBox x:Name="txtSzukaj"/>

                <Button Content="Szukaj" Margin="0,10,0,0" Click="btnSzukaj_Click"/>
                <Button Content="Odśwież" Click="btnOdswiez_Click"/>
            </StackPanel>
        </StackPanel>

        <!-- Tabela -->
        <DataGrid x:Name="gridProdukty"
                  Grid.Row="1"
                  AutoGenerateColumns="True"
                  SelectionChanged="gridProdukty_SelectionChanged"/>
    </Grid>
</Window>

8. Logika okna — MainWindow.xaml.cs


Wytłumaczenie przed kodem


PEŁNY KOD MainWindow.xaml.cs

using System;
using System.Data;
using System.Windows;
using System.Windows.Controls;

namespace BazaDanychWPF
{
    public partial class MainWindow : Window
    {
        // Obiekt odpowiedzialny za pracę z bazą danych
        BazaDanych baza = new BazaDanych();

        public MainWindow()
        {
            InitializeComponent();

            // Po uruchomieniu okna pobieramy dane z bazy
            WyswietlProdukty();
        }

        // Metoda wczytuje dane z bazy i ustawia je jako źródło dla tabeli
        private void WyswietlProdukty(string filtr = "")
        {
            gridProdukty.ItemsSource = baza.PobierzProdukty(filtr).DefaultView;
        }

        // Przycisk DODAJ – dodaje nowy rekord do bazy
        private void btnDodaj_Click(object sender, RoutedEventArgs e)
        {
            if (string.IsNullOrWhiteSpace(txtNazwa.Text))
            {
                MessageBox.Show("Podaj nazwę!");
                return;
            }

            // Sprawdzamy, czy cena jest poprawną liczbą
            if (!double.TryParse(txtCena.Text, out double cena))
            {
                MessageBox.Show("Cena musi być liczbą.");
                return;
            }

            // Wywołanie metody z klasy BazaDanych
            baza.DodajProdukt(txtNazwa.Text, cena);

            // Odświeżenie tabeli
            WyswietlProdukty();
        }

        // Przycisk USUŃ – usuwa rekord na podstawie ID
        private void btnUsun_Click(object sender, RoutedEventArgs e)
        {
            if (!int.TryParse(txtID.Text, out int id))
            {
                MessageBox.Show("Podaj poprawne ID!");
                return;
            }

            baza.UsunProdukt(id);
            WyswietlProdukty();
        }

        // Przycisk ZAPISZ – zapisuje nowe wartości do wybranego rekordu
        private void btnZapisz_Click(object sender, RoutedEventArgs e)
        {
            if (!int.TryParse(txtID.Text, out int id))
            {
                MessageBox.Show("Podaj ID!");
                return;
            }

            if (!double.TryParse(txtCena.Text, out double cena))
            {
                MessageBox.Show("Cena musi być liczbą.");
                return;
            }

            baza.EdytujProdukt(id, txtNazwa.Text, cena);
            WyswietlProdukty();
        }

        // Po kliknięciu rekordu w tabeli wypełniamy pola edycji
        private void gridProdukty_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (gridProdukty.SelectedItem is DataRowView row)
            {
                txtID.Text = row["id"].ToString();
                txtNazwa.Text = row["nazwa"].ToString();
                txtCena.Text = row["cena"].ToString();
            }
        }

        // Wyszukiwanie po fragmencie nazwy
        private void btnSzukaj_Click(object sender, RoutedEventArgs e)
        {
            WyswietlProdukty(txtSzukaj.Text);
        }

        // Przywrócenie pełnej listy produktów
        private void btnOdswiez_Click(object sender, RoutedEventArgs e)
        {
            WyswietlProdukty();
        }
    }
}

9. Logika bazy — BazaDanych.cs

Tutaj dzieją się trzy ważne rzeczy:
1. Tworzenie połączenia

Każda operacja na bazie musi mieć:

using var con = new SQLiteConnection(sciezka);
con.Open();

using sprawia, że połączenie zamknie się automatycznie.


2. Tworzenie tabeli na starcie

Program sam tworzy tabelę, jeśli jej nie ma — idealne rozwiązanie egzaminacyjne.

3. Wykonywanie SQL

INSERT / DELETE / UPDATE używają:

cmd.ExecuteNonQuery();

SELECT używa:

cmd.ExecuteReader();

a dane są ładowane do DataTable.


PEŁNY KOD BazaDanych.cs

using System;
using System.Data;
using System.Data.SQLite;

namespace BazaDanychWPF
{
    public class BazaDanych
    {
        // Ścieżka do pliku bazy danych (w katalogu aplikacji)
        private string sciezka =
            $"Data Source={AppDomain.CurrentDomain.BaseDirectory}sklep.db;Version=3;"; 

//***

        public BazaDanych()
        {
            // Przy starcie programu tworzymy tabelę, jeśli jej nie ma
            UtworzTabeleJesliNieIstnieje();
        }

        // Tworzenie tabeli w bazie – tylko jeśli nie istnieje
        private void UtworzTabeleJesliNieIstnieje()
        {
            using var con = new SQLiteConnection(sciezka);
            con.Open();

            string sql =
                "CREATE TABLE IF NOT EXISTS Produkty (" +
                "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
                "nazwa TEXT NOT NULL, " +
                "cena REAL NOT NULL);";

            using var cmd = new SQLiteCommand(sql, con);
            cmd.ExecuteNonQuery(); // wykonanie polecenia SQL
        }

        // Pobieranie danych z bazy (SELECT)
        public DataTable PobierzProdukty(string filtr = "")
        {
            using var con = new SQLiteConnection(sciezka);
            con.Open();

            string sql = "SELECT * FROM Produkty";

            // Filtr wyszukiwania
            if (!string.IsNullOrWhiteSpace(filtr))
                sql += " WHERE nazwa LIKE @f";

            using var cmd = new SQLiteCommand(sql, con);

            if (!string.IsNullOrWhiteSpace(filtr))
                cmd.Parameters.AddWithValue("@f", "%" + filtr + "%");

            using SQLiteDataReader r = cmd.ExecuteReader();

            // Wczytanie wyników SELECT do tabeli w pamięci
            DataTable dt = new DataTable();
            dt.Load(r);
            return dt;
        }

        // Dodawanie rekordu
        public void DodajProdukt(string nazwa, double cena)
        {
            using var con = new SQLiteConnection(sciezka);
            con.Open();

            string sql = "INSERT INTO Produkty(nazwa, cena) VALUES (@n,@c)";
            using var cmd = new SQLiteCommand(sql, con);

            cmd.Parameters.AddWithValue("@n", nazwa);
            cmd.Parameters.AddWithValue("@c", cena);

            cmd.ExecuteNonQuery();
        }

        // Usuwanie rekordu po ID
        public void UsunProdukt(int id)
        {
            using var con = new SQLiteConnection(sciezka);
            con.Open();

            string sql = "DELETE FROM Produkty WHERE id=@id";
            using var cmd = new SQLiteCommand(sql, con);

            cmd.Parameters.AddWithValue("@id", id);

            cmd.ExecuteNonQuery();
        }

        // Edycja rekordu
        public void EdytujProdukt(int id, string nazwa, double cena)
        {
            using var con = new SQLiteConnection(sciezka);
            con.Open();

            string sql = "UPDATE Produkty SET nazwa=@n, cena=@c WHERE id=@id";
            using var cmd = new SQLiteCommand(sql, con);

            cmd.Parameters.AddWithValue("@n", nazwa);
            cmd.Parameters.AddWithValue("@c", cena);
            cmd.Parameters.AddWithValue("@id", id);

            cmd.ExecuteNonQuery();
        }
    }
}

*** „Na egzaminie zamiast własnego sklep.db ustaw w connection stringu ścieżkę do pliku bazy podanej w treści zadania (np. Data Source=C:\\Egzamin\\baza\\egzamin.db).”


10. Najpotrzebniejsze komendy C# i SQL

CelKomenda
Połączenie z baząnew SQLiteConnection(...)
Otwieraniecon.Open()
Wykonywanie SELECTcmd.ExecuteReader()
Wykonywanie INSERT/DELETE/UPDATEcmd.ExecuteNonQuery()
Parametry SQLcmd.Parameters.AddWithValue("@x", val)
Ładowanie wynikówdt.Load(reader)
Autoużycie połączeńusing var con = ...

Hmmmmmm……

11. Rozbudowa bazy danych – co by było, gdybyśmy chcieli dodać koszyk zamówień?

Do tej pory aplikacja obsługiwała jedną tabelę – Produkty.
Pozwalała ona dodawać, edytować, usuwać i wyszukiwać produkty w bazie danych.

Co by było jednak, gdybyśmy chcieli stworzyć prosty koszyk zamówień, podobny do tego, który znamy ze sklepów internetowych?

W takiej sytuacji jedna tabela już nie wystarczy.


11.1. Nowa koncepcja – druga tabela w bazie danych

Aby stworzyć koszyk, potrzebujemy informacji:

Nie chcemy jednak powielać danych (nazwy i ceny produktów), bo:

👉 Dlatego tworzymy drugą tabelę, która odwołuje się do produktów przez ich ID.


11.2. Struktura nowej tabeli – Koszyk

Dodajemy tabelę Koszyk o następującej strukturze:

KolumnaOpis
idunikalny identyfikator wpisu w koszyku
produkt_idID produktu z tabeli Produkty
iloscilość danego produktu

📌 Tabela Koszyk nie przechowuje nazwy ani ceny produktu.
📌 Przechowuje tylko informację, który produkt i w jakiej ilości znajduje się w koszyku.

To jest najprostszy i poprawny model relacji danych na poziomie INF.04.


11.3. Rozbudowa bazy – tworzenie drugiej tabeli

Rozbudowę zaczynamy od warstwy bazy danych, czyli pliku BazaDanych.cs.

📄 Plik: BazaDanych.cs

W metodzie UtworzTabeleJesliNieIstnieje() dopiszemy tworzenie drugiej tabeli:

// Tworzenie tabeli koszyka – tylko jeśli nie istnieje
string sqlKoszyk =
    "CREATE TABLE IF NOT EXISTS Koszyk (" +
    "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
    "produkt_id INTEGER NOT NULL, " +
    "ilosc INTEGER NOT NULL);";

using var cmd2 = new SQLiteCommand(sqlKoszyk, con);
cmd2.ExecuteNonQuery();

Wyjaśnienie:


11.4. Dodawanie produktu do koszyka

Następnie dodajemy metodę umożliwiającą zapis produktu do koszyka.

📄 Plik: BazaDanych.cs

// Dodaje wybrany produkt do koszyka
public void DodajDoKoszyka(int produktId, int ilosc)
{
    using var con = new SQLiteConnection(sciezka);
    con.Open();

    string sql =
        "INSERT INTO Koszyk (produkt_id, ilosc) " +
        "VALUES (@pid, @ilosc)";

    using var cmd = new SQLiteCommand(sql, con);
    cmd.Parameters.AddWithValue("@pid", produktId);
    cmd.Parameters.AddWithValue("@ilosc", ilosc);

    cmd.ExecuteNonQuery();
}

Wyjaśnienie:


11.5. Pobieranie koszyka – połączenie danych z dwóch tabel

Aby wyświetlić koszyk w aplikacji, potrzebujemy:

W tym miejscu łączymy dane z dwóch tabel.

📄 Plik: BazaDanych.cs

// Pobiera zawartość koszyka wraz z danymi produktu
public DataTable PobierzKoszyk()
{
    using var con = new SQLiteConnection(sciezka);
    con.Open();

    string sql =
        "SELECT Koszyk.id, Produkty.nazwa, Produkty.cena, Koszyk.ilosc, " +
        "(Produkty.cena * Koszyk.ilosc) AS suma " +
        "FROM Koszyk " +
        "JOIN Produkty ON Koszyk.produkt_id = Produkty.id";

    using var cmd = new SQLiteCommand(sql, con);
    using SQLiteDataReader r = cmd.ExecuteReader();

    DataTable dt = new DataTable();
    dt.Load(r);
    return dt;
}

Wyjaśnienie:

📌 To jedyny moment, w którym używamy JOIN.


11.6. Podsumowanie rozbudowy bazy danych

Po tej modyfikacji aplikacja:

A TERAZ WYGLĄD i LOGIKA

11.7. Rozbudowa interfejsu – dodanie koszyka zamówień

Skoro baza danych została rozbudowana o drugą tabelę (Koszyk),
musimy umożliwić użytkownikowi:

Zrobimy to bez tworzenia nowego okna, tylko przez rozbudowę istniejącego interfejsu.


11.7.1. Zmiany w pliku MainWindow.xaml

Dodajemy:

Nie usuwamy nic z poprzedniego interfejsu – tylko dopisujemy.


Fragment do dopisania w panelu danych (pod polem ID)

<TextBlock Text="Ilość:" Margin="0,10,0,0"/>
<TextBox x:Name="txtIlosc"/>

<Button Content="Dodaj do koszyka"
        Margin="0,10,0,0"
        Click="btnDodajDoKoszyka_Click"/>

Zmiana układu siatki (na samej górze pliku)

Zmieniamy definicję wierszy:

<Grid.RowDefinitions>
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="*"/>
    <RowDefinition Height="*"/>
</Grid.RowDefinitions>

Drugi DataGrid – koszyk (na dole okna)

<DataGrid x:Name="gridKoszyk"
          Grid.Row="2"
          Margin="0,10,0,0"
          AutoGenerateColumns="True"/>

Wyjaśnienie dla ucznia:


11.8. Logika koszyka – MainWindow.xaml.cs

Teraz musimy:


11.8.1. Metoda wyświetlania koszyka

Plik: MainWindow.xaml.cs

// Wyświetlenie zawartości koszyka
private void WyswietlKoszyk()
{
    gridKoszyk.ItemsSource = baza.PobierzKoszyk().DefaultView;
}

Analogicznie do WyswietlProdukty().


11.8.2. Dodawanie produktu do koszyka

private void btnDodajDoKoszyka_Click(object sender, RoutedEventArgs e)
{
    // Sprawdzenie, czy wybrano produkt
    if (!int.TryParse(txtID.Text, out int produktId))
    {
        MessageBox.Show("Wybierz produkt z listy.");
        return;
    }

    // Sprawdzenie ilości
    if (!int.TryParse(txtIlosc.Text, out int ilosc))
    {
        MessageBox.Show("Podaj poprawną ilość.");
        return;
    }

    // Dodanie produktu do koszyka
    baza.DodajDoKoszyka(produktId, ilosc);

    // Odświeżenie widoku koszyka
    WyswietlKoszyk();
}

CAŁOŚĆ XAML

<Window x:Class="BazaDanychWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Baza Danych WPF - Produkty i Koszyk"
        Height="650" Width="750">

    <Grid Margin="10">

        <!-- ZMIANA: dodajemy trzeci wiersz na koszyk -->
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <!-- NOWE -->
        </Grid.RowDefinitions>

        <!-- GÓRNY PANEL -->
        <StackPanel Orientation="Horizontal" Margin="0,0,0,10">

            <!-- LEWY PANEL: formularz produktu -->
            <StackPanel Width="220">
                <TextBlock Text="Nazwa:"/>
                <TextBox x:Name="txtNazwa"/>

                <TextBlock Text="Cena:" Margin="0,10,0,0"/>
                <TextBox x:Name="txtCena"/>

                <TextBlock Text="ID (edycja/usuwanie):" Margin="0,10,0,0"/>
                <TextBox x:Name="txtID"/>

                <Button Content="Dodaj"
                        Margin="0,10,0,0"
                        Click="btnDodaj_Click"/>

                <Button Content="Zapisz zmiany"
                        Click="btnZapisz_Click"/>

                <Button Content="Usuń"
                        Click="btnUsun_Click"/>

                <!-- ============================== -->
                <!-- NOWE ELEMENTY – KOSZYK -->
                <!-- ============================== -->

                <TextBlock Text="Ilość:"
                           Margin="0,20,0,0"/>
                <!-- NOWE -->
                <TextBox x:Name="txtIlosc"/>
                <!-- NOWE -->

                <Button Content="Dodaj do koszyka"
                        Margin="0,10,0,0"
                        Click="btnDodajDoKoszyka_Click"/>
                <!-- NOWE -->
            </StackPanel>

            <!-- PRAWY PANEL: wyszukiwanie -->
            <StackPanel Margin="20,0,0,0" Width="200">
                <TextBlock Text="Szukaj nazwy:"/>
                <TextBox x:Name="txtSzukaj"/>

                <Button Content="Szukaj"
                        Margin="0,10,0,0"
                        Click="btnSzukaj_Click"/>

                <Button Content="Odśwież"
                        Click="btnOdswiez_Click"/>
            </StackPanel>

        </StackPanel>

        <!-- TABELA PRODUKTÓW -->
        <DataGrid x:Name="gridProdukty"
                  Grid.Row="1"
                  Margin="0,0,0,10"
                  AutoGenerateColumns="True"
                  SelectionChanged="gridProdukty_SelectionChanged"/>

        <!-- NOWE: TABELA KOSZYKA -->
        <DataGrid x:Name="gridKoszyk"
                  Grid.Row="2"
                  AutoGenerateColumns="True"/>
        <!-- NOWE -->

    </Grid>
</Window>