Загрузка данных


пояснение
1. Строка подключения (Ключ от базы данных)
Она всегда пишется в самом начале класса окна.

C#
// Это строка - как "адрес" и "ключи" от твоей базы данных.
// Data Source - имя твоего сервера (компьютера), где лежит база.
// Initial Catalog - название самой базы данных.
// Integrated Security=True - используем авторизацию Windows (без пароля).
// TrustServerCertificate=True - говорим программе не бояться локального сервера.
private string connectionString = @"Data Source=СВОЙ_СЕРВЕР;Initial Catalog=LibraryDB;Integrated Security=True;TrustServerCertificate=True";
2. Чтение данных (SELECT)
Этот метод мы используем, чтобы вытащить данные из базы и показать их в таблице DataGrid на экране.

C#
private void LoadData()
{
    // Блок using автоматически закроет подключение к базе в самом конце,
    // даже если внутри произойдет ошибка. Это экономит память.
    using (SqlConnection conn = new SqlConnection(connectionString))
    {
        // 1. Пишем сам SQL-запрос. Звездочка означает "взять все колонки".
        string query = "SELECT * FROM Books";
        
        // 2. Создаем "Адаптер" (SqlDataAdapter). 
        // Это специальный умный помощник. Ему мы даем текст запроса и подключение.
        // Он САМ откроет базу, выполнит SELECT и заберет результат.
        SqlDataAdapter adapter = new SqlDataAdapter(query, conn);
        
        // 3. Создаем пустую виртуальную табличку в оперативной памяти программы.
        DataTable dt = new DataTable();
        
        // 4. Просим нашего помощника-адаптера залить выкачанные из БД данные
        // в нашу пустую табличку dt.
        adapter.Fill(dt);
        
        // 5. Говорим нашему интерфейсу (DataGridBooks на экране): 
        // "Эй, возьми данные из виртуальной таблички и покажи их пользователю!"
        DataGridBooks.ItemsSource = dt.DefaultView;
    }
}
3. Изменение данных (INSERT / UPDATE / DELETE)
Этот шаблон одинаков для добавления, обновления и удаления. Рассмотрим на примере Добавления (INSERT).

C#
private void BtnAdd_Click(object sender, RoutedEventArgs e)
{
    // Сначала идет проверка (валидация), чтобы пользователь не ввел пустоту.
    if (string.IsNullOrWhiteSpace(txtTitle.Text))
    {
        MessageBox.Show("Название не может быть пустым!");
        return; // Выходим из метода, дальше код не пойдет
    }

    // Снова создаем канал связи с базой данных
    using (SqlConnection conn = new SqlConnection(connectionString))
    {
        // 1. Пишем SQL-запрос. 
        // ВМЕСТО реальных значений мы пишем "заглушки" (параметры), которые начинаются с символа @.
        // Это нужно, чтобы хакер не мог сломать базу (защита от SQL-инъекций).
        string query = "INSERT INTO Books (Title, Author) VALUES (@Title, @Author)";
        
        // 2. Создаем "Команду" (SqlCommand) - это курьер, который понесет запрос в базу.
        // Даем курьеру текст запроса (query) и показываем путь к базе (conn).
        SqlCommand cmd = new SqlCommand(query, conn);
        
        // 3. Заполняем наши "заглушки" реальными данными из текстовых полей на экране.
        // Говорим: "Вместо @Title подставь то, что написано в txtTitle.Text".
        cmd.Parameters.AddWithValue("@Title", txtTitle.Text);
        cmd.Parameters.AddWithValue("@Author", txtAuthor.Text);

        // 4. Вручную ОТКРЫВАЕМ подключение (как поднять трубку телефона перед разговором).
        // При SELECT за нас это делал Адаптер, а здесь мы должны сделать это сами.
        conn.Open();
        
        // 5. Даем команду: "ВЫПОЛНИТЬ!". 
        // ExecuteNonQuery переводится как "выполнить запрос, который не возвращает таблицу".
        // База данных получит наш INSERT и запишет строку в таблицу.
        cmd.ExecuteNonQuery();
    } // Здесь блок using автоматически сделает conn.Close() (повесит трубку)

    // 6. Вызываем наш метод чтения, чтобы таблица на экране обновилась 
    // и показала только что добавленную запись.
    LoadData(); 
    
    // 7. Радуем пользователя сообщением.
    MessageBox.Show("Книга добавлена!");
}













Билет №1: Библиотечный учет @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@



### Шаг 1: Подготовка базы данных (SSMS)

1. Открой **SQL Server Management Studio 20**.
2. Создай пустую базу данных: нажми правой кнопкой на `Databases` -> `New Database...` -> назови её, например, **LibraryDB**.
3. Таблицу руками создавать не нужно, так как в задании №1 сказано: «создание таблицы при первом запуске». Мы напишем код, который сделает это сам.



### Шаг 2: Интерфейс (XAML)

Создай проект **Приложение WPF (.NET Framework)**.
Открой `MainWindow.xaml` и замени код внутри `<Window> ... </Window>` на этот простой макет. Здесь есть поля для ввода, кнопки для каждого задания и таблица для вывода данных:

```xml
<Grid Margin="10">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="250"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <StackPanel Grid.Column="0" Margin="0,0,10,0">
        <TextBlock Text="Название (Title):"/>
        <TextBox x:Name="txtTitle" Margin="0,0,0,5"/>
        
        <TextBlock Text="Автор (Author):"/>
        <TextBox x:Name="txtAuthor" Margin="0,0,0,5"/>

        <TextBlock Text="Год издания (PublishYear):"/>
        <TextBox x:Name="txtYear" Margin="0,0,0,5"/>

        <TextBlock Text="Жанр (Genre):"/>
        <TextBox x:Name="txtGenre" Margin="0,0,0,5"/>

        <CheckBox x:Name="chkIsAvailable" Content="В наличии" Margin="0,0,0,10" IsChecked="True"/>

        <Button Content="Добавить" Click="BtnAdd_Click" Margin="0,0,0,5"/>
        <Button Content="Изменить выбранное" Click="BtnUpdate_Click" Margin="0,0,0,5"/>
        <Button Content="Удалить выбранное" Click="BtnDelete_Click" Margin="0,0,0,15"/>

        <TextBlock Text="Фильтр (Жанр или Автор):" FontWeight="Bold"/>
        <TextBox x:Name="txtFilter" Margin="0,5,0,5"/>
        <Button Content="Найти" Click="BtnFilter_Click" Margin="0,0,0,5"/>
        <Button Content="Сбросить фильтр" Click="BtnResetFilter_Click"/>
    </StackPanel>

    <DataGrid x:Name="DataGridBooks" Grid.Column="1" AutoGenerateColumns="True" IsReadOnly="True" SelectionMode="Single"/>
</Grid>

```

### Шаг 3: Логика программы (C#)

Открой `MainWindow.xaml.cs`. Здесь мы реализуем все пункты задания:

* 
**Задание 1:** Создание таблицы при старте.


* 
**Задание 2:** Чтение и фильтрация.


* 
**Задание 3:** Добавление с валидацией (год и пустое название).


* 
**Задание 4:** Изменение данных.


* 
**Задание 5:** Удаление с подтверждением.



Скопируй этот код. **Важно:** замени `YOUR_SERVER_NAME` в строке подключения на имя твоего сервера из SSMS (или скопируй строку из Обозревателя серверов в Visual Studio).

