Загрузка данных
пояснение
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("Выберите студента в таблице.");
}
}
}
```