diff --git a/AdoNetShop/AdoNetShop.csproj b/AdoNetShop/AdoNetShop.csproj
index b68cac9..678b460 100644
--- a/AdoNetShop/AdoNetShop.csproj
+++ b/AdoNetShop/AdoNetShop.csproj
@@ -5,6 +5,9 @@
net8.0
enable
enable
+
+ LatestMajor
diff --git a/AdoNetShop/Data/OrderRepository.cs b/AdoNetShop/Data/OrderRepository.cs
index 578dcd3..69a9a81 100644
--- a/AdoNetShop/Data/OrderRepository.cs
+++ b/AdoNetShop/Data/OrderRepository.cs
@@ -90,6 +90,120 @@ public class OrderRepository
}
}
+ // Демонстрация отката (ROLLBACK) для защиты лабораторной.
+ // Первая позиция списывается успешно, на второй специально выбрасывается
+ // ошибка. В результате БД не меняется вообще: остатки возвращаются
+ // к прежнему значению, а заказ не создаётся.
+ // Метод возвращает текст описания случившимся — для вывода в консоль.
+ public async Task CreateOrderWithRollbackDemoAsync(int customerId)
+ {
+ await using var connection = _connectionFactory.CreateConnection();
+ await connection.OpenAsync();
+
+ // Берём два товара, которые есть на складе
+ string selectSql = "SELECT TOP 2 Id, Name, Price, StockQuantity FROM Products WHERE StockQuantity >= 2 ORDER BY Id";
+ var demoItems = new List<(int Id, string Name, decimal Price, int Stock)>();
+
+ await using (var selectCommand = new SqlCommand(selectSql, connection))
+ await using (var reader = await selectCommand.ExecuteReaderAsync())
+ {
+ while (await reader.ReadAsync())
+ {
+ demoItems.Add((
+ reader.GetInt32(0),
+ reader.GetString(1),
+ reader.GetDecimal(2),
+ reader.GetInt32(3)));
+ }
+ }
+
+ if (demoItems.Count < 2)
+ return "Для демонстрации нужно минимум 2 товара с остатком от 2 штук.";
+
+ var report = new List();
+ report.Add("Остатки ДО операции:");
+ foreach (var it in demoItems)
+ report.Add($" [{it.Id}] {it.Name} — было {it.Stock} шт.");
+
+ await using var transaction = (SqlTransaction)await connection.BeginTransactionAsync();
+
+ try
+ {
+ // Шаг 1. Создаём заказ (он тоже будет откатан)
+ string insertOrder = @"
+ INSERT INTO Orders (CustomerId, OrderDate, Status, TotalAmount)
+ VALUES (@CustomerId, @OrderDate, @Status, @TotalAmount);";
+
+ decimal total = 0m;
+ foreach (var it in demoItems) total += it.Price * 2;
+
+ await using (var orderCommand = new SqlCommand(insertOrder, connection, transaction))
+ {
+ orderCommand.Parameters.Add("@CustomerId", SqlDbType.Int).Value = customerId;
+ orderCommand.Parameters.Add("@OrderDate", SqlDbType.DateTime2).Value = DateTime.Now;
+ orderCommand.Parameters.Add("@Status", SqlDbType.NVarChar, 50).Value = "Created";
+ orderCommand.Parameters.Add("@TotalAmount", SqlDbType.Decimal).Value = total;
+ await orderCommand.ExecuteNonQueryAsync();
+ }
+
+ // Шаг 2. Списываем первую позицию — это успешно
+ await DeductStockAsync(connection, transaction, demoItems[0].Id, 2);
+
+ report.Add($"Списание товара [{demoItems[0].Id}] {demoItems[0].Name} - 2 шт. прошло успешно.");
+ report.Add("Но тут приложение «падает» (искусственная ошибка).");
+ report.Add("Вызывается Rollback — все изменения выше отменяются:");
+
+ // Шаг 3. Искусственная ошибка внутри транзакции
+ throw new InvalidOperationException("Искусственная ошибка при обработке второй позиции заказа");
+ }
+ catch (Exception ex)
+ {
+ // Откат: и заказ, и списание первой позиции исчезают
+ await transaction.RollbackAsync();
+ report.Add($"Ошибка: {ex.Message}");
+ report.Add("Выполнен transaction.RollbackAsync().");
+ }
+
+ // Читаем остатки ПОСЛЕ отката — база вернулась в исходное состояние
+ string checkSql = @"
+ SELECT Id, Name, StockQuantity
+ FROM Products
+ WHERE Id = @Id1 OR Id = @Id2";
+
+ await using var checkCommand = new SqlCommand(checkSql, connection);
+ checkCommand.Parameters.Add("@Id1", SqlDbType.Int).Value = demoItems[0].Id;
+ checkCommand.Parameters.Add("@Id2", SqlDbType.Int).Value = demoItems[1].Id;
+
+ report.Add("Остатки ПОСЛЕ операции:");
+ await using (var checkReader = await checkCommand.ExecuteReaderAsync())
+ {
+ while (await checkReader.ReadAsync())
+ {
+ report.Add($" [{checkReader.GetInt32(0)}] {checkReader.GetString(1)} — стало {checkReader.GetInt32(2)} шт.");
+ }
+ }
+
+ report.Add("Транзакция откатилась: остатки не изменились, заказ в базе отсутствует.");
+ return string.Join(Environment.NewLine, report);
+ }
+
+ // Уменьшение остатка товара внутри транзакции
+ private static async Task DeductStockAsync(SqlConnection connection, SqlTransaction transaction, int productId, int quantity)
+ {
+ string updateSql = @"
+ UPDATE Products
+ SET StockQuantity = StockQuantity - @Quantity
+ WHERE Id = @ProductId AND StockQuantity >= @Quantity";
+
+ await using var command = new SqlCommand(updateSql, connection, transaction);
+ command.Parameters.Add("@ProductId", SqlDbType.Int).Value = productId;
+ command.Parameters.Add("@Quantity", SqlDbType.Int).Value = quantity;
+
+ int affected = await command.ExecuteNonQueryAsync();
+ if (affected == 0)
+ throw new InvalidOperationException($"Недостаточно товара на складе (Id = {productId})");
+ }
+
// Получение списка заказов (простая реализация)
public async Task> GetAllOrdersBasicAsync()
{
diff --git a/AdoNetShop/Program.cs b/AdoNetShop/Program.cs
index 7d2b76a..9b0445c 100644
--- a/AdoNetShop/Program.cs
+++ b/AdoNetShop/Program.cs
@@ -1,4 +1,4 @@
-using System.Data;
+using System.Data;
using AdoNetShop.Data;
using AdoNetShop.Models;
using AdoNetShop.Services;
@@ -30,7 +30,7 @@ class Program
bool exit = false;
while (!exit)
{
- Console.Clear();
+ ClearConsole();
Console.WriteLine("================================");
Console.WriteLine(" ADO.NET SHOP");
Console.WriteLine("================================");
@@ -45,11 +45,20 @@ class Program
Console.WriteLine("9. Просмотреть заказы");
Console.WriteLine("10. Демонстрация DataAdapter (Disconnected mode)");
Console.WriteLine("11. Демонстрация Stored Procedures");
+ Console.WriteLine("12. Демонстрация ROLLBACK транзакции");
Console.WriteLine("0. Выход");
Console.WriteLine("================================");
Console.Write("Выберите действие: ");
- var input = Console.ReadLine();
+var input = Console.ReadLine();
+
+ // Ввод закрыт (например, перенаправлен и закончился) — выходим из программы,
+ // иначе цикл меню будет крутиться бесконечно.
+ if (input is null)
+ {
+ exit = true;
+ continue;
+ }
try
{
@@ -88,6 +97,9 @@ class Program
case "11":
await DemoStoredProcedures(shopService);
break;
+ case "12":
+ await DemoRollback(shopService);
+ break;
case "0":
exit = true;
break;
@@ -110,11 +122,39 @@ class Program
if (!exit)
{
Console.WriteLine("\nНажмите любую клавишу для продолжения...");
- Console.ReadKey();
+ WaitForAnyKey();
}
}
}
+ // Очистка экрана. Console.Clear() не работает, если вывод перенаправлен
+ // (например, при запуске через管道 или в CI) — в этом случае просто пропускаем её.
+ private static void ClearConsole()
+ {
+ try
+ {
+ Console.Clear();
+ }
+ catch (IOException)
+ {
+ // Консоли нет — ничего не очищаем
+ }
+ }
+
+ // Console.ReadKey() тоже требует настоящую консоль.
+ // Если ввод перенаправлен, просто читаем строку ввода.
+ private static void WaitForAnyKey()
+ {
+ if (Console.IsInputRedirected)
+ {
+ Console.ReadLine();
+ }
+ else
+ {
+ Console.ReadKey();
+ }
+ }
+
private static async Task ShowAllProducts(ShopService service)
{
Console.WriteLine("\n--- Список товаров ---");
@@ -343,4 +383,18 @@ class Program
? $"Цена товара [{products[0].Id}] успешно изменена на {newPrice:C}"
: "Цена не была изменена (смотрите сообщение об ошибке выше).");
}
-}
+
+ // Наглядная демонстрация отката транзакции (Rollback).
+ // Первая позиция списывается успешно, затем происходит ошибка —
+ // и вся транзакция отменяется. Остатки в базе не меняются.
+ private static async Task DemoRollback(ShopService service)
+ {
+ Console.WriteLine("\n--- Демонстрация ROLLBACK транзакции ---");
+ Console.Write("Введите Id клиента (Enter = 1): ");
+ string input = Console.ReadLine() ?? "";
+ int customerId = int.TryParse(input, out int id) && id > 0 ? id : 1;
+
+ string report = await service.CreateOrderWithRollbackDemoAsync(customerId);
+ Console.WriteLine(report);
+ }
+}
\ No newline at end of file
diff --git a/AdoNetShop/Services/ShopService.cs b/AdoNetShop/Services/ShopService.cs
index b68da47..a123721 100644
--- a/AdoNetShop/Services/ShopService.cs
+++ b/AdoNetShop/Services/ShopService.cs
@@ -94,6 +94,10 @@ public class ShopService
}
public Task> GetAllOrdersAsync() => _orderRepository.GetAllOrdersBasicAsync();
+
+ // Демонстрация отката транзакции (пункт меню 12)
+ public Task CreateOrderWithRollbackDemoAsync(int customerId)
+ => _orderRepository.CreateOrderWithRollbackDemoAsync(customerId);
public Task GetProductsTableAsync() => _productRepository.GetProductsTableAsync();
}
diff --git a/README.md b/README.md
index cf0f0b5..627d8e1 100644
--- a/README.md
+++ b/README.md
@@ -584,9 +584,15 @@ docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=Your_password123!" -p 1433:1
| 5 | `05_procedures.sql` | Создаёт хранимые процедуры |
| 6 | `06_test.sql` | Проверочные запросы (для самопроверки, можно не выполнять) |
-Скрипты написаны «безопасно»: `01` использует `IF NOT EXISTS`, `02` создаёт
-таблицы только если их нет, `05` удаляет процедуру перед созданием. Поэтому
-скрипты можно запускать повторно — ошибки «уже существует» не будет.
+Скрипты написаны «безопасно» и их можно запускать повторно:
+
+* `01` создаёт базу только если её ещё нет;
+* `02` создаёт таблицы только если их нет;
+* `03` сначала удаляет прежние данные, а затем подставляет свои значения вместе с
+ `Id` (через `SET IDENTITY_INSERT`), поэтому результат всегда одинаковый:
+ товары `Id = 1…6`, клиенты `Id = 1…2`;
+* `05` удаляет процедуру перед созданием, поэтому повторный запуск не вызовет ошибку
+ «процедура уже существует».
### Способ 2: через командную строку (sqlcmd)
@@ -668,6 +674,26 @@ Server=(local)\SQLEXPRESS — эквивалент
Server=.\MSSQLSERVER — именованный экземпляр основного сервера
```
+### Если используется LocalDB (лёгкая версия SQL Server для разработки)
+
+LocalDB — это «облегчённый» SQL Server, который запускается одной командой и не требует
+установки службы. Он удобен тем, что не занимает ресурсы и не мешает другим программам.
+
+Запуск экземпляра (по умолчанию он называется `MSSQLLocalDB`):
+
+```bash
+sqllocaldb start MSSQLLocalDB
+```
+
+Строка подключения для него:
+
+```json
+"DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=AdoNetShop;Trusted_Connection=True;TrustServerCertificate=True;"
+```
+
+> В файле `appsettings.json` строка по умолчанию — `Server=localhost`.
+> Если у вас LocalDB, замените её на вариант выше.
+
### Если нужна аутентификация SQL Server (логин и пароль)
```json
@@ -740,15 +766,29 @@ dotnet run
4. Изменить товар
5. Удалить товар
6. Список категорий
-7. Список клиенты
+7. Список клиентов
8. Создать заказ (Демонстрация Transaction)
9. Просмотреть заказы
10. Демонстрация DataAdapter (Disconnected mode)
11. Демонстрация Stored Procedures
+12. Демонстрация ROLLBACK транзакции
0. Выход
================================
```
+> **Если .NET 8 не установлен.** Проект собирается под `net8.0`, но в `.csproj`
+> включён параметр `LatestMajor`. Это значит: если на
+> компьютере установлена только более новая версия .NET (9, 10, 11…),
+> приложение всё равно запустится на ней. Если же ошибка всё-таки появилась,
+> либо поставьте .NET 8 SDK, либо смените в `AdoNetShop.csproj` строку
+> `net8.0` на установленную у вас версию.
+
+> **Кодировка SQL-файлов.** Все скрипты в папке `database` сохранены в UTF-8 **с BOM**
+> (метка порядка байтов в начале файла). Благодаря этому кириллица корректно читается
+> и в SSMS, и в Visual Studio, и в командной строке (`sqlcmd -f 65001 -i файл.sql`).
+> Если вы пересохраните скрипт в другой кодировке — русские названия могут превратиться
+> в «кракозябры».
+
---
## 11. Что умеет программа (демонстрация преподавателю)
@@ -832,6 +872,24 @@ dotnet run
Из C# мы вызываем их по имени, а не пишем запрос текстом. Основная бизнес-логика
при этом остаётся в приложении — процедуры здесь только для демонстрации».
+### Пункт 12. Демонстрация ROLLBACK → наглядный откат транзакции ★
+
+Второй сценарий, который обязательно стоит показать преподавателю.
+Метод `OrderRepository.CreateOrderWithRollbackDemoAsync()` делает ровно то,
+что описано в учебнике про транзакции:
+
+1. Открывает транзакцию (`BeginTransactionAsync`).
+2. Создаёт заказ.
+3. **Успешно** списывает 2 штуки первого товара.
+4. Затем «падает» с ошибкой (искусственное исключение).
+5. Выполняет `RollbackAsync()`.
+6. Читает остатки заново и показывает их в консоли.
+
+**Что увидит преподаватель:** первое списание прошло успешно, но после отката
+остатки **такие же, как были до операции**, а заказ в базе отсутствует.
+Вот эту картинку и нужно доказать: «даже если часть операций внутри транзакции
+успела выполниться, откат возвращает базу в исходное состояние».
+
---
## 12. Обработка ошибок и валидация
@@ -914,9 +972,14 @@ catch (SqlException ex)
| `UpdatePriceViaStoredProcedureAsync(...)` | `StoredProcedure` + `UPDATE`, `SqlException` |
### 3. `AdoNetShop/Data/OrderRepository.cs` — транзакция
-Метод `CreateOrderWithTransactionAsync()`: `BeginTransactionAsync()` → `CommitAsync()`
-/ `RollbackAsync()`. Показывает, что все команды внутри транзакции получают
-третий параметр `transaction`.
+Два метода, которые обязательно нужно показать:
+
+* `CreateOrderWithTransactionAsync()` — «боевой» сценарий: `BeginTransactionAsync()`
+ → `CommitAsync()` / `RollbackAsync()`. Все команды внутри транзакции получают
+ третий параметр `transaction`.
+* `CreateOrderWithRollbackDemoAsync()` — наглядная демонстрация отката
+ для защиты: часть операций успевает выполниться, потом происходит ошибка,
+ и `RollbackAsync()` возвращает базу в исходное состояние.
### 4. `AdoNetShop/appsettings.json` — конфигурация
Строка подключения вынесена из кода. Покажите преподавателю, что в исходниках
@@ -944,6 +1007,9 @@ catch (SqlException ex)
| `The model ... context has been changed` | Изменили модель после миграции | Не применимо к этому проекту — здесь **нет** EF и миграций |
| Ошибка `PK_...` при `INSERT` | Попытка вставить дубликат | Например, два клиента с одинаковым Email (`UNIQUE`) |
| Не компилируется проект | Не установлен .NET 8 SDK | Установите SDK и выполните `dotnet restore` |
+| `You must install or update .NET to run this application` | На компьютере нет runtime .NET 8 | Установите .NET 8 SDK **или** проверьте, что в `.csproj` есть `LatestMajor` |
+| Вместо русских букв «кракозябры» | Скрипт сохранён не в UTF-8 | Не пересохраняйте `.sql` в ANSI-кодировке либо запускайте `sqlcmd -f 65001 -i файл.sql` |
+| Ошибка FOREIGN KEY при повторном запуске `03_insert_data.sql` | Ручная вставка данных без `IDENTITY_INSERT` | Запустите скрипт заново: он сам удаляет старые данные и подставляет свои Id |
---
@@ -963,6 +1029,8 @@ catch (SqlException ex)
| 10 | `SqlTransaction`, `Commit`, `Rollback` | `Data/OrderRepository.cs` |
| 11 | `CommandType.StoredProcedure` | `GetProductsByStoredProcedureAsync` |
| 12 | `SqlException` | `UpdatePriceViaStoredProcedureAsync` |
+| 13 | `Commit` / `Rollback` | `CreateOrderWithTransactionAsync` |
+| 14 | Наглядный откат (отмена выполненных операций) | `CreateOrderWithRollbackDemoAsync` |
---
diff --git a/database/01_create_database.sql b/database/01_create_database.sql
index 8b94a39..26c4cf5 100644
--- a/database/01_create_database.sql
+++ b/database/01_create_database.sql
@@ -1,4 +1,4 @@
--- 01_create_database.sql
+-- 01_create_database.sql
-- Скрипт для создания базы данных
USE master;
diff --git a/database/02_create_tables.sql b/database/02_create_tables.sql
index f5e7cbf..570ba79 100644
--- a/database/02_create_tables.sql
+++ b/database/02_create_tables.sql
@@ -1,4 +1,4 @@
--- 02_create_tables.sql
+-- 02_create_tables.sql
-- Скрипт для создания таблиц
USE [AdoNetShop];
diff --git a/database/03_insert_data.sql b/database/03_insert_data.sql
index 1886288..6a88c4e 100644
--- a/database/03_insert_data.sql
+++ b/database/03_insert_data.sql
@@ -1,42 +1,66 @@
--- 03_insert_data.sql
--- Скрипт для вставки тестовых данных
+-- 03_insert_data.sql
+-- Тестовые данные для интернет-магазина.
+--
+-- Скрипт можно запускать сколько угодно раз: данные каждый раз
+-- подставляются заново и получают одни и те же Id (1, 2, 3, ...).
+-- Для этого Id задаются явно (SET IDENTITY_INSERT), а в конце счётчики
+-- IDENTITY приводятся к правильному состоянию.
USE [AdoNetShop];
GO
--- Очистка таблиц перед вставкой (опционально, для чистоты эксперимента)
--- Отключаем проверку внешних ключей
-EXEC sp_MSForEachTable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'
-EXEC sp_MSForEachTable 'DELETE FROM ?'
-EXEC sp_MSForEachTable 'ALTER TABLE ? WITH CHECK CHECK CONSTRAINT ALL'
--- Сброс identity
-DBCC CHECKIDENT ('Categories', RESEED, 0);
-DBCC CHECKIDENT ('Products', RESEED, 0);
-DBCC CHECKIDENT ('Customers', RESEED, 0);
-DBCC CHECKIDENT ('Orders', RESEED, 0);
-DBCC CHECKIDENT ('OrderItems', RESEED, 0);
+-- Удаляем старые данные. Порядок важен: сначала удаляем таблицы,
+-- на которые ссылаются другие (иначе нарушится FOREIGN KEY).
+DELETE FROM [OrderItems];
+DELETE FROM [Orders];
+DELETE FROM [Products];
+DELETE FROM [Categories];
+DELETE FROM [Customers];
GO
-
--- Добавляем категории
-INSERT INTO Categories (Name) VALUES
-('Laptops'),
-('Smartphones'),
-('Accessories');
-
--- Добавляем товары
-INSERT INTO Products (Name, Description, Price, StockQuantity, CategoryId) VALUES
-('Laptop Dell XPS 15', 'High performance laptop', 1500.00, 10, 1),
-('MacBook Pro 14', 'Apple laptop with M2 chip', 1999.99, 5, 1),
-('iPhone 14 Pro', 'Latest Apple smartphone', 999.00, 20, 2),
-('Samsung Galaxy S23', 'Flagship Android phone', 899.50, 15, 2),
-('Wireless Mouse', 'Ergonomic wireless mouse', 25.00, 50, 3),
-('USB-C Hub', 'Multi-port adapter', 45.00, 30, 3);
-
--- Добавляем клиентов
-INSERT INTO Customers (Name, Email, Phone) VALUES
-('Ivan Ivanov', 'ivan@example.com', '+1234567890'),
-('Petr Petrov', 'petr@example.com', '+0987654321');
-
--- Заказы добавлять не будем, создадим их из кода, чтобы протестировать транзакции
+-- ============ Категории ============
+-- SET IDENTITY_INSERT ON позволяет указать значение колонки IDENTITY вручную.
+-- В каждый момент времени это разрешено только для одной таблицы,
+-- поэтому ON и OFF ставятся вокруг каждого INSERT.
+SET IDENTITY_INSERT [Categories] ON;
+INSERT INTO [Categories] (Id, Name) VALUES
+(1, N'Ноутбуки'),
+(2, N'Смартфоны'),
+(3, N'Аксессуары');
+SET IDENTITY_INSERT [Categories] OFF;
GO
+
+-- ============ Товары ============
+SET IDENTITY_INSERT [Products] ON;
+INSERT INTO [Products] (Id, Name, Description, Price, StockQuantity, CategoryId) VALUES
+(1, N'Ноутбук Dell XPS 15', N'Мощный ноутбук для работы и игр', 1500.00, 10, 1),
+(2, N'MacBook Pro 14', N'Ноутбук Apple на чипе M2 Pro', 1999.99, 5, 1),
+(3, N'iPhone 14 Pro', N'Флагманский смартфон Apple', 999.00, 20, 2),
+(4, N'Samsung Galaxy S23', N'Флагманский смартфон Samsung', 899.50, 15, 2),
+(5, N'Беспроводная мышь', N'Эргономичная беспроводная мышь', 25.00, 50, 3),
+(6, N'USB-C хаб', N'Многопортовый адаптер USB-C', 45.00, 30, 3);
+SET IDENTITY_INSERT [Products] OFF;
+GO
+
+-- ============ Клиенты ============
+SET IDENTITY_INSERT [Customers] ON;
+INSERT INTO [Customers] (Id, Name, Email, Phone) VALUES
+(1, N'Иван Иванов', N'ivan@example.com', N'+7 999 123-45-67'),
+(2, N'Пётр Петров', N'petr@example.com', N'+7 999 765-43-21');
+SET IDENTITY_INSERT [Customers] OFF;
+GO
+
+-- ============ Заказы ============
+-- Заказы намеренно НЕ создаются скриптом: их удобнее оформить
+-- из приложения (пункт меню 8), чтобы наглядно показать работу транзакции.
+GO
+
+-- Приводим счётчики IDENTITY к текущему максимуму, чтобы следующий
+-- товар/клиент/заказ, созданный из приложения, получил свободный Id.
+-- (Например, первым заказом из приложения будет Id = 1.)
+DBCC CHECKIDENT ('Categories', RESEED, 3) WITH NO_INFOMSGS;
+DBCC CHECKIDENT ('Products', RESEED, 6) WITH NO_INFOMSGS;
+DBCC CHECKIDENT ('Customers', RESEED, 2) WITH NO_INFOMSGS;
+DBCC CHECKIDENT ('Orders', RESEED, 0) WITH NO_INFOMSGS;
+DBCC CHECKIDENT ('OrderItems', RESEED, 0) WITH NO_INFOMSGS;
+GO
\ No newline at end of file
diff --git a/database/04_queries.sql b/database/04_queries.sql
index 87563f7..fedf2db 100644
--- a/database/04_queries.sql
+++ b/database/04_queries.sql
@@ -1,4 +1,4 @@
--- 04_queries.sql
+-- 04_queries.sql
-- Примеры запросов
USE [AdoNetShop];
diff --git a/database/05_procedures.sql b/database/05_procedures.sql
index 0daedd4..5c217eb 100644
--- a/database/05_procedures.sql
+++ b/database/05_procedures.sql
@@ -1,4 +1,4 @@
--- 05_procedures.sql
+-- 05_procedures.sql
-- Хранимые процедуры (Stored Procedures).
-- ВАЖНО: основная бизнес-логика остаётся в C# (ADO.NET).
-- Здесь только простые процедуры, чтобы показать, как ADO.NET
diff --git a/database/06_test.sql b/database/06_test.sql
index d97a7d2..594030a 100644
--- a/database/06_test.sql
+++ b/database/06_test.sql
@@ -1,5 +1,7 @@
--- 06_test.sql
--- Проверочные запросы. Выполняйте вручную после 01-05 скриптов.
+-- 06_test.sql
+-- Проверочные запросы: выполните вручную после скриптов 01-05.
+-- Скрипт ничего не ломает: он только читает данные и показывает,
+-- какие ошибки ожидаются при нарушении ограничений.
USE [AdoNetShop];
GO
@@ -11,21 +13,23 @@ SELECT * FROM Customers;
SELECT * FROM Orders;
SELECT * FROM OrderItems;
--- ============ Примеры SELECT-запросов из приложения ============
+-- ============ Примеры запросов, которые выполняет приложение ============
-- 1. Все товары (демонстрация SqlDataReader)
SELECT Id, Name, Description, Price, StockQuantity, CategoryId
FROM Products;
GO
--- 2. Один товар по Id (демонстрация параметризованного SqlCommand)
-DECLARE @Id INT = 1;
+-- 2. Один товар по Id (демонстрация параметризованного SqlCommand).
+-- Id берём из таблицы, чтобы скрипт работал после любого числа перезапусков.
+DECLARE @Id INT = (SELECT MIN(Id) FROM Products);
+
SELECT Id, Name, Description, Price, StockQuantity, CategoryId
FROM Products
WHERE Id = @Id;
GO
--- 3. Товары с названием категории (JOIN)
+-- 3. Товары вместе с названиями категорий (JOIN)
SELECT p.Id, p.Name, p.Price, p.StockQuantity, c.Name AS CategoryName
FROM Products p
JOIN Categories c ON p.CategoryId = c.Id
@@ -38,37 +42,41 @@ GO
EXEC dbo.GetProducts;
GO
--- Товары только 1-й категории
-EXEC dbo.GetProducts @CategoryId = 1;
+-- Товары только первой категории
+DECLARE @CatId INT = (SELECT MIN(Id) FROM Categories);
+EXEC dbo.GetProducts @CategoryId = @CatId;
GO
-- Один товар
-EXEC dbo.GetProductById @ProductId = 1;
+DECLARE @Id2 INT = (SELECT MIN(Id) FROM Products);
+EXEC dbo.GetProductById @ProductId = @Id2;
GO
-- Успешное изменение цены
-EXEC dbo.UpdateProductPrice @ProductId = 1, @NewPrice = 1450.00;
-SELECT Id, Name, Price FROM Products WHERE Id = 1;
+DECLARE @Id3 INT = (SELECT MIN(Id) FROM Products);
+EXEC dbo.UpdateProductPrice @ProductId = @Id3, @NewPrice = 1450.00;
+SELECT Id, Name, Price FROM Products WHERE Id = @Id3;
GO
--- Ожидаемая ошибка: цена <= 0 (процедура сама вызовет RAISERROR)
-EXEC dbo.UpdateProductPrice @ProductId = 1, @NewPrice = -10.00;
+-- Ожидается ошибка: цена должна быть > 0 (RAISERROR внутри процедуры).
+-- Снимите комментарий со следующей строки, чтобы увидеть ошибку:
+-- EXEC dbo.UpdateProductPrice @ProductId = 1, @NewPrice = -10.00;
GO
-- ============ Проверка ограничений (CHECK) ============
--- Ожидаемая ошибка: Price > 0
-INSERT INTO Products (Name, Price, StockQuantity, CategoryId)
-VALUES ('Тестовый товар', -5, 1, 1);
+-- Ожидается ошибка CHECK: Price > 0
+-- INSERT INTO Products (Name, Price, StockQuantity, CategoryId)
+-- VALUES (N'Тестовый товар', -5, 1, 1);
GO
--- Ожидаемая ошибка: StockQuantity >= 0
-UPDATE Products SET StockQuantity = -1 WHERE Id = 1;
+-- Ожидается ошибка CHECK: StockQuantity >= 0
+-- UPDATE Products SET StockQuantity = -1 WHERE Id = 1;
GO
-- ============ Проверка целостности связей (FOREIGN KEY) ============
--- Ожидаемая ошибка: категории 999 не существует
-INSERT INTO Products (Name, Price, StockQuantity, CategoryId)
-VALUES ('Товар без категории', 100, 1, 999);
+-- Ожидается ошибка FOREIGN KEY: категории 999 не существует
+-- INSERT INTO Products (Name, Price, StockQuantity, CategoryId)
+-- VALUES (N'Товар без категории', 100, 1, 999);
GO
\ No newline at end of file