```csharp
using System;
using System.Data;
using System.Data.SqlClient;
using System.Windows;

namespace LibraryApp
{
    public partial class MainWindow : Window
    {
        // Строка подключения (Замени YOUR_SERVER_NAME на свой, например: localhost или .\SQLEXPRESS)
        private string connectionString = @"Data Source=YOUR_SERVER_NAME;Initial Catalog=LibraryDB;Integrated Security=True";

        public MainWindow()
        {
            InitializeComponent();
            
            // Задание 1: Создание таблицы при первом запуске
            CreateTableIfNotExists(); 
            
            // Задание 2: Вывод списка всех книг
            LoadData(); 
        }

        // --- ЗАДАНИЕ 1: Создание таблицы ---
        private void CreateTableIfNotExists()
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                conn.Open();
                string query = @"
                    IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='Books' and xtype='U')
                    CREATE TABLE Books (
                        Id INT PRIMARY KEY IDENTITY(1,1),
                        Title NVARCHAR(100) NOT NULL,
                        Author NVARCHAR(100),
                        PublishYear INT,
                        Genre NVARCHAR(50),
                        IsAvailable BIT
                    )";
                SqlCommand cmd = new SqlCommand(query, conn);
                cmd.ExecuteNonQuery();
            }
        }

        // --- ЗАДАНИЕ 2: Просмотр (Read) и Фильтрация ---
        private void LoadData(string filter = "")
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                string query = "SELECT * FROM Books";
                
                // Фильтрация по жанру или автору
                if (!string.IsNullOrEmpty(filter))
                {
                    query += " WHERE Genre LIKE @filter OR Author LIKE @filter";
                }

                SqlCommand cmd = new SqlCommand(query, conn);
                if (!string.IsNullOrEmpty(filter))
                {
                    cmd.Parameters.AddWithValue("@filter", "%" + filter + "%");
                }

                SqlDataAdapter adapter = new SqlDataAdapter(cmd);
                DataTable dt = new DataTable();
                adapter.Fill(dt);
                DataGridBooks.ItemsSource = dt.DefaultView;
            }
        }

        private void BtnFilter_Click(object sender, RoutedEventArgs e)
        {
            LoadData(txtFilter.Text);
        }

        private void BtnResetFilter_Click(object sender, RoutedEventArgs e)
        {
            txtFilter.Clear();
            LoadData();
        }

        // --- ЗАДАНИЕ 3: Добавление (Create) с валидацией ---
        private void BtnAdd_Click(object sender, RoutedEventArgs e)
        {
            // Валидация: название не пустое, год не больше текущего
            if (string.IsNullOrWhiteSpace(txtTitle.Text))
            {
                MessageBox.Show("Название книги не может быть пустым!");
                return;
            }

            if (!int.TryParse(txtYear.Text, out int year) || year > DateTime.Now.Year)
            {
                MessageBox.Show("Некорректный год издания (не может быть больше текущего)!");
                return;
            }

            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                string query = "INSERT INTO Books (Title, Author, PublishYear, Genre, IsAvailable) VALUES (@Title, @Author, @Year, @Genre, @IsAvail)";
                SqlCommand cmd = new SqlCommand(query, conn);
                cmd.Parameters.AddWithValue("@Title", txtTitle.Text);
                cmd.Parameters.AddWithValue("@Author", txtAuthor.Text);
                cmd.Parameters.AddWithValue("@Year", year);
                cmd.Parameters.AddWithValue("@Genre", txtGenre.Text);
                cmd.Parameters.AddWithValue("@IsAvail", chkIsAvailable.IsChecked ?? false);

                conn.Open();
                cmd.ExecuteNonQuery();
            }
            LoadData(); // Обновляем таблицу
            MessageBox.Show("Книга добавлена!");
        }

        // --- ЗАДАНИЕ 4: Изменение (Update) ---
        private void BtnUpdate_Click(object sender, RoutedEventArgs e)
        {
            // Получаем выбранную строку из DataGrid
            if (DataGridBooks.SelectedItem is DataRowView row)
            {
                int id = Convert.ToInt32(row["Id"]);

                using (SqlConnection conn = new SqlConnection(connectionString))
                {
                    // Обновляем данные на те, что сейчас введены в текстовые поля
                    string query = "UPDATE Books SET Title=@Title, Author=@Author, PublishYear=@Year, Genre=@Genre, IsAvailable=@IsAvail WHERE Id=@Id";
                    SqlCommand cmd = new SqlCommand(query, conn);
                    cmd.Parameters.AddWithValue("@Id", id);
                    cmd.Parameters.AddWithValue("@Title", txtTitle.Text);
                    cmd.Parameters.AddWithValue("@Author", txtAuthor.Text);
                    cmd.Parameters.AddWithValue("@Year", int.Parse(txtYear.Text)); // В реальном проекте тут тоже нужна валидация
                    cmd.Parameters.AddWithValue("@Genre", txtGenre.Text);
                    cmd.Parameters.AddWithValue("@IsAvail", chkIsAvailable.IsChecked ?? false);

                    conn.Open();
                    cmd.ExecuteNonQuery();
                }
                LoadData();
                MessageBox.Show("Данные обновлены!");
            }
            else
            {
                MessageBox.Show("Выберите книгу в таблице для изменения.");
            }
        }

        // --- ЗАДАНИЕ 5: Удаление (Delete) с подтверждением ---
        private void BtnDelete_Click(object sender, RoutedEventArgs e)
        {
            if (DataGridBooks.SelectedItem is DataRowView row)
            {
                int id = Convert.ToInt32(row["Id"]);

                // Подтверждение действия
                MessageBoxResult result = MessageBox.Show("Вы уверены, что хотите удалить эту книгу?", "Подтверждение", MessageBoxButton.YesNo, MessageBoxImage.Warning);
                
                if (result == MessageBoxResult.Yes)
                {
                    using (SqlConnection conn = new SqlConnection(connectionString))
                    {
                        string query = "DELETE FROM Books WHERE Id=@Id";
                        SqlCommand cmd = new SqlCommand(query, conn);
                        cmd.Parameters.AddWithValue("@Id", id);

                        conn.Open();
                        cmd.ExecuteNonQuery();
                    }
                    LoadData();
                }
            }
            else
            {
                MessageBox.Show("Выберите книгу в таблице для удаления.");
            }
        }
    }
}

```

### Как работать с этим на экзамене:

1. Тебе не нужно заучивать код наизусть. Главное понять паттерн: `SqlConnection` (подключение) -> `SqlCommand` (запрос) -> `conn.Open()` -> `cmd.ExecuteNonQuery()` (выполнение).
2. Запросы параметризованы (через `@Title` и т.д.) — это защищает от SQL-инъекций и решает проблемы с типами данных.
3. Валидация делается простыми `if` до открытия подключения к базе.
4. Вывод данных через `SqlDataAdapter` в `DataTable` и привязка к `DataGrid.ItemsSource` — это самый быстрый способ вывести данные без лишнего кода.



Вот пошаговый алгоритм, как собрать интерфейс для Билета №1, кликая мышкой в Visual Studio.

---

## Шаг 1: Делим окно на две части (Сетку)

По умолчанию на форме у тебя находится один большой `Grid` (сетка). Нам нужно разделить его на две колонки: узкую левую (для кнопок и полей) и широкую правую (для таблицы).

1. Посмотри на визуальный конструктор (дизайнер) твоего окна.
2. Подведи курсор к **самой верхней рамке (линейке)** окна примерно на четверть расстояния слева. Курсор превратится в стрелочку с плюсиком.
3. Кликни один раз. Появится вертикальная пунктирная линия. Ты только что разделил окно на `Column 0` (левая) и `Column 1` (правая).
4. Нажми на эту линию на линейке и в появившемся маленьком меню убедись, что для левой колонки можно задать фиксированную ширину (например, `250`), а для правой оставить `*` (чтобы она растягивалась).

---

## Шаг 2: Создаем контейнер для кнопок и полей

Чтобы элементы на левой панели не накладывались друг на друга, их нужно упаковать в вертикальный стек.

1. Открой **Панель элементов (Toolbox)** (если её нет, нажми `Ctrl + Alt + X`).
2. Найди там элемент **StackPanel** (он находится в группе «Все элементы управления WPF» или «Панели»).
3. Зажми его мышкой и перетащи в **левую колонку** нашего окна.
4. Выдели этот `StackPanel` кликом, зайди в окно **Свойства (Properties)** (справа внизу) и найди свойство **Margin**. Поставь там, например, `10`, чтобы элементы не прилипали к краям окна.

---

## Шаг 3: Набрасываем поля ввода (Данные книги)

Теперь по очереди перетаскиваем элементы внутрь нашей левой `StackPanel` сверху вниз. Для каждого текстового поля нам нужна подпись (`TextBlock`) и само поле (`TextBox`).

1. Перетащи **TextBlock**. В окне *Свойства* найди поле **Text** и напиши: `Название книги:`.


2. Сразу под него перетащи **TextBox**. В окне *Свойства* найди самое верхнее поле **Имя (Name)** и назови его `txtTitle`. *(Поле Text у него сотри, чтобы оно было пустым!)*


3. Повтори это для остальных полей:
* 
**TextBlock** (`Автор:`) -> **TextBox** с именем `txtAuthor`.


* 
**TextBlock** (`Год издания:`) -> **TextBox** с именем `txtYear`.


* 
**TextBlock** (`Жанр:`) -> **TextBox** с именем `txtGenre`.




4. Перетащи **CheckBox**. В свойстве **Content** напиши `В наличии` , свойство **Name** укажи как `chkIsAvailable`, а свойство **IsChecked** выставь в `True` (поставив галочку в свойствах).



Чтобы элементы визуально не слипались, у каждого `TextBox` и `TextBlock` в свойствах можно задать **Margin** (например, нижний отступ `5`).

---

## Шаг 4: Добавляем кнопки действий

Продолжаем тащить элементы в эту же левую панель, прямо под поля ввода:

1. Перетащи **Button**. В свойстве **Content** напиши `Добавить`, в поле **Name** — `BtnAdd`.


2. Перетащи второй **Button**. **Content**: `Изменить выбранное`, **Name**: `BtnUpdate`.


3. Перетащи третий **Button**. **Content**: `Удалить выбранное`, **Name**: `BtnDelete`.



---

## Шаг 5: Делаем блок фильтрации

В этой же левой панели сделаем визуальное разделение для поиска:

1. Перетащи **TextBlock**. **Content**: `Фильтр (Жанр/Автор):`. Можно в свойствах текста (Font) сделать его **Bold** (жирным).


2. Перетащи **TextBox**. **Name**: `txtFilter`.
3. Перетащи **Button**. **Content**: `Найти`, **Name**: `BtnFilter`.


4. Перетащи **Button**. **Content**: `Сбросить`, **Name**: `BtnResetFilter`.



---

## Шаг 6: Ставим таблицу в правую часть

Левая панель готова. Переходим к выводу данных.

1. В **Панели элементов** найди **DataGrid** (это наша таблица).


2. Перетащи её в **правую колонку** окна.
3. В окне *Свойства* задай ей **Name**: `DataGridBooks`.


4. Там же в свойствах найди галочку **IsReadOnly** и поставь её (чтобы пользователь не мог менять данные прямо в ячейках таблицы, ведь у нас для этого есть кнопки).



---

## Шаг 7: Оживляем кнопки (Создаем события)

Интерфейс собран, теперь нужно связать кнопки с кодом C#.

1. Просто **дважды кликни левой кнопкой мыши** по кнопке «Добавить» прямо на главном экране конструктора.
2. Visual Studio автоматически перекинет тебя в файл кода (`MainWindow.xaml.cs`) и создаст пустой метод `BtnAdd_Click`.


3. Вернись в конструктор и точно так же дважды кликни по кнопкам «Изменить», «Удалить», «Найти» и «Сбросить».



Теперь у тебя готовы все «крючки» в коде, куда останется просто вписать логику работы с БД (тот C#-код, который мы разбирали ранее).

Хочешь сейчас подробно разобрать, как написать логику подключения к SQL Server через Обозреватель серверов без написания строк подключения вручную?









































Билет №2: Складской учет товаров @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Что нужно сделать по заданию: Работать с таблицей Products (поля: Name, Category, Quantity, Price). Реализовать вывод с сортировкой по цене/количеству , добавление с валидацией (неотрицательные числа) , функции «Приход/Списание» и полное редактирование , а также удаление с проверкой на существование Id.  

1. Как собрать интерфейс через Панель элементов (Toolbox)Раздели главный Grid окна на две колонки на верхней линейке (левая ~260, правая *).Перетащи StackPanel в левую колонку. Назови её в свойствах (или оставь без имени), задай Margin = 10.Внутрь StackPanel по очереди перетащи элементы для ввода данных товара:TextBlock (Название товара:) $\rightarrow$ TextBox с именем txtName.TextBlock (Категория:) $\rightarrow$ TextBox с именем txtCategory.TextBlock (Количество:) $\rightarrow$ TextBox с именем txtQuantity.TextBlock (Цена:) $\rightarrow$ TextBox с именем txtPrice.Перетащи кнопки действий в эту же StackPanel:Button (Добавить товар) $\rightarrow$ имя BtnAdd.Button (Сохранить изменения карточки) $\rightarrow$ имя BtnUpdate.Button (Удалить товар) $\rightarrow$ имя BtnDelete.Ниже добавь текстовое поле и кнопки для быстрых функций «Приход/Списание»:  TextBlock (Количество для Прихода/Списания:) $\rightarrow$ TextBox с именем txtChangeAmount.Button (Приход (+)) $\rightarrow$ имя BtnIncome.Button (Списание (-)) $\rightarrow$ имя BtnExpense.Добавь блок сортировки:  TextBlock (Сортировка:) $\rightarrow$ Button (По цене) с именем BtnSortPrice и Button (По количеству) с именем BtnSortQty.В правую колонку перетащи таблицу DataGrid, задай ей имя DataGridProducts и поставь галочку IsReadOnly = True в свойствах.Дважды кликни по каждой кнопке на форме, чтобы сгенерировать события в C#.2. Код интерфейса 

	(MainWindow.xaml)XML<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Складской учет" Height="500" Width="850">
    <Grid Margin="10">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="260"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <StackPanel Grid.Column="0" Margin="0,0,10,0">
            <TextBlock Text="Наименование товара:"/>
            <TextBox x:Name="txtName" Margin="0,0,0,5"/>

            <TextBlock Text="Категория:"/>
            <TextBox x:Name="txtCategory" Margin="0,0,0,5"/>

            <TextBlock Text="Количество:"/>
            <TextBox x:Name="txtQuantity" Margin="0,0,0,5"/>

            <TextBlock Text="Цена за единицу:"/>
            <TextBox x:Name="txtPrice" Margin="0,0,0,10"/>

            <Button Content="Добавить" Click="BtnAdd_Click" Margin="0,0,0,5"/>
            <Button Content="Изменить карточку" Click="BtnUpdate_Click" Margin="0,0,0,5"/>
            <Button Content="Удалить товар" Click="BtnDelete_Click" Margin="0,0,0,15"/>

            <TextBlock Text="Операции с количеством:" FontWeight="Bold"/>
            <TextBox x:Name="txtChangeAmount" Margin="0,2,0,5" ToolTip="Введите число для изменения"/>
            <UniformGrid Columns="2" Margin="0,0,0,15">
                <Button Content="Приход (+)" Click="BtnIncome_Click" Margin="0,0,2,0"/>
                <Button Content="Списание (-)" Click="BtnExpense_Click" Margin="2,0,0,0"/>
            </UniformGrid>

            <TextBlock Text="Сортировать по:" FontWeight="Bold"/>
            <UniformGrid Columns="2" Margin="0,5,0,0">
                <Button Content="Цене" Click="BtnSortPrice_Click" Margin="0,0,2,0"/>
                <Button Content="Количеству" Click="BtnSortQty_Click" Margin="2,0,0,0"/>
            </UniformGrid>
        </StackPanel>

        <DataGrid x:Name="DataGridProducts" Grid.Column="1" AutoGenerateColumns="True" IsReadOnly="True" SelectionMode="Single"/>
    </Grid>
</Window>
3. Логика программы (MainWindow.xaml.cs)C#using System;
using System.Data;
using System.Data.SqlClient;
using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        // ====================================================================================
        // ИНСТРУКЦИЯ: Замени СВОЙ_СЕРВЕР на имя твоего сервера из SSMS (например: .\SQLEXPRESS или localhost)
        // ====================================================================================
        private string connectionString = @"Data Source=СВОЙ_СЕРВЕР;Initial Catalog=StorageDB;Integrated Security=True;TrustServerCertificate=True";

        public MainWindow()
        {
            InitializeComponent();
            CreateTableIfNotExists(); // Создаем таблицу, чтобы программа сразу работала
            LoadData();
        }

        [cite_start]// Задание 1: Создание таблицы и подключение [cite: 23]
        private void CreateTableIfNotExists()
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                conn.Open();
                string query = @"
                    IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='Products' and xtype='U')
                    CREATE TABLE Products (
                        Id INT PRIMARY KEY IDENTITY(1,1),
                        Name NVARCHAR(100) NOT NULL,
                        Category NVARCHAR(100),
                        Quantity INT NOT NULL,
                        Price DECIMAL(18,2) NOT NULL
                    )";
                SqlCommand cmd = new SqlCommand(query, conn);
                cmd.ExecuteNonQuery();
            }
        }

        [cite_start]// Задание 2: Просмотр и сортировка 
        private void LoadData(string orderBy = "Id")
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                // Прямая подстановка имени колонки безопасна, если мы сами передаем её в коде (Price или Quantity)
                string query = $"SELECT * FROM Products ORDER BY {orderBy}";
                SqlDataAdapter adapter = new SqlDataAdapter(query, conn);
                DataTable dt = new DataTable();
                adapter.Fill(dt);
                DataGridProducts.ItemsSource = dt.DefaultView;
            }
        }

        private void BtnSortPrice_Click(object sender, RoutedEventArgs e) => LoadData("Price");
        private void BtnSortQty_Click(object sender, RoutedEventArgs e) => LoadData("Quantity");

        [cite_start]// Задание 3: Добавление с валидацией 
        private void BtnAdd_Click(object sender, RoutedEventArgs e)
        {
            if (string.IsNullOrWhiteSpace(txtName.Text))
            {
                MessageBox.Show("Наименование товара не может быть пустым!");
                return;
            }

            [cite_start]// Проверка, что количество и цена — числа и они неотрицательные 
            if (!int.TryParse(txtQuantity.Text, out int qty) || qty < 0)
            {
                MessageBox.Show("Количество должно быть целым неотрицательным числом!");
                return;
            }

            if (!decimal.TryParse(txtPrice.Text, out decimal price) || price < 0)
            {
                MessageBox.Show("Цена не может быть отрицательной!");
                return;
            }

            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                string query = "INSERT INTO Products (Name, Category, Quantity, Price) VALUES (@Name, @Category, @Qty, @Price)";
                SqlCommand cmd = new SqlCommand(query, conn);
                cmd.Parameters.AddWithValue("@Name", txtName.Text);
                cmd.Parameters.AddWithValue("@Category", txtCategory.Text);
                cmd.Parameters.AddWithValue("@Qty", qty);
                cmd.Parameters.AddWithValue("@Price", price);

                conn.Open();
                cmd.ExecuteNonQuery();
            }
            LoadData();
            MessageBox.Show("Товар успешно добавлен!");
        }

        [cite_start]// Задание 4: Полное редактирование карточки [cite: 27]
        private void BtnUpdate_Click(object sender, RoutedEventArgs e)
        {
            if (DataGridProducts.SelectedItem is DataRowView row)
            {
                int id = Convert.ToInt32(row["Id"]);

                if (!int.TryParse(txtQuantity.Text, out int qty) || qty < 0 || !decimal.TryParse(txtPrice.Text, out decimal price) || price < 0)
                {
                    MessageBox.Show("Проверьте корректность введенных чисел (не могут быть отрицательными)!");
                    return;
                }

                using (SqlConnection conn = new SqlConnection(connectionString))
                {
                    string query = "UPDATE Products SET Name=@Name, Category=@Category, Quantity=@Qty, Price=@Price WHERE Id=@Id";
                    SqlCommand cmd = new SqlCommand(query, conn);
                    cmd.Parameters.AddWithValue("@Id", id);
                    cmd.Parameters.AddWithValue("@Name", txtName.Text);
                    cmd.Parameters.AddWithValue("@Category", txtCategory.Text);
                    cmd.Parameters.AddWithValue("@Qty", qty);
                    cmd.Parameters.AddWithValue("@Price", price);

                    conn.Open();
                    cmd.ExecuteNonQuery();
                }
                LoadData();
                MessageBox.Show("Карточка товара обновлена!");
            }
            else MessageBox.Show("Выберите товар в таблице!");
        }

        [cite_start]// Задание 4: Приход и Списание товара 
        private void ChangeQuantity(bool isIncome)
        {
            if (DataGridProducts.SelectedItem is DataRowView row)
            {
                int id = Convert.ToInt32(row["Id"]);
                int currentQty = Convert.ToInt32(row["Quantity"]);

                if (!int.TryParse(txtChangeAmount.Text, out int amount) || amount <= 0)
                {
                    MessageBox.Show("Введите корректное число больше нуля для изменения количества!");
                    return;
                }

                // Рассчитываем новое количество на складе
                int newQty = isIncome ? currentQty + amount : currentQty - amount;

                if (newQty < 0)
                {
                    MessageBox.Show("Ошибка! На складе нет такого количества товара для списания!");
                    return;
                }

                using (SqlConnection conn = new SqlConnection(connectionString))
                {
                    string query = "UPDATE Products SET Quantity=@Qty WHERE Id=@Id";
                    SqlCommand cmd = new SqlCommand(query, conn);
                    cmd.Parameters.AddWithValue("@Qty", newQty);
                    cmd.Parameters.AddWithValue("@Id", id);

                    conn.Open();
                    cmd.ExecuteNonQuery();
                }
                LoadData();
                txtChangeAmount.Clear();
            }
            else MessageBox.Show("Выберите товар в таблице!");
        }

        private void BtnIncome_Click(object sender, RoutedEventArgs e) => ChangeQuantity(true);
        private void BtnExpense_Click(object sender, RoutedEventArgs e) => ChangeQuantity(false);

        [cite_start]// Задание 5: Удаление и обработка несуществующего Id 
        private void BtnDelete_Click(object sender, RoutedEventArgs e)
        {
            if (DataGridProducts.SelectedItem is DataRowView row)
            {
                int id = Convert.ToInt32(row["Id"]);

                using (SqlConnection conn = new SqlConnection(connectionString))
                {
                    string query = "DELETE FROM Products WHERE Id=@Id";
                    SqlCommand cmd = new SqlCommand(query, conn);
                    cmd.Parameters.AddWithValue("@Id", id);

                    conn.Open();
                    int rowsAffected = cmd.ExecuteNonQuery();

                    [cite_start]// Обработка ситуации, если запись с таким Id уже была кем-то удалена или не существует 
                    if (rowsAffected == 0)
                    {
                        MessageBox.Show("Ошибка: Товар с таким ID не найден в базе данных!");
                    }
                    else
                    {
                        MessageBox.Show("Товар успешно удален.");
                    }
                }
                LoadData();
            }
            else MessageBox.Show("Выберите товар для удаления.");
        }
    }
}

Билет №3: Кадровый учет сотрудников   @@@@@@@@@@@@@@@

Что нужно сделать по заданию: Работать с таблицей Employees (поля: FullName, Position, Department, HireDate, Salary). Важное условие: Реализовать слой работы с данными (DAL)  — это значит, что весь SQL-код мы вынесем в отдельный красивый класс, а в окне останется только интерфейс. Также нужен поиск по ФИО/должности , добавление с валидацией даты (не из будущего) и оклада (>0) , изменение и удаление.  1. Как собрать интерфейс через Панель элементов (Toolbox)Раздели сетку окна на 2 колонки (левая 260, правая *).В левую колонку перетащи StackPanel.Внутрь StackPanel перетащи элементы ввода:TextBlock (ФИО сотрудника:) $\rightarrow$ TextBox с именем txtFullName.TextBlock (Должность:) $\rightarrow$ TextBox с именем txtPosition.TextBlock (Отдел:) $\rightarrow$ TextBox с именем txtDepartment.TextBlock (Дата приема:) $\rightarrow$ элемент DatePicker (выбор даты из календаря) с именем dpHireDate.TextBlock (Оклад:) $\rightarrow$ TextBox с именем txtSalary.Добавь стандартные кнопки: BtnAdd («Принять на работу»), BtnUpdate («Изменить данные»), BtnDelete («Уволить»).Добавь блок поиска:  TextBlock (Поиск (ФИО / Должность):) $\rightarrow$ TextBox с именем txtSearch.Button (Найти) с именем BtnSearch и Button (Сбросить) с именем BtnReset.В правую колонку добавь DataGrid с именем DataGridEmployees и IsReadOnly = True.2. Код интерфейса (MainWindow.xaml)XML<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Кадровый учет" Height="500" Width="850">
    <Grid Margin="10">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="260"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <StackPanel Grid.Column="0" Margin="0,0,10,0">
            <TextBlock Text="ФИО сотрудника:"/>
            <TextBox x:Name="txtFullName" Margin="0,0,0,5"/>

            <TextBlock Text="Должность:"/>
            <TextBox x:Name="txtPosition" Margin="0,0,0,5"/>

            <TextBlock Text="Отдел:"/>
            <TextBox x:Name="txtDepartment" Margin="0,0,0,5"/>

            <TextBlock Text="Дата приема на работу:"/>
            <DatePicker x:Name="dpHireDate" Margin="0,0,0,5"/>

            <TextBlock Text="Оклад:"/>
            <TextBox x:Name="txtSalary" Margin="0,0,0,10"/>

            <Button Content="Принять на работу" Click="BtnAdd_Click" Margin="0,0,0,5"/>
            <Button Content="Изменить данные" Click="BtnUpdate_Click" Margin="0,0,0,5"/>
            <Button Content="Уволить (Удалить)" Click="BtnDelete_Click" Margin="0,0,0,20"/>

            <TextBlock Text="Поиск (ФИО или Должность):" FontWeight="Bold"/>
            <TextBox x:Name="txtSearch" Margin="0,5,0,5"/>
            <Button Content="Найти" Click="BtnSearch_Click" Margin="0,0,0,5"/>
            <Button Content="Сбросить поиск" Click="BtnReset_Click"/>
        </StackPanel>

        <DataGrid x:Name="DataGridEmployees" Grid.Column="1" AutoGenerateColumns="True" IsReadOnly="True" SelectionMode="Single"/>
    </Grid>
</Window>
3. Создание слоя DAL (Отдельный файл класса)Нажми правой кнопкой на свой проект в Обозревателе решений $\rightarrow$ Добавить (Add) $\rightarrow Класс (Class). Назови его EmployeeDAL.cs. Вставь туда этот код работы с БД:C#using System;
using System.Data;
using System.Data.SqlClient;

namespace WpfApp1
{
    public class EmployeeDAL
    {
        // ====================================================================================
        // ИНСТРУКЦИЯ: Замени СВОЙ_СЕРВЕР на имя твоего сервера из SSMS
        // ====================================================================================
        private string connectionString = @"Data Source=СВОЙ_СЕРВЕР;Initial Catalog=StaffDB;Integrated Security=True;TrustServerCertificate=True";

        [cite_start]// Задание 1: Проверка и создание таблицы внутри DAL слоя 
        public void CreateTable()
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                conn.Open();
                string query = @"
                    IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='Employees' and xtype='U')
                    CREATE TABLE Employees (
                        Id INT PRIMARY KEY IDENTITY(1,1),
                        FullName NVARCHAR(150) NOT NULL,
                        Position NVARCHAR(100),
                        Department NVARCHAR(100),
                        HireDate DATETIME,
                        Salary DECIMAL(18,2)
                    )";
                SqlCommand cmd = new SqlCommand(query, conn);
                cmd.ExecuteNonQuery();
            }
        }

        [cite_start]// Задание 2: Получение списка и Поиск 
        public DataTable GetAllEmployees(string searchKeyword = "")
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                string query = "SELECT * FROM Employees";
                if (!string.IsNullOrEmpty(searchKeyword))
                {
                    query += " WHERE FullName LIKE @search OR Position LIKE @search";
                }

                SqlCommand cmd = new SqlCommand(query, conn);
                if (!string.IsNullOrEmpty(searchKeyword))
                {
                    cmd.Parameters.AddWithValue("@search", "%" + searchKeyword + "%");
                }

                SqlDataAdapter adapter = new SqlDataAdapter(cmd);
                DataTable dt = new DataTable();
                adapter.Fill(dt);
                return dt;
            }
        }

        [cite_start]// Задание 3: Добавление записи 
        public void AddEmployee(string name, string pos, string dept, DateTime hireDate, decimal salary)
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                string query = "INSERT INTO Employees (FullName, Position, Department, HireDate, Salary) VALUES (@Name, @Pos, @Dept, @Date, @Salary)";
                SqlCommand cmd = new SqlCommand(query, conn);
                cmd.Parameters.AddWithValue("@Name", name);
                cmd.Parameters.AddWithValue("@Pos", pos);
                cmd.Parameters.AddWithValue("@Dept", dept);
                cmd.Parameters.AddWithValue("@Date", hireDate);
                cmd.Parameters.AddWithValue("@Salary", salary);

                conn.Open();
                cmd.ExecuteNonQuery();
            }
        }

        [cite_start]// Задание 4: Обновление записи 
        public void UpdateEmployee(int id, string name, string pos, string dept, DateTime hireDate, decimal salary)
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                string query = "UPDATE Employees SET FullName=@Name, Position=@Pos, Department=@Dept, HireDate=@Date, Salary=@Salary WHERE Id=@Id";
                SqlCommand cmd = new SqlCommand(query, conn);
                cmd.Parameters.AddWithValue("@Id", id);
                cmd.Parameters.AddWithValue("@Name", name);
                cmd.Parameters.AddWithValue("@Pos", pos);
                cmd.Parameters.AddWithValue("@Dept", dept);
                cmd.Parameters.AddWithValue("@Date", hireDate);
                cmd.Parameters.AddWithValue("@Salary", salary);

                conn.Open();
                cmd.ExecuteNonQuery();
            }
        }

        [cite_start]// Задание 5: Удаление (Физическое) 
        public void DeleteEmployee(int id)
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                string query = "DELETE FROM Employees WHERE Id=@Id";
                SqlCommand cmd = new SqlCommand(query, conn);
                cmd.Parameters.AddWithValue("@Id", id);

                conn.Open();
                cmd.ExecuteNonQuery();
            }
        }
    }
}
4. Логика формы (MainWindow.xaml.cs) — использует только DAL слойтеперь код нашей формы чистый, в нем нет SQL-строк, потому что мы выполнили требование  №3 о разделении слоев!  C#using System;
using System.Data;
using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        // Создаем экземпляр нашего слоя работы с данными
        private EmployeeDAL dal = new EmployeeDAL();

        public MainWindow()
        {
            InitializeComponent();
            dal.CreateTable(); // Создаем таблицу через DAL
            LoadGrid();
            dpHireDate.SelectedDate = DateTime.Now; // Ставим текущую дату по умолчанию
        }

        private void LoadGrid(string search = "")
        {
            DataGridEmployees.ItemsSource = dal.GetAllEmployees(search).DefaultView;
        }

        [cite_start]// Кнопка поиска 
        private void BtnSearch_Click(object sender, RoutedEventArgs e) => LoadGrid(txtSearch.Text);
        private void BtnReset_Click(object sender, RoutedEventArgs e) { txtSearch.Clear(); LoadGrid(); }

        [cite_start]// Задание 3: Добавление сотрудника с валидацией интерфейса 
        private void BtnAdd_Click(object sender, RoutedEventArgs e)
        {
            if (string.IsNullOrWhiteSpace(txtFullName.Text))
            {
                MessageBox.Show("ФИО сотрудника не может быть пустым!");
                return;
            }

            DateTime selectedDate = dpHireDate.SelectedDate ?? DateTime.Now;
            [cite_start]// Проверка: Дата приема не может быть из будущего 
            if (selectedDate > DateTime.Now)
            {
                MessageBox.Show("Дата приема на работу не может быть в будущем!");
                return;
            }

            [cite_start]// Проверка: Оклад должен быть больше нуля 
            if (!decimal.TryParse(txtSalary.Text, out decimal salary) || salary <= 0)
            {
                MessageBox.Show("Оклад должен быть числом больше нуля!");
                return;
            }

            dal.AddEmployee(txtFullName.Text, txtPosition.Text, txtDepartment.Text, selectedDate, salary);
            LoadGrid();
            MessageBox.Show("Сотрудник успешно принят на работу!");
        }

        [cite_start]// Задание 4: Редактирование 
        private void BtnUpdate_Click(object sender, RoutedEventArgs e)
        {
            if (DataGridEmployees.SelectedItem is DataRowView row)
            {
                int id = Convert.ToInt32(row["Id"]);
                DateTime selectedDate = dpHireDate.SelectedDate ?? DateTime.Now;

                if (selectedDate > DateTime.Now || !decimal.TryParse(txtSalary.Text, out decimal salary) || salary <= 0)
                {
                    MessageBox.Show("Проверьте правильность даты и оклада (> 0)!");
                    return;
                }

                dal.UpdateEmployee(id, txtFullName.Text, txtPosition.Text, txtDepartment.Text, selectedDate, salary);
                LoadGrid();
                MessageBox.Show("Данные сотрудника обновлены!");
            }
            else MessageBox.Show("Выберите сотрудника в таблице!");
        }

        [cite_start]// Задание 5: Удаление (Увольнение) 
        private void BtnDelete_Click(object sender, RoutedEventArgs e)
        {
            if (DataGridEmployees.SelectedItem is DataRowView row)
            {
                int id = Convert.ToInt32(row["Id"]);

                MessageBoxResult result = MessageBox.Show("Вы уверены, что хотите уволить сотрудника и удалить запись?", "Внимание", MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result == MessageBoxResult.Yes)
                {
                    dal.DeleteEmployee(id);
                    LoadGrid();
                }
            }
            else MessageBox.Show("Выберите сотрудника в таблице!");
        }
    }
}
 













Эта ошибка — очень забавный артефакт копирования! Она возникает из-за того, что в C# квадратные скобки `[...]` зарезервированы для так называемых **атрибутов** (специальных пометок для классов и методов).

Когда ты копировал код или текст, туда случайно попали скрытые теги разметки платформы или системные маркеры (вроде `[cite_start]`). Компилятор C# увидел квадратные скобки, подумал, что ты пытаешься применить какой-то продвинутый атрибут, автоматически добавил к нему слово `Attribute` и теперь ругается, что не знает, что это такое.

### Как это исправить за 5 секунд:

1. Открой файлы **MainWindow.xaml.cs** и **EmployeeDAL.cs** (если успел его создать).
2. Нажми комбинацию клавиш `Ctrl + F` (поиск).
3. Введи в поиск слово **cite** или **cite_start**.
4. Ты увидишь строчки, где затесались эти маркеры в квадратных скобках. **Просто полностью удали эти строки или эти скобки с текстом.** В нашем коде никаких квадратных скобок в C# быть не должно!

---


---

# Билет №4: Учет заявок в автосервисе@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

**Что нужно сделать по заданию:** Таблица `ServiceOrders` (поля: ClientName, CarModel, IssueDescription, Status, Cost). Нужно корректно работать с типом `nullable` для стоимости (так как цена неизвестна до конца ремонта), сделать текстовую или цветовую индикацию статуса, запретить удаление заявок в статусе «Готово».

### 1. Как собрать интерфейс через Панель элементов (Toolbox)

1. Раздели сетку главного окна на две колонки (левая `260` для кнопок, правая `*` для таблицы).
2. Перетащи **StackPanel** в левую колонку (`Margin = 10`).
3. Внутрь `StackPanel` по очереди перетащи элементы:
* **TextBlock** (`Имя клиента:`) $\rightarrow$ **TextBox** с именем `txtClientName`.
* **TextBlock** (`Модель авто:`) $\rightarrow$ **TextBox** с именем `txtCarModel`.
* **TextBlock** (`Неисправность:`) $\rightarrow$ **TextBox** с именем `txtIssue`.
* **TextBlock** (`Статус заявки:`) $\rightarrow$ **ComboBox** (выпадающий список) с именем `cmbStatus`.
* *Чтобы заполнить ComboBox статусами без кода: выдели его, найди в Свойствах поле **Items**, нажми кнопку с троеточием и добавь 3 строки: "Новая", "В работе", "Готово".*


* **TextBlock** (`Стоимость (можно пусто):`) $\rightarrow$ **TextBox** с именем `txtCost`.


4. Перетащи кнопки действий ниже в ту же `StackPanel`:
* **Button** (`Создать заявку`) $\rightarrow$ имя `BtnAdd`.
* **Button** (`Обновить статус и цену`) $\rightarrow$ имя `BtnUpdate`.
* **Button** (`Удалить ошибку`) $\rightarrow$ имя `BtnDelete`.


5. В правую колонку перетащи таблицу **DataGrid**, задай имя `DataGridOrders`, свойство `IsReadOnly = True`.
6. Дважды кликни по кнопкам «Создать», «Обновить», «Удалить», чтобы создать события в C#.

### 2. Код интерфейса (MainWindow.xaml)

```xml
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Автосервис - Заявки" Height="500" Width="850">
    <Grid Margin="10">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="260"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <StackPanel Grid.Column="0" Margin="0,0,10,0">
            <TextBlock Text="Имя клиента:"/>
            <TextBox x:Name="txtClientName" Margin="0,0,0,5"/>

            <TextBlock Text="Марка и модель авто:"/>
            <TextBox x:Name="txtCarModel" Margin="0,0,0,5"/>

            <TextBlock Text="Описание неисправности:"/>
            <TextBox x:Name="txtIssue" Margin="0,0,0,5" Height="50" AcceptsReturn="True" TextWrapping="Wrap"/>

            <TextBlock Text="Статус:"/>
            <ComboBox x:Name="cmbStatus" Margin="0,0,0,5">
                <ComboBoxItem Content="Новая" IsSelected="True"/>
                <ComboBoxItem Content="В работе"/>
                <ComboBoxItem Content="Готово"/>
            </ComboBox>

            <TextBlock Text="Стоимость ремонта (руб):"/>
            <TextBox x:Name="txtCost" Margin="0,0,0,15"/>

            <Button Content="Создать новую заявку" Click="BtnAdd_Click" Margin="0,0,0,5"/>
            <Button Content="Изменить статус/стоимость" Click="BtnUpdate_Click" Margin="0,0,0,5"/>
            <Button Content="Удалить заявку" Click="BtnDelete_Click"/>
        </StackPanel>

        <DataGrid x:Name="DataGridOrders" Grid.Column="1" AutoGenerateColumns="True" IsReadOnly="True" SelectionMode="Single"/>
    </Grid>
</Window>

```

### 3. Логика программы (MainWindow.xaml.cs)

```csharp
using System;
using System.Data;
using System.Data.SqlClient;
using System.Windows;
using System.Windows.Controls;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        // ====================================================================================
        // ИНСТРУКЦИЯ: Замени СВОЙ_СЕРВЕР на имя твоего сервера из SSMS (например: .\SQLEXPRESS)
        // ====================================================================================
        private string connectionString = @"Data Source=СВОЙ_СЕРВЕР;Initial Catalog=AutoServiceDB;Integrated Security=True;TrustServerCertificate=True";

        public MainWindow()
        {
            InitializeComponent();
            CreateTableIfNotExists();
            LoadData();
        }

        // Задание 1: Подключение к БД и поддержка Nullable типа для Cost
        private void CreateTableIfNotExists()
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                conn.Open();
                string query = @"
                    IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='ServiceOrders' and xtype='U')
                    CREATE TABLE ServiceOrders (
                        Id INT PRIMARY KEY IDENTITY(1,1),
                        ClientName NVARCHAR(100) NOT NULL,
                        CarModel NVARCHAR(100) NOT NULL,
                        IssueDescription NVARCHAR(MAX),
                        Status NVARCHAR(50) NOT NULL,
                        Cost DECIMAL(18,2) NULL -- Поле может быть NULL, как просит 
                    )";
                SqlCommand cmd = new SqlCommand(query, conn);
                cmd.ExecuteNonQuery();
            }
        }

        // Задание 2: Просмотр списка заявок
        private void LoadData()
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                // Для текстовой индикации мы можем выводить статус как есть, sql вернет красивую таблицу
                string query = "SELECT Id, ClientName, CarModel, IssueDescription, Status, ISNULL(CAST(Cost AS NVARCHAR), 'Не рассчитана') AS [Cost] FROM ServiceOrders";
                SqlDataAdapter adapter = new SqlDataAdapter(query, conn);
                DataTable dt = new DataTable();
                adapter.Fill(dt);
                DataGridOrders.ItemsSource = dt.DefaultView;
            }
        }

        // Задание 3: Добавление новой заявки
        private void BtnAdd_Click(object sender, RoutedEventArgs e)
        {
            if (string.IsNullOrWhiteSpace(txtClientName.Text) || string.IsNullOrWhiteSpace(txtCarModel.Text))
            {
                MessageBox.Show("Имя клиента и модель машины обязательны для заполнения!");
                return;
            }

            // Обработка nullable стоимости: если поле пустое, то в БД запишем NULL
            decimal? cost = null;
            if (!string.IsNullOrWhiteSpace(txtCost.Text))
            {
                if (decimal.TryParse(txtCost.Text, out decimal parsedCost) && parsedCost >= 0)
                {
                    cost = parsedCost;
                }
                else
                {
                    MessageBox.Show("Стоимость должна быть положительным числом или оставаться пустой!");
                    return;
                }
            }

            string status = ((ComboBoxItem)cmbStatus.SelectedItem).Content.ToString();

            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                string query = "INSERT INTO ServiceOrders (ClientName, CarModel, IssueDescription, Status, Cost) VALUES (@Client, @Car, @Issue, @Status, @Cost)";
                SqlCommand cmd = new SqlCommand(query, conn);
                cmd.Parameters.AddWithValue("@Client", txtClientName.Text);
                cmd.Parameters.AddWithValue("@Car", txtCarModel.Text);
                cmd.Parameters.AddWithValue("@Issue", txtIssue.Text);
                cmd.Parameters.AddWithValue("@Status", status);
                
                // Если cost null, отправляем специальный DBNull.Value
                cmd.Parameters.AddWithValue("@Cost", (object)cost ?? DBNull.Value);

                conn.Open();
                cmd.ExecuteNonQuery();
            }
            LoadData();
            MessageBox.Show("Заявка успешно зарегистрирована!");
        }

        // Задание 4: Изменение статуса и стоимости
        private void BtnUpdate_Click(object sender, RoutedEventArgs e)
        {
            if (DataGridOrders.SelectedItem is DataRowView row)
            {
                int id = Convert.ToInt32(row["Id"]);

                decimal? cost = null;
                if (!string.IsNullOrWhiteSpace(txtCost.Text))
                {
                    if (!decimal.TryParse(txtCost.Text, out decimal parsedCost) || parsedCost < 0)
                    {
                        MessageBox.Show("Некорректное значение стоимости!");
                        return;
                    }
                    cost = parsedCost;
                }

                string status = ((ComboBoxItem)cmbStatus.SelectedItem).Content.ToString();

                using (SqlConnection conn = new SqlConnection(connectionString))
                {
                    string query = "UPDATE ServiceOrders SET Status=@Status, Cost=@Cost WHERE Id=@Id";
                    SqlCommand cmd = new SqlCommand(query, conn);
                    cmd.Parameters.AddWithValue("@Id", id);
                    cmd.Parameters.AddWithValue("@Status", status);
                    cmd.Parameters.AddWithValue("@Cost", (object)cost ?? DBNull.Value);

                    conn.Open();
                    cmd.ExecuteNonQuery();
                }
                LoadData();
                MessageBox.Show("Заявка успешно обновлена!");
            }
            else MessageBox.Show("Выберите заявку в таблице.");
        }

        // Задание 5: Удаление с запретом для статуса "Готово"
        private void BtnDelete_Click(object sender, RoutedEventArgs e)
        {
            if (DataGridOrders.SelectedItem is DataRowView row)
            {
                int id = Convert.ToInt32(row["Id"]);
                string currentStatus = row["Status"].ToString();

                // КРИТИЧЕСКОЕ ТРЕБОВАНИЕ : Запретить удаление если статус "Готово"
                if (currentStatus == "Готово")
                {
                    MessageBox.Show("Ошибка! Нельзя удалять выполненные заявки со статусом 'Готово'!", "Отказ в доступе", MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }

                MessageBoxResult result = MessageBox.Show("Вы уверены, что хотите удалить эту заявку?", "Подтверждение", MessageBoxButton.YesNo, MessageBoxImage.Warning);
                if (result == MessageBoxResult.Yes)
                {
                    using (SqlConnection conn = new SqlConnection(connectionString))
                    {
                        string query = "DELETE FROM ServiceOrders WHERE Id=@Id";
                        SqlCommand cmd = new SqlCommand(query, conn);
                        cmd.Parameters.AddWithValue("@Id", id);

                        conn.Open();
                        cmd.ExecuteNonQuery();
                    }
                    LoadData();
                    MessageBox.Show("Заявка удалена.");
                }
            }
            else MessageBox.Show("Выберите заявку для удаления.");
        }
    }
}

```

---

# Билет №5: Учет успеваемости студентов

**Что нужно сделать по заданию:** Таблица `Students` (поля: FullName, GroupNumber, AverageGrade, IsScholarship). Требуется использовать строго параметризованные запросы, валидировать балл от 2.0 до 5.0, а также встроить автоматическую бизнес-логику: если балл обновляется и становится $\ge 4.5$, то галочка стипендии (`IsScholarship`) автоматически переключается в `true`.

### 1. Как собрать интерфейс через Панель элементов (Toolbox)

1. Раздели сетку окна на две колонки (левая `260`, правая `*`).
2. В левую колонку перетащи **StackPanel** (`Margin = 10`).
3. Внутрь `StackPanel` закинь элементы управления:
* **TextBlock** (`ФИО студента:`) $\rightarrow$ **TextBox** с именем `txtFullName`.
* **TextBlock** (`Номер группы:`) $\rightarrow$ **TextBox** с именем `txtGroup`.
* **TextBlock** (`Средний балл (2.0 - 5.0):`) $\rightarrow$ **TextBox** с именем `txtGrade`.
* **CheckBox** с текстом `Получает стипендию` $\rightarrow$ имя `chkScholarship`.


4. Ниже перетащи кнопки:
* **Button** (`Добавить студента`) $\rightarrow$ имя `BtnAdd`.
* **Button** (`Обновить балл (после сессии)`) $\rightarrow$ имя `BtnUpdateGrade`.
* **Button** (`Отчислить (Удалить)`) $\rightarrow$ имя `BtnDelete`.


5. В правую колонку перетащи таблицу **DataGrid**, назови её `DataGridStudents`, поставь галочку `IsReadOnly = True`.
6. Сделай двойной клик по всем трем кнопкам.

### 2. Код интерфейса (MainWindow.xaml)

```xml
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Успеваемость студентов" Height="500" Width="850">
    <Grid Margin="10">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="260"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <StackPanel Grid.Column="0" Margin="0,0,10,0">
            <TextBlock Text="ФИО Студента:"/>
            <TextBox x:Name="txtFullName" Margin="0,0,0,5"/>

            <TextBlock Text="Номер группы (например, ИВТ-21):"/>
            <TextBox x:Name="txtGroup" Margin="0,0,0,5"/>

            <TextBlock Text="Средний балл (от 2.0 до 5.0):"/>
            <TextBox x:Name="txtGrade" Margin="0,0,0,5"/>

            <CheckBox x:Name="chkScholarship" Content="Наличие стипендии" Margin="0,5,0,15"/>

            <Button Content="Добавить студента" Click="BtnAdd_Click" Margin="0,0,0,5"/>
            <Button Content="Обновить балл после сессии" Click="BtnUpdateGrade_Click" Margin="0,0,0,5"/>
            <Button Content="Отчислить студента" Click="BtnDelete_Click"/>
        </StackPanel>

        <DataGrid x:Name="DataGridStudents" Grid.Column="1" AutoGenerateColumns="True" IsReadOnly="True" SelectionMode="Single"/>
    </Grid>
</Window>

```

### 3. Логика программы (MainWindow.xaml.cs)

```csharp
using System;
using System.Data;
using System.Data.SqlClient;
using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        // ====================================================================================
        // ИНСТРУКЦИЯ: Замени СВОЙ_СЕРВЕР на имя твоего сервера из SSMS (например: localhost)
        // ====================================================================================
        private string connectionString = @"Data Source=СВОЙ_СЕРВЕР;Initial Catalog=UniversityDB;Integrated Security=True;TrustServerCertificate=True";

        public MainWindow()
        {
            InitializeComponent();
            CreateTableIfNotExists();
            LoadData();
        }

        // Задание 1: Подключение и создание таблицы
        private void CreateTableIfNotExists()
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                conn.Open();
                string query = @"
                    IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='Students' and xtype='U')
                    CREATE TABLE Students (
                        Id INT PRIMARY KEY IDENTITY(1,1),
                        FullName NVARCHAR(150) NOT NULL,
                        GroupNumber NVARCHAR(50) NOT NULL,
                        AverageGrade FLOAT NOT NULL,
                        IsScholarship BIT NOT NULL
                    )";
                SqlCommand cmd = new SqlCommand(query, conn);
                cmd.ExecuteNonQuery();
            }
        }

        // Задание 2: Вывод списка студентов
        private void LoadData()
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                // Запрос вытаскивает все данные напрямую в таблицу
                string query = "SELECT * FROM Students";
                SqlDataAdapter adapter = new SqlDataAdapter(query, conn);
                DataTable dt = new DataTable();
                adapter.Fill(dt);
                DataGridStudents.ItemsSource = dt.DefaultView;
            }
        }

        // Задание 3: Добавление с валидацией диапазона балла
        private void BtnAdd_Click(object sender, RoutedEventArgs e)
        {
            if (string.IsNullOrWhiteSpace(txtFullName.Text) || string.IsNullOrWhiteSpace(txtGroup.Text))
            {
                MessageBox.Show("ФИО и Номер группы не могут быть пустыми!");
                return;
            }

            // ТРЕБОВАНИЕ : Средний балл должен быть строго в диапазоне от 2.0 до 5.0
            if (!double.TryParse(txtGrade.Text, out double grade) || grade < 2.0 || grade > 5.0)
            {
                MessageBox.Show("Ошибка! Средний балл должен быть числом в диапазоне от 2.0 до 5.0!");
                return;
            }

            bool isScholarship = chkScholarship.IsChecked ?? false;

            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                // ТРЕБОВАНИЕ : Используем исключительно параметризованные запросы
                string query = "INSERT INTO Students (FullName, GroupNumber, AverageGrade, IsScholarship) VALUES (@Name, @Group, @Grade, @Schol)";
                SqlCommand cmd = new SqlCommand(query, conn);
                cmd.Parameters.AddWithValue("@Name", txtFullName.Text);
                cmd.Parameters.AddWithValue("@Group", txtGroup.Text);
                cmd.Parameters.AddWithValue("@Grade", grade);
                cmd.Parameters.AddWithValue("@Schol", isScholarship);

                conn.Open();
                cmd.ExecuteNonQuery();
            }
            LoadData();
            MessageBox.Show("Студент успешно зачислен!");
        }

        // Задание 4: Изменение балла с автоматической бизнес-логикой стипендии
        private void BtnUpdateGrade_Click(object sender, RoutedEventArgs e)
        {
            if (DataGridStudents.SelectedItem is DataRowView row)
            {
                int id = Convert.ToInt32(row["Id"]);

                if (!double.TryParse(txtGrade.Text, out double newGrade) || newGrade < 2.0 || newGrade > 5.0)
                {
                    MessageBox.Show("Введите корректный балл от 2.0 до 5.0 для обновления!");
                    return;
                }

                // КРИТИЧЕСКОЕ ТРЕБОВАНИЕ : Бизнес-логика в коде.
                // Если средний балл становится >= 4.5, поле IsScholarship автоматически становится true.
                // В противном случае сохраняем то значение, которое сейчас выбрано в чекбоксе формы.
                bool targetScholarship = chkScholarship.IsChecked ?? false;
                if (newGrade >= 4.5)
                {
                    targetScholarship = true;
                }

                using (SqlConnection conn = new SqlConnection(connectionString))
                {
                    string query = "UPDATE Students SET AverageGrade=@Grade, IsScholarship=@Schol WHERE Id=@Id";
                    SqlCommand cmd = new SqlCommand(query, conn);
                    cmd.Parameters.AddWithValue("@Id", id);
                    cmd.Parameters.AddWithValue("@Grade", newGrade);
                    cmd.Parameters.AddWithValue("@Schol", targetScholarship);

                    conn.Open();
                    cmd.ExecuteNonQuery();
                }
                LoadData();
                MessageBox.Show(newGrade >= 4.5 
                    ? "Балл обновлен! Студенту автоматически назначена стипендия за отличную учебу." 
                    : "Балл успешно обновлен.");
            }
            else MessageBox.Show("Выберите студента в таблице для обновления балла.");
        }

        // Задание 5: Отчисление (Удаление) с подтверждением действия
        private void BtnDelete_Click(object sender, RoutedEventArgs e)
        {
            if (DataGridStudents.SelectedItem is DataRowView row)
            {
                int id = Convert.ToInt32(row["Id"]);
                string name = row["FullName"].ToString();

                // ТРЕБОВАНИЕ : Запрос подтверждения перед удалением
                MessageBoxResult result = MessageBox.Show($"Вы уверены, что хотите отчислить студента {name}?", "Подтверждение отчисления", MessageBoxButton.YesNo, MessageBoxImage.Warning);
                
                if (result == MessageBoxResult.Yes)
                {
                    using (SqlConnection conn = new SqlConnection(connectionString))
                    {
                        string query = "DELETE FROM Students WHERE Id=@Id";
                        SqlCommand cmd = new SqlCommand(query, conn);
                        cmd.Parameters.AddWithValue("@Id", id);

                        conn.Open();
                        cmd.ExecuteNonQuery();
                    }
                    LoadData();
                    MessageBox.Show("Студент успешно отчислен.");
                }
            }
            else MessageBox.Show("Выберите студента в таблице.");
        }
    }
}

```