970 lines
54 KiB
Markdown
970 lines
54 KiB
Markdown
# AdoNetShop — лабораторная работа по технологии ADO.NET
|
||
|
||
**Что это за проект.** Это небольшой учебный интернет-магазин, написанный на C# (.NET 8).
|
||
Всё общение с базой данных Microsoft SQL Server выполняется **вручную через ADO.NET**
|
||
(пакет `Microsoft.Data.SqlClient`). В проекте **нет** Entity Framework, Dapper, NHibernate
|
||
и любых других ORM — нам важно увидеть, как приложение общается с БД «на низком уровне».
|
||
|
||
---
|
||
|
||
## Содержание
|
||
|
||
1. [Что такое ADO.NET и зачем он нужен](#1-что-такое-adonet-и-зачем-он-нужен)
|
||
2. [Что такое Provider (провайдер данных)](#2-что-такое-provider-провайдер-данных)
|
||
3. [Основные классы ADO.NET, которые используются в проекте](#3-основные-классы-adonet-используемые-в-проекте)
|
||
- 3.1 `SqlConnection` — подключение к базе
|
||
- 3.2 `SqlCommand` — команда (запрос)
|
||
- 3.3 `SqlParameter` — параметры и защита от SQL Injection
|
||
- 3.4 `SqlDataReader` — чтение результатов (connected)
|
||
- 3.5 `SqlDataAdapter` и `DataTable` — disconnected
|
||
- 3.6 `SqlTransaction` — транзакции
|
||
4. [Connected vs Disconnected: в чём разница](#4-connected-vs-disconnected-в-чём-разница)
|
||
5. [Структура проекта](#5-структура-проекта)
|
||
6. [Структура базы данных](#6-структура-базы-данных)
|
||
7. [Как установить SQL Server](#7-как-установить-sql-server)
|
||
8. [Как создать базу данных и выполнить SQL-скрипты](#8-как-создать-базу-данных-и-выполнить-sql-скрипты)
|
||
9. [Как изменить строку подключения (Connection String)](#9-как-изменить-строку-подключения-connection-string)
|
||
10. [Как запустить проект](#10-как-запустить-проект)
|
||
11. [Что умеет программа (демонстрация преподавателю)](#11-что-умеет-программа-демонстрация-преподавателю)
|
||
12. [Обработка ошибок и валидация](#12-обработка-ошибок-и-валидация)
|
||
13. [Какие файлы нужно изучить в первую очередь](#13-какие-файлы-нужно-изучить-в-первую-очередь)
|
||
14. [Частые проблемы и их решения](#14-частые-проблемы-и-их-решения)
|
||
|
||
---
|
||
|
||
## 1. Что такое ADO.NET и зачем он нужен
|
||
|
||
**ADO.NET** (ActiveX Data Objects .NET) — это набор классов, входящих в состав .NET,
|
||
который позволяет приложению на C# общаться с источниками данных: реляционными базами
|
||
данных, XML-файлами и т.д.
|
||
|
||
Проще всего представить ADO.NET так:
|
||
|
||
```
|
||
┌──────────────────────┐ ADO.NET ┌──────────────────────┐
|
||
│ Приложение на C# │ ◄──────────────────► │ SQL Server │
|
||
│ (ваш код) │ команды и результаты │ (данные) │
|
||
└──────────────────────┘ └──────────────────────┘
|
||
```
|
||
|
||
Приложение не «знает» SQL Server напрямую. Между ними стоит слой ADO.NET, который
|
||
переводит вызовы C# (открыть соединение, выполнить запрос, прочитать результат)
|
||
в сетевые сообщения протокола SQL Server.
|
||
|
||
**Зачем это нужно:**
|
||
|
||
1. **Прямой контроль над SQL.** Вы сами пишете запрос — значит, вы можете использовать
|
||
всё, что умеет конкретная СУБД: индексы, хинты, `JOIN`, оконные функции, `OUTPUT`,
|
||
временные таблицы. Никакой ORM не даст вам такой свободы без «хаков».
|
||
2. **Производительность.** ORM делает много «умной» работы (отслеживание изменений,
|
||
ленивая загрузка, кэширование), которая часто не нужна. Прямой ADO.NET — это
|
||
минимальные накладные расходы. Для учебных и для production-задач вродя отчётов
|
||
ADO.NET часто быстрее.
|
||
3. **Понимание того, как всё работает на самом деле.** Даже если потом вы будете писать
|
||
на Entity Framework, понимание ADO.NET помогает понять, что ORM делает под капотом.
|
||
4. **Не зависимость от лишних зависимостей.** ADO.NET — часть платформы .NET,
|
||
нужен только один NuGet-пакет провайдера.
|
||
|
||
---
|
||
|
||
## 2. Что такое Provider (провайдер данных)
|
||
|
||
ADO.NET — это набор **интерфейсов и абстракций**, но он не умеет сам ходить в базу.
|
||
Для каждой СУБД существует конкретная реализация — **провайдер данных** (Data Provider).
|
||
|
||
| СУБД | Провайдер (пакет NuGet) |
|
||
|---|---|
|
||
| Microsoft SQL Server | `Microsoft.Data.SqlClient` |
|
||
| PostgreSQL | `Npgsql` |
|
||
| MySQL / MariaDB | `MySqlConnector` |
|
||
| Oracle | `Oracle.ManagedDataAccess` |
|
||
| SQLite | `Microsoft.Data.Sqlite` |
|
||
|
||
В нашем проекте используется **`Microsoft.Data.SqlClient`** — официальный провайдер
|
||
для SQL Server. Именно он предоставляет классы `SqlConnection`, `SqlCommand`,
|
||
`SqlDataReader`, `SqlDataAdapter`, `SqlTransaction`.
|
||
|
||
> **Зачем провайдер вынесен в отдельный пакет, а не встроен в .NET?**
|
||
> Разработчики .NET не могут включить в платформу драйверы ко всем существующим СУБД —
|
||
> это слишком много кода и лицензионные ограничения. Поэтому каждый провайдер —
|
||
> отдельный NuGet-пакет, который подключается одной строкой в `.csproj`:
|
||
|
||
```xml
|
||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.0" />
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Основные классы ADO.NET, используемые в проекте
|
||
|
||
### 3.1 `SqlConnection` — подключение к базе
|
||
|
||
`SqlConnection` — это объект, представляющий **физическое соединение** с SQL Server.
|
||
|
||
Создаётся из **строки подключения** (connection string), которая содержит всю
|
||
необходимую информацию: адрес сервера, имя базы, способ аутентификации.
|
||
|
||
```csharp
|
||
// Файл: AdoNetShop/Data/DbConnectionFactory.cs
|
||
await using var connection = new SqlConnection(connectionString);
|
||
|
||
// Открываем физическое подключение к SQL Server
|
||
await connection.OpenAsync();
|
||
```
|
||
|
||
Три важных правила:
|
||
|
||
1. **Создать** — `new SqlConnection(connectionString)`. Сам по себе объект ещё не
|
||
подключён, это просто «заготовка».
|
||
2. **Открыть** — `Open()` / `OpenAsync()`. Только после этого можно выполнять команды.
|
||
Строка подключения читается именно в этот момент (ленивое подключение).
|
||
3. **Закрыть/освободить** — `Close()` / `Dispose()`. Подключения — ограниченный ресурс
|
||
(пул ограничен по числу соединений), поэтому их обязательно нужно закрывать.
|
||
|
||
В проекте мы **нигде не вызываем `Close()` вручную**, а всегда используем `await using`:
|
||
|
||
```csharp
|
||
await using var connection = _connectionFactory.CreateConnection();
|
||
await connection.OpenAsync();
|
||
// ... работа с базой ...
|
||
// при выходе из блока соединение автоматически закроется и освободится,
|
||
// даже если внутри произошло исключение
|
||
```
|
||
|
||
`using` / `await using` — это конструкция языка C#, которая гарантирует вызов
|
||
`Dispose()`. Для `SqlConnection`, `SqlCommand`, `SqlDataReader` это обязательно,
|
||
потому что они используют неуправляемые ресурсы.
|
||
|
||
---
|
||
|
||
### 3.2 `SqlCommand` — команда (запрос)
|
||
|
||
`SqlCommand` — это объект, который содержит **что именно** мы хотим сделать с базой:
|
||
SQL-запрос, имя хранимой процедуры или текст команды.
|
||
|
||
```csharp
|
||
await using var command = new SqlCommand(
|
||
"SELECT Id, Name, Price FROM Products WHERE CategoryId = @CategoryId",
|
||
connection);
|
||
```
|
||
|
||
Три способа выполнить команду:
|
||
|
||
| Метод | Что возвращает | Когда использовать |
|
||
|---|---|---|
|
||
| `ExecuteReaderAsync()` | `SqlDataReader` | `SELECT` — когда нужно прочитать много строк |
|
||
| `ExecuteScalarAsync()` | одно значение (`object`) | `SELECT COUNT(*)`, `SELECT SCOPE_IDENTITY()` |
|
||
| `ExecuteNonQueryAsync()` | число затронутых строк (`int`) | `INSERT`, `UPDATE`, `DELETE` |
|
||
|
||
Примеры из проекта (`AdoNetShop/Data/ProductRepository.cs`):
|
||
|
||
```csharp
|
||
// ExecuteScalarAsync — получить Id только что вставленного товара
|
||
// (SCOPE_IDENTITY() возвращает Id, который сгенерировала база для новой строки)
|
||
string query = @"
|
||
INSERT INTO Products (Name, Description, Price, StockQuantity, CategoryId)
|
||
VALUES (@Name, @Description, @Price, @StockQuantity, @CategoryId);
|
||
SELECT SCOPE_IDENTITY();";
|
||
var newId = await command.ExecuteScalarAsync();
|
||
return Convert.ToInt32(newId);
|
||
|
||
// ExecuteNonQueryAsync — обновить товар, вернётся количество затронутых строк
|
||
int rowsAffected = await command.ExecuteNonQueryAsync();
|
||
return rowsAffected > 0;
|
||
```
|
||
|
||
---
|
||
|
||
### 3.3 `SqlParameter` — параметры и защита от SQL Injection
|
||
|
||
**Это самая важная тема для защиты лабораторной.**
|
||
|
||
Правило №1 при работе с любой БД: **никогда не собирайте SQL-запрос склейкой строк.**
|
||
|
||
❌ **НЕЛЬЗЯ** так — это SQL Injection:
|
||
|
||
```csharp
|
||
// ОПАСНО! Не делайте так никогда.
|
||
string query = "SELECT * FROM Products WHERE Name = '" + name + "'";
|
||
```
|
||
|
||
Почему это опасно: если пользователь введёт в поле имя товара
|
||
|
||
```
|
||
' OR '1'='1
|
||
```
|
||
|
||
то запрос превратится в `SELECT * FROM Products WHERE Name = '' OR '1'='1'` —
|
||
вернётся вся таблица. Это и есть **SQL Injection**: злоумышленник может подставить
|
||
`'; DROP TABLE Products; --` и удалить таблицу.
|
||
|
||
✅ **ПРАВИЛЬНО** так — использовать параметры:
|
||
|
||
```csharp
|
||
// Безопасно. Значение не «вклеивается» в текст запроса,
|
||
// база получает его как отдельный типизированный параметр.
|
||
command.Parameters.Add("@Id", SqlDbType.Int).Value = id;
|
||
```
|
||
|
||
Что здесь важно:
|
||
|
||
* `@Id` — имя параметра в SQL-тексте, оно **не является** значением.
|
||
* `SqlDbType.Int` — явно указываем SQL-тип. Это правильнее, чем
|
||
`AddWithValue`, потому что `AddWithValue` передаёт .NET-тип, а SQL Server может
|
||
неправильно его интерпретировать (например, объект станет `nvarchar`, и запрос
|
||
по числовому полю будет работать медленнее или вообще иначе).
|
||
* `SqlClient` отправляет значение и тип **отдельно от текста запроса**, поэтому
|
||
вставить произвольный SQL через значение параметра невозможно.
|
||
|
||
Также обратите внимание, что текст SQL в проекте всегда пишется константой
|
||
(через `@"..."` — многострочная строка), а пользовательские данные подставляются
|
||
**только** через `Parameters`. Поиск по коду не должен находить конкатенацию
|
||
пользовательских данных с SQL.
|
||
|
||
---
|
||
|
||
### 3.4 `SqlDataReader` — чтение результатов запроса
|
||
|
||
`SqlDataReader` — «читатель» результата `SELECT`. Он работает в режиме
|
||
**forward-only** (только вперёд) и **read-only** (только чтение).
|
||
|
||
Что это значит на практике:
|
||
|
||
* Данные приходят **по одной строке**, а не сразу все. Это экономит память —
|
||
можно обработать миллион строк, не загружая их в память целиком.
|
||
* Нельзя вернуться на предыдущую строку (нельзя «отмотать назад»).
|
||
* Пока `reader` открыт, **нельзя** выполнять другие команды на том же подключении.
|
||
|
||
Пример из проекта:
|
||
|
||
```csharp
|
||
// Выполняем SELECT и получаем потоковое чтение результата
|
||
await using var reader = await command.ExecuteReaderAsync();
|
||
|
||
// пока reader.ReadAsync() возвращает true — читаем очередную строку
|
||
while (await reader.ReadAsync())
|
||
{
|
||
int id = reader.GetInt32(reader.GetOrdinal("Id"));
|
||
string name = reader.GetString(reader.GetOrdinal("Name"));
|
||
|
||
// Если в поле может быть NULL, проверяем это отдельно
|
||
string? description = reader.IsDBNull(reader.GetOrdinal("Description"))
|
||
? null
|
||
: reader.GetString(reader.GetOrdinal("Description"));
|
||
|
||
products.Add(new Product { Id = id, Name = name, Description = description });
|
||
}
|
||
```
|
||
|
||
Методы `GetInt32`, `GetString`, `GetDecimal` читают значение нужного типа.
|
||
`GetOrdinal("Name")` возвращает номер колонки по её названию — читать по номеру
|
||
(`reader.GetString(0)`) тоже можно, но по названию понятнее и безопаснее
|
||
(если порядок колонок в запросе изменится, код продолжит работать).
|
||
|
||
---
|
||
|
||
### 3.5 `SqlDataAdapter` и `DataTable` — disconnected (автономный) режим
|
||
|
||
Мы уже посмотрели на **connected** подход: подключение открыто, `SqlDataReader`
|
||
читает строки по одной. Он быстрый, но подключение занято всё время чтения.
|
||
|
||
**Disconnected (автономный) подход** работает иначе:
|
||
|
||
1. `SqlDataAdapter` получает команду с запросом.
|
||
2. Сам открывает подключение (если оно закрыто), выполняет `SELECT`
|
||
и **забирает весь результат в память**.
|
||
3. Заполняет `DataTable` (или `DataSet`) — таблицу, которая живёт в памяти приложения.
|
||
4. **Сам закрывает подключение** (если открывал его).
|
||
|
||
После этого вы работаете с `DataTable` как с обычной таблицей в памяти,
|
||
а база данных **полностью свободна** и не ждёт вас.
|
||
|
||
Пример из проекта (`AdoNetShop/Data/ProductRepository.cs`):
|
||
|
||
```csharp
|
||
public async Task<DataTable> GetProductsTableAsync()
|
||
{
|
||
await using var connection = _connectionFactory.CreateConnection();
|
||
|
||
string query = "SELECT Id, Name, Price, StockQuantity FROM Products";
|
||
await using var command = new SqlCommand(query, connection);
|
||
|
||
// SqlDataAdapter — «мост» между источником данных и DataTable
|
||
using var adapter = new SqlDataAdapter(command);
|
||
var table = new DataTable("Products");
|
||
|
||
// Fill() сам откроет подключение, выполнит запрос,
|
||
// заполнит DataTable и закроет подключение.
|
||
// Fill() синхронный, поэтому оборачиваем его в Task.Run.
|
||
await Task.Run(() => adapter.Fill(table));
|
||
|
||
return table;
|
||
}
|
||
```
|
||
|
||
А дальше в `Program.cs` мы работаем с таблицей **без базы данных**:
|
||
|
||
```csharp
|
||
DataTable table = await service.GetProductsTableAsync();
|
||
|
||
foreach (DataRow row in table.Rows) // никакого подключения к БД уже нет
|
||
{
|
||
int id = (int)row["Id"];
|
||
string name = (string)row["Name"];
|
||
decimal price = (decimal)row["Price"];
|
||
Console.WriteLine($"{id}\t{name}\t{price:C}");
|
||
}
|
||
```
|
||
|
||
> **Когда что использовать?**
|
||
> * `SqlDataReader` — когда данных много (тысячи строк) и нужна экономия памяти
|
||
> и максимальная скорость.
|
||
> * `SqlDataAdapter` + `DataTable` — когда данных немного и с ними нужно удобно
|
||
> работать: сортировать, фильтровать, показывать в отчёте, передавать дальше.
|
||
|
||
---
|
||
|
||
### 3.6 `SqlTransaction` — транзакция
|
||
|
||
**Транзакция** — это группа SQL-операций, которые выполняются как одно целое:
|
||
**всё или ничего**.
|
||
|
||
Классический пример из нашего проекта — **создание заказа**. Это несколько операций:
|
||
|
||
```
|
||
1. Создать заказ в таблице Orders
|
||
2. Для каждого товара заказа:
|
||
2.1 Уменьшить остаток в таблице Products
|
||
2.2 Добавить позицию в таблицу OrderItems
|
||
3. Зафиксировать (COMMIT)
|
||
```
|
||
|
||
Если на шаге 2.1 не хватает товара, то нельзя оставлять базу в таком состоянии:
|
||
заказ создан, а половина позиций — нет. Нужно отменить **всё**. Именно это
|
||
и обеспечивает транзакция.
|
||
|
||
Реализация в проекте (`AdoNetShop/Data/OrderRepository.cs`):
|
||
|
||
```csharp
|
||
await using var connection = _connectionFactory.CreateConnection();
|
||
await connection.OpenAsync();
|
||
|
||
// 1) Начинаем транзакцию
|
||
await using var transaction = (SqlTransaction)await connection.BeginTransactionAsync();
|
||
|
||
try
|
||
{
|
||
// 2) Создаём заказ
|
||
await using var insertOrder = new SqlCommand(insertOrderQuery, connection, transaction);
|
||
insertOrder.Parameters.Add("@CustomerId", SqlDbType.Int).Value = order.CustomerId;
|
||
int newOrderId = Convert.ToInt32(await insertOrder.ExecuteScalarAsync());
|
||
|
||
foreach (var item in order.Items)
|
||
{
|
||
// 3) Уменьшаем остаток. В условии — проверка, что товара хватает:
|
||
// WHERE Id = @ProductId AND StockQuantity >= @Quantity
|
||
await using var updateStock = new SqlCommand(updateStockQuery, connection, transaction);
|
||
updateStock.Parameters.Add("@ProductId", SqlDbType.Int).Value = item.ProductId;
|
||
updateStock.Parameters.Add("@Quantity", SqlDbType.Int).Value = item.Quantity;
|
||
|
||
int rowsAffected = await updateStock.ExecuteNonQueryAsync();
|
||
if (rowsAffected == 0)
|
||
{
|
||
// Товара не хватает (или его нет). Бросаем исключение —
|
||
// код перейдёт в catch и сделает откат всей транзакции.
|
||
throw new Exception($"Недостаточно товара на складе для ProductId: {item.ProductId}");
|
||
}
|
||
|
||
// 4) Добавляем позицию заказа
|
||
await using var insertItem = new SqlCommand(insertItemQuery, connection, transaction);
|
||
// ... параметры ...
|
||
await insertItem.ExecuteNonQueryAsync();
|
||
}
|
||
|
||
// 5) Всё прошло успешно — фиксируем результат
|
||
await transaction.CommitAsync();
|
||
return newOrderId;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 6) Любая ошибка — откатываем все изменения
|
||
await transaction.RollbackAsync();
|
||
throw new Exception($"Ошибка при создании заказа. Транзакция отменена. Детали: {ex.Message}", ex);
|
||
}
|
||
```
|
||
|
||
Обратите внимание на важную деталь: **каждая команда внутри транзакции получает
|
||
третий параметр — `transaction`**. Если забыть его передать, SQL Server выдаст ошибку
|
||
«Invalid operation. The transaction associated with this command is not the connection's
|
||
active transaction».
|
||
|
||
---
|
||
|
||
## 4. Connected vs Disconnected: в чём разница
|
||
|
||
| | Connected (подключенный) | Disconnected (автономный) |
|
||
|---|---|---|
|
||
| Что используем | `SqlConnection` + `SqlCommand` + `SqlDataReader` | `SqlDataAdapter` + `DataTable` / `DataSet` |
|
||
| Когда открыто подключение | Всё время, пока читаем данные | Только на момент загрузки данных |
|
||
| Как читаем | По одной строке (`ReadAsync()`) | Сразу таблица целиком |
|
||
| Расход памяти | Минимальный (строка за строкой) | Вся выборка в памяти |
|
||
| Скорость на больших объёмах | Выше | Ниже (из-за накладных расходов на загрузку) |
|
||
| Можно работать без БД после загрузки | Нет | Да |
|
||
| Где в проекте | `GetAllAsync()`, `GetByIdAsync()`, `CreateOrderWithTransactionAsync()` | `GetProductsTableAsync()` (пункт меню 10) |
|
||
|
||
Простое правило: **данных много → `SqlDataReader`; данных мало и нужно удобно
|
||
работать в памяти → `SqlDataAdapter` + `DataTable`.**
|
||
|
||
---
|
||
|
||
## 5. Структура проекта
|
||
|
||
```
|
||
Artem/
|
||
│
|
||
├── README.md ← этот файл (теория + инструкции)
|
||
├── .gitignore
|
||
├── AdoNetShop.slnx ← файл решения Visual Studio / Rider
|
||
│
|
||
├── AdoNetShop/ ← сам проект C#
|
||
│ ├── AdoNetShop.csproj ← файл проекта, здесь подключается Microsoft.Data.SqlClient
|
||
│ ├── Program.cs ← консольное меню и сценарии демонстрации
|
||
│ ├── appsettings.json ← строка подключения к БД (НЕ в коде!)
|
||
│ │
|
||
│ ├── Models/ ← простые классы предметной области (POCO)
|
||
│ │ ├── Product.cs ← Id, Name, Description, Price, StockQuantity, CategoryId
|
||
│ │ ├── Category.cs ← Id, Name
|
||
│ │ ├── Customer.cs ← Id, Name, Email, Phone
|
||
│ │ ├── Order.cs ← Id, CustomerId, OrderDate, Status, TotalAmount
|
||
│ │ └── OrderItem.cs ← Id, OrderId, ProductId, Quantity, Price
|
||
│ │
|
||
│ ├── Data/ ← ВЕСЬ РАБОТА С БАЗОЙ ЗДЕСЬ (ADO.NET)
|
||
│ │ ├── DbConnectionFactory.cs ← создание SqlConnection из строки подключения
|
||
│ │ ├── ProductRepository.cs ← CRUD товаров + SqlDataReader + SqlDataAdapter + Stored Procedure
|
||
│ │ ├── CategoryRepository.cs ← чтение категорий
|
||
│ │ ├── CustomerRepository.cs ← чтение клиентов
|
||
│ │ └── OrderRepository.cs ← создание заказа в транзакции (Commit / Rollback)
|
||
│ │
|
||
│ └── Services/
|
||
│ └── ShopService.cs ← небольшая прослойка: проверки и «прокси» к репозиториям
|
||
│
|
||
└── database/ ← SQL-скрипты (выполняются по порядку)
|
||
├── 01_create_database.sql ← создание базы данных AdoNetShop
|
||
├── 02_create_tables.sql ← таблицы, ключи, ограничения CHECK, индексы
|
||
├── 03_insert_data.sql ← тестовые данные
|
||
├── 04_queries.sql ← примеры запросов
|
||
├── 05_procedures.sql ← хранимые процедуры
|
||
└── 06_test.sql ← проверочные запросы (выполнять вручную)
|
||
```
|
||
|
||
**Почему такая структура?** Это классическое разделение ответственности:
|
||
|
||
* **Models** — «что есть в магазине». Никакой логики, только данные.
|
||
* **Data (Repository)** — «как получить и сохранить данные». Здесь живёт
|
||
весь ADO.NET: `SqlConnection`, `SqlCommand`, `SqlDataReader`, транзакции.
|
||
* **Services** — «что можно сделать с магазином». Здесь простые проверки
|
||
данных перед сохранением. Бизнес-логика намеренно не усложнена.
|
||
* **Program** — «как это показываем пользователю». Консольное меню.
|
||
|
||
Repository (репозиторий) — просто класс, в котором собраны SQL-запросы к одной
|
||
таблице. Это удобно: когда понадобится поменять SQL, вы открываете один файл.
|
||
|
||
---
|
||
|
||
## 6. Структура базы данных
|
||
|
||
```
|
||
Categories (категории)
|
||
Id INT IDENTITY PRIMARY KEY
|
||
Name NVARCHAR(100) NOT NULL
|
||
▲
|
||
│ FK Products.CategoryId
|
||
│
|
||
Products (товары) ─┐
|
||
Id INT IDENTITY PRIMARY KEY│ 1 товар ──► много позиций заказа
|
||
Name NVARCHAR(200) NOT NULL │
|
||
Description NVARCHAR(MAX) NULL │
|
||
Price DECIMAL(18,2) NOT NULL CHECK (Price > 0)
|
||
StockQuantity INT NOT NULL CHECK (StockQuantity >= 0)
|
||
CategoryId INT NOT NULL │
|
||
▲ │
|
||
│ │ FK OrderItems.ProductId
|
||
│ │
|
||
Customers (клиенты) │
|
||
Id INT IDENTITY PRIMARY KEY │
|
||
Name NVARCHAR(100) NOT NULL │
|
||
Email NVARCHAR(100) NOT NULL UNIQUE │
|
||
Phone NVARCHAR(20) NULL │
|
||
▲ │
|
||
│ FK Orders.CustomerId │
|
||
│ │
|
||
Orders (заказы) ─────────────────────┘
|
||
Id INT IDENTITY PRIMARY KEY
|
||
CustomerId INT NOT NULL
|
||
OrderDate DATETIME2 NOT NULL DEFAULT GETDATE()
|
||
Status NVARCHAR(50) NOT NULL DEFAULT 'New'
|
||
TotalAmount DECIMAL(18,2) NOT NULL CHECK (TotalAmount >= 0)
|
||
|
||
OrderItems (позиции заказа)
|
||
Id INT IDENTITY PRIMARY KEY
|
||
OrderId INT NOT NULL FK Orders(Id) ON DELETE CASCADE
|
||
ProductId INT NOT NULL FK Products(Id)
|
||
Quantity INT NOT NULL CHECK (Quantity > 0)
|
||
Price DECIMAL(18,2) NOT NULL CHECK (Price > 0)
|
||
```
|
||
|
||
Что здесь важно и почему:
|
||
|
||
* `IDENTITY` — автоинкремент: база сама генерирует `Id`, приложение его не придумывает.
|
||
* `PRIMARY KEY` — гарантирует уникальность строк.
|
||
* `FOREIGN KEY` — гарантирует, что нельзя создать заказ для несуществующего клиента
|
||
или позицию для несуществующего товара. База защищает целостность данных.
|
||
* `CHECK` — ограничение на значения: цена не может быть отрицательной,
|
||
количество в заказе — нулевым или отрицательным.
|
||
* `UNIQUE` на `Customers.Email` — нельзя завести двух клиентов с одним email.
|
||
* `INDEX` на внешних ключах (`IX_Products_CategoryId`, `IX_Orders_CustomerId`,
|
||
`IX_OrderItems_OrderId`, `IX_OrderItems_ProductId`) — ускоряют `JOIN` и поиск.
|
||
* `DECIMAL(18,2)` для денег — **не** `FLOAT` и **не** `DOUBLE`: это единственный
|
||
тип, который хранит денежные значения без ошибок округления.
|
||
* `NVARCHAR`, а не `VARCHAR` — `NVARCHAR` поддерживает кириллицу и любые языки.
|
||
|
||
---
|
||
|
||
## 7. Как установить SQL Server
|
||
|
||
Есть три бесплатных варианта — выбирайте любой:
|
||
|
||
### Вариант А: SQL Server Express (самый простой для учёбы)
|
||
1. Скачайте **SQL Server Express** с официального сайта Microsoft:
|
||
https://www.microsoft.com/ru-ru/products/sql-server/sql-server-express-downloads
|
||
2. При установке отметьте **Basic** (базовый) тип установки.
|
||
3. В мастере установки обязательно включите **средства управления** —
|
||
**SQL Server Management Tools (SSMS)**. Это программа, в которой вы будете
|
||
выполнять SQL-скрипты.
|
||
4. После установки запустите **SQL Server Configuration Manager** и проверьте,
|
||
что служба **SQL Server (SQLEXPRESS)** запущена (состояние «Running»).
|
||
5. Откройте SSMS и подключитесь к серверу. Сервер для Express по умолчанию
|
||
называется **`.\SQLEXPRESS`**.
|
||
|
||
### Вариант Б: Developer Edition (полная версия, бесплатно для разработки)
|
||
Скачивается на той же странице Microsoft. После установки сервер по умолчанию
|
||
называется **`localhost`** (или имя вашего компьютера).
|
||
|
||
### Вариант В: Docker (если установлен Docker)
|
||
```bash
|
||
docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=Your_password123!" -p 1433:1433 -d mcr.microsoft.com/mssql/server:2022-latest
|
||
```
|
||
|
||
> **Важно:** SQL Server — это отдельная программа-сервер. Пока он не установлен
|
||
> и не запущен, приложение не сможет подключиться, даже если код написан правильно.
|
||
|
||
---
|
||
|
||
## 8. Как создать базу данных и выполнить SQL-скрипты
|
||
|
||
### Способ 1: через SSMS (рекомендуется для учебной работы)
|
||
|
||
1. Запустите **SQL Server Management Studio**.
|
||
2. В поле «Тип сервера» выберите **Database Engine**.
|
||
3. В поле «Имя сервера» введите `localhost` (или `.\SQLEXPRESS` для Express,
|
||
или `localhost\SQLEXPRESS` — этот вариант подходит всегда).
|
||
4. Аутентификация: **Windows Authentication** — вводить логин и пароль не нужно.
|
||
5. Нажмите **Connect**. Если увидели окно с деревом серверов — вы подключились.
|
||
|
||
Дальше откройте каждый файл из папки `database` через **File → Open → File**
|
||
и нажмите **Execute** (или клавишу `F5`). **Порядок важен**, выполняйте строго
|
||
по номерам:
|
||
|
||
| Шаг | Файл | Что делает |
|
||
|---|---|---|
|
||
| 1 | `01_create_database.sql` | Создаёт базу данных `AdoNetShop` |
|
||
| 2 | `02_create_tables.sql` | Создаёт таблицы, ключи, ограничения, индексы |
|
||
| 3 | `03_insert_data.sql` | Заполняет таблицы тестовыми данными |
|
||
| 4 | `04_queries.sql` | Примеры запросов (для понимания, можно не выполнять) |
|
||
| 5 | `05_procedures.sql` | Создаёт хранимые процедуры |
|
||
| 6 | `06_test.sql` | Проверочные запросы (для самопроверки, можно не выполнять) |
|
||
|
||
Скрипты написаны «безопасно»: `01` использует `IF NOT EXISTS`, `02` создаёт
|
||
таблицы только если их нет, `05` удаляет процедуру перед созданием. Поэтому
|
||
скрипты можно запускать повторно — ошибки «уже существует» не будет.
|
||
|
||
### Способ 2: через командную строку (sqlcmd)
|
||
|
||
```bash
|
||
# Способ с Windows-аутентификацией
|
||
sqlcmd -S localhost -E -i "database\01_create_database.sql"
|
||
sqlcmd -S localhost -E -d AdoNetShop -i "database\02_create_tables.sql"
|
||
sqlcmd -S localhost -E -d AdoNetShop -i "database\03_insert_data.sql"
|
||
|
||
# Для SQL Server Express
|
||
sqlcmd -S .\SQLEXPRESS -E -i "database\01_create_database.sql"
|
||
```
|
||
|
||
### Проверка, что всё создалось
|
||
|
||
В SSMS подключитесь к базе `AdoNetShop` (в списке слева) и выполните:
|
||
|
||
```sql
|
||
SELECT * FROM Categories;
|
||
SELECT * FROM Products;
|
||
SELECT * FROM Customers;
|
||
```
|
||
|
||
Вы должны увидеть: 3 категории, 6 товаров, 2 клиентов.
|
||
|
||
---
|
||
|
||
## 9. Как изменить строку подключения (Connection String)
|
||
|
||
Строка подключения **не зашита в код**. Она лежит в файле
|
||
`AdoNetShop/appsettings.json`:
|
||
|
||
```json
|
||
{
|
||
"ConnectionStrings": {
|
||
"DefaultConnection": "Server=localhost;Database=AdoNetShop;Trusted_Connection=True;TrustServerCertificate=True;"
|
||
}
|
||
}
|
||
```
|
||
|
||
Читает её класс `DbConnectionFactory` (`AdoNetShop/Data/DbConnectionFactory.cs`):
|
||
|
||
```csharp
|
||
var configuration = new ConfigurationBuilder()
|
||
.SetBasePath(Directory.GetCurrentDirectory())
|
||
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
||
.Build();
|
||
|
||
_connectionString = configuration.GetConnectionString("DefaultConnection");
|
||
```
|
||
|
||
### Что означают параметры строки подключения
|
||
|
||
| Параметр | Значение в примере | Что это |
|
||
|---|---|---|
|
||
| `Server` | `localhost` | Адрес сервера SQL Server |
|
||
| `Database` | `AdoNetShop` | Имя базы данных |
|
||
| `Trusted_Connection` | `True` | Использовать Windows-аутентификацию (логин/пароль не нужны) |
|
||
| `TrustServerCertificate` | `True` | Доверять самоподписанному SSL-сертификату SQL Server (нужно для свежих версий `Microsoft.Data.SqlClient`) |
|
||
|
||
### Если SQL Server работает как **именованный экземпляр**
|
||
|
||
Так бывает после установки SQL Server Express — сервер называется `SQLEXPRESS`.
|
||
В строке подключения именованный экземпляр пишется через обратный слэш `\`:
|
||
|
||
```json
|
||
"DefaultConnection": "Server=.\\SQLEXPRESS;Database=AdoNetShop;Trusted_Connection=True;TrustServerCertificate=True;"
|
||
```
|
||
|
||
> **Про обратный слэш и JSON.** В JSON символ `\` — начало escape-последовательности,
|
||
> поэтому обратный слэш записывается **двойным**: `\\`.
|
||
> В строке подключения это означает один-единственный обратный слэш.
|
||
|
||
Варианты, которые тоже работают:
|
||
|
||
```
|
||
Server=localhost\SQLEXPRESS — если хотите быть явными
|
||
Server=(local)\SQLEXPRESS — эквивалент
|
||
Server=.\MSSQLSERVER — именованный экземпляр основного сервера
|
||
```
|
||
|
||
### Если нужна аутентификация SQL Server (логин и пароль)
|
||
|
||
```json
|
||
"DefaultConnection": "Server=localhost;Database=AdoNetShop;User Id=sa;Password=ТвойПароль;TrustServerCertificate=True;"
|
||
```
|
||
|
||
> Внимание: пароль в `appsettings.json` — это «учебный» вариант. В настоящем проекте
|
||
> такие данные хранят в переменных окружения или в защищённом хранилище секретов.
|
||
> Для лабораторной работы достаточно `appsettings.json` — главное, что пароль
|
||
> **не зашит в исходный код**, а вынесен в конфигурацию.
|
||
|
||
### Как проверить, что подключение работает
|
||
|
||
```bash
|
||
# В SSMS: Server → Connect → Database Engine, имя сервера как в appsettings.json
|
||
# Либо в командной строке проекта (Visual Studio / Rider → Terminal):
|
||
cd AdoNetShop
|
||
dotnet run
|
||
```
|
||
|
||
Если в меню выбрать пункт `1. Список товаров` и вы увидите таблицу с товарами —
|
||
подключение работает корректно.
|
||
|
||
---
|
||
|
||
## 10. Как запустить проект
|
||
|
||
### Что нужно
|
||
* [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) — проверьте командой `dotnet --version`
|
||
* Запущенный SQL Server (см. раздел 7)
|
||
* Выполненные скрипты `01`, `02`, `03`, `05` (см. раздел 8)
|
||
* Правильная строка подключения в `appsettings.json` (см. раздел 9)
|
||
|
||
### Запуск из командной строки
|
||
|
||
```bash
|
||
cd AdoNetShop
|
||
|
||
# Восстановить пакеты (обычно делается автоматически)
|
||
dotnet restore
|
||
|
||
# Собрать проект
|
||
dotnet build
|
||
|
||
# Запустить
|
||
dotnet run
|
||
```
|
||
|
||
### Запуск из Visual Studio
|
||
|
||
1. Откройте файл решения `AdoNetShop.slnx` (или `AdoNetShop.csproj`).
|
||
2. Убедитесь, что в качестве стартового проекта выбран `AdoNetShop`.
|
||
3. Нажмите `Ctrl + F5` (запуск без отладки) или `F5` (с отладкой).
|
||
4. Для отладки поставьте точки останова в `ProductRepository.GetAllAsync` и
|
||
`OrderRepository.CreateOrderWithTransactionAsync` — очень полезно для защиты.
|
||
|
||
### Запуск из Rider
|
||
|
||
Откройте папку проекта в Rider — он сам определит `.csproj`. Нажмите `Shift + F10`.
|
||
|
||
### Что должно появиться
|
||
|
||
```
|
||
================================
|
||
ADO.NET SHOP
|
||
================================
|
||
1. Список товаров
|
||
2. Найти товар по ID
|
||
3. Добавить товар
|
||
4. Изменить товар
|
||
5. Удалить товар
|
||
6. Список категорий
|
||
7. Список клиенты
|
||
8. Создать заказ (Демонстрация Transaction)
|
||
9. Просмотреть заказы
|
||
10. Демонстрация DataAdapter (Disconnected mode)
|
||
11. Демонстрация Stored Procedures
|
||
0. Выход
|
||
================================
|
||
```
|
||
|
||
---
|
||
|
||
## 11. Что умеет программа (демонстрация преподавателю)
|
||
|
||
Каждый пункт меню — это отдельная демонстрация элемента ADO.NET.
|
||
|
||
### Пункт 1. Список товаров → `SqlDataReader` + `ExecuteReaderAsync`
|
||
|
||
Вызывает `ProductRepository.GetAllAsync()`. Показывает потоковое чтение
|
||
результата `SELECT` через `SqlDataReader`.
|
||
|
||
**Что говорить:** «Открывается соединение, выполняется SELECT, и результат
|
||
читается построчно объектом `SqlDataReader`, пока `ReadAsync()` возвращает `true`».
|
||
|
||
### Пункт 2. Найти товар по ID → параметризованный `SqlCommand`
|
||
|
||
Вызывает `GetByIdAsync(id)`. Демонстрирует `WHERE Id = @Id` + `SqlParameter`.
|
||
|
||
**Что говорить:** «Пользовательский ввод никогда не подставляется в текст запроса
|
||
через конкатенацию — только через параметры. Это защита от SQL Injection».
|
||
|
||
### Пункт 3. Добавить товар → `ExecuteScalarAsync`
|
||
|
||
Вызывает `CreateAsync(product)`. Вставляет строку и получает сгенерированный
|
||
базой `Id` через `SELECT SCOPE_IDENTITY()`.
|
||
|
||
**Что говорить:** «`ExecuteScalarAsync` возвращает одно значение — в нашем случае
|
||
это `Id` нового товара, который сгенерировал `IDENTITY`».
|
||
|
||
### Пункт 4. Изменить товар → `ExecuteNonQueryAsync`
|
||
|
||
Вызывает `UpdateAsync(product)`. Возвращает количество затронутых строк.
|
||
|
||
### Пункт 5. Удалить товар → `ExecuteNonQueryAsync`
|
||
|
||
Вызывает `DeleteAsync(id)`. Обратите внимание: если на товар ссылаются строки
|
||
в `OrderItems`, база не даст удалить такой товар (Foreign Key). Это тоже можно
|
||
показать как защиту целостности данных.
|
||
|
||
### Пункт 6. Категории / Пункт 7. Клиенты → простые `SELECT`
|
||
|
||
Чтение справочных таблиц, которые нужны, чтобы заполнить выпадающие списки
|
||
при добавлении товара и оформлении заказа.
|
||
|
||
### Пункт 8. Создать заказ → `SqlTransaction`, `Commit`, `Rollback` ★
|
||
|
||
**Главная демонстрация лабораторной.** Создаёт заказ целиком в одной транзакции.
|
||
|
||
**Как проверить откат:**
|
||
1. Выберите клиента (например, `1`).
|
||
2. Добавьте товар `1` в количестве `2` — заказ создастся успешно.
|
||
3. Добавьте товар, которого на складе мало, в количестве `9999`.
|
||
4. Программа выведет ошибку «Недостаточно товара на складе».
|
||
5. Откройте SSMS и выполните `SELECT * FROM Orders;` — нового заказа не будет.
|
||
Транзакция откатила **всё**: и заказ, и позиции, и изменения остатков.
|
||
|
||
**Что говорить:** «Транзакция гарантирует принцип "все или ничего". Нельзя получить
|
||
ситуацию, когда заказ создан, а товары к нему — нет».
|
||
|
||
### Пункт 9. Просмотреть заказы → чтение результата
|
||
|
||
Показывает созданные заказы: клиент, сумма, дата, статус.
|
||
|
||
### Пункт 10. DataAdapter demo → `SqlDataAdapter` + `DataTable` (disconnected)
|
||
|
||
Вызывает `GetProductsTableAsync()`. Показывает, что данные загрузились
|
||
в таблицу в памяти и с ними можно работать **без подключения к базе**.
|
||
|
||
**Что говорить:** «`SqlDataAdapter` сам открыл подключение, скачал данные в
|
||
`DataTable` и закрыл подключение. Дальше мы работаем с обычной таблицей в памяти —
|
||
база данных в этот момент свободна для других пользователей».
|
||
|
||
### Пункт 11. Stored Procedures demo → `CommandType.StoredProcedure`
|
||
|
||
Вызывает хранимые процедуры `dbo.GetProducts` и `dbo.UpdateProductPrice`.
|
||
|
||
**Как проверить обработку ошибки:** введите цену `-5` — процедура вызовет
|
||
`RAISERROR`, приложение перехватит `SqlException` и покажет сообщение.
|
||
|
||
**Что говорить:** «Хранимые процедуры — это SQL-код, который хранится на сервере.
|
||
Из C# мы вызываем их по имени, а не пишем запрос текстом. Основная бизнес-логика
|
||
при этом остаётся в приложении — процедуры здесь только для демонстрации».
|
||
|
||
---
|
||
|
||
## 12. Обработка ошибок и валидация
|
||
|
||
### Проверка пользовательского ввода (в `Program.cs` и `ShopService.cs`)
|
||
|
||
| Что проверяем | Где |
|
||
|---|---|
|
||
| ID — целое число | `int.TryParse(...)` перед вызовом репозитория |
|
||
| Цена больше нуля | `ShopService.CreateProductAsync`, `Product.Price` |
|
||
| Количество на складе не отрицательное | `ShopService.CreateProductAsync`, `StockQuantity` |
|
||
| Email не пустой | при добавлении клиента |
|
||
| Количество товара в заказе больше нуля | при оформлении заказа |
|
||
| Товар существует и его хватает | `ShopService.CreateOrderAsync` до открытия транзакции |
|
||
|
||
Пример:
|
||
|
||
```csharp
|
||
if (product.Price <= 0)
|
||
throw new ArgumentException("Price must be greater than 0");
|
||
if (product.StockQuantity < 0)
|
||
throw new ArgumentException("Stock cannot be negative");
|
||
```
|
||
|
||
### Обработка ошибок в приложении
|
||
|
||
В главном цикле `Program.cs` все пункты меню выполняются внутри `try/catch`:
|
||
|
||
```csharp
|
||
try
|
||
{
|
||
switch (input) { /* ... */ }
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[ОШИБКА]: {ex.Message}");
|
||
if (ex.InnerException != null)
|
||
Console.WriteLine($"[ДЕТАЛИ]: {ex.InnerException.Message}");
|
||
}
|
||
```
|
||
|
||
Такой подход выбран сознательно: **для учебного проекта исключения не скрываются
|
||
молча** — пользователь видит, что произошло. В продакшене ошибки логировали бы
|
||
в файл, а пользователю показывали бы нейтральное сообщение.
|
||
|
||
Дополнительно ошибки от самой СУБД обрабатываются отдельно. `SqlException` —
|
||
это исключение SQL Server, в нём есть номер ошибки и текст от сервера:
|
||
|
||
```csharp
|
||
catch (SqlException ex)
|
||
{
|
||
Console.WriteLine($"Ошибка SQL Server (код {ex.Number}): {ex.Message}");
|
||
return false;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 13. Какие файлы нужно изучить в первую очередь
|
||
|
||
Если преподаватель спросит «покажи, где у тебя ADO.NET», открывайте эти файлы
|
||
в таком порядке:
|
||
|
||
### 1. `AdoNetShop/Data/DbConnectionFactory.cs` — подключение
|
||
Класс, который создаёт `SqlConnection`. Здесь видно, что строка подключения
|
||
берётся из `appsettings.json`.
|
||
|
||
### 2. `AdoNetShop/Data/ProductRepository.cs` — основные элементы ADO.NET
|
||
**Самый важный файл лабораторной.** В нём собрано всё сразу:
|
||
|
||
| Метод | Что показывает |
|
||
|---|---|
|
||
| `GetAllAsync()` | `SqlConnection.OpenAsync()`, `SqlCommand`, `ExecuteReaderAsync()`, `SqlDataReader` |
|
||
| `GetByIdAsync(id)` | `SqlParameter`, параметризованный запрос |
|
||
| `CreateAsync(product)` | `ExecuteScalarAsync()`, `SCOPE_IDENTITY()` |
|
||
| `UpdateAsync(product)` | `ExecuteNonQueryAsync()` для `UPDATE` |
|
||
| `DeleteAsync(id)` | `ExecuteNonQueryAsync()` для `DELETE` |
|
||
| `GetProductsTableAsync()` | `SqlDataAdapter` + `DataTable` (disconnected) |
|
||
| `GetProductsByStoredProcedureAsync(...)` | `CommandType.StoredProcedure` |
|
||
| `UpdatePriceViaStoredProcedureAsync(...)` | `StoredProcedure` + `UPDATE`, `SqlException` |
|
||
|
||
### 3. `AdoNetShop/Data/OrderRepository.cs` — транзакция
|
||
Метод `CreateOrderWithTransactionAsync()`: `BeginTransactionAsync()` → `CommitAsync()`
|
||
/ `RollbackAsync()`. Показывает, что все команды внутри транзакции получают
|
||
третий параметр `transaction`.
|
||
|
||
### 4. `AdoNetShop/appsettings.json` — конфигурация
|
||
Строка подключения вынесена из кода. Покажите преподавателю, что в исходниках
|
||
нет ни логинов, ни паролей.
|
||
|
||
### 5. `database/02_create_tables.sql` — схема БД
|
||
`PRIMARY KEY`, `FOREIGN KEY`, `CHECK`, индексы. Свяжите таблицы с классами
|
||
из папки `Models/`.
|
||
|
||
### 6. `database/05_procedures.sql` — хранимые процедуры
|
||
Три процедуры, которые вызываются из `ProductRepository`.
|
||
|
||
---
|
||
|
||
## 14. Частые проблемы и их решения
|
||
|
||
| Симптом | Причина | Что сделать |
|
||
|---|---|---|
|
||
| `A network-related or instance-specific error occurred` | SQL Server не запущен или неверный адрес сервера | Запустите службу SQL Server. Проверьте `Server` в `appsettings.json`. Для Express должно быть `.\SQLEXPRESS` |
|
||
| `Login failed for user` | Неверный режим аутентификации | Попробуйте `Trusted_Connection=True` (Windows Auth) вместо `User Id=sa;Password=...` |
|
||
| `Certificate is not trusted` | Не настроен SSL-сертификат SQL Server | Добавьте `TrustServerCertificate=True` в строку подключения |
|
||
| `Cannot open database "AdoNetShop"` | База не создана | Выполните `01_create_database.sql` в SSMS |
|
||
| `Invalid object name 'Products'` | Таблицы не созданы | Выполните `02_create_tables.sql` |
|
||
| `Could not find file appsettings.json` | Запустили из другой папки | Запускайте команду из папки `AdoNetShop`, где лежит `.csproj` |
|
||
| `The model ... context has been changed` | Изменили модель после миграции | Не применимо к этому проекту — здесь **нет** EF и миграций |
|
||
| Ошибка `PK_...` при `INSERT` | Попытка вставить дубликат | Например, два клиента с одинаковым Email (`UNIQUE`) |
|
||
| Не компилируется проект | Не установлен .NET 8 SDK | Установите SDK и выполните `dotnet restore` |
|
||
|
||
---
|
||
|
||
## Краткая памятка: 12 элементов ADO.NET, которые демонстрирует проект
|
||
|
||
| # | Элемент ADO.NET | Где найти |
|
||
|---|---|---|
|
||
| 1 | `SqlConnection` (создание) | `Data/DbConnectionFactory.cs` |
|
||
| 2 | `OpenAsync()` / `Close()` / `using` | все репозитории |
|
||
| 3 | `SqlCommand` | `Data/ProductRepository.cs` |
|
||
| 4 | `ExecuteReaderAsync()` | `GetAllAsync` |
|
||
| 5 | `ExecuteScalarAsync()` | `CreateAsync` |
|
||
| 6 | `ExecuteNonQueryAsync()` | `UpdateAsync`, `DeleteAsync` |
|
||
| 7 | `SqlParameter` | `GetByIdAsync`, `CreateAsync` |
|
||
| 8 | `SqlDataReader` | `GetAllAsync` |
|
||
| 9 | `SqlDataAdapter` + `DataTable` | `GetProductsTableAsync` |
|
||
| 10 | `SqlTransaction`, `Commit`, `Rollback` | `Data/OrderRepository.cs` |
|
||
| 11 | `CommandType.StoredProcedure` | `GetProductsByStoredProcedureAsync` |
|
||
| 12 | `SqlException` | `UpdatePriceViaStoredProcedureAsync` |
|
||
|
||
---
|
||
|
||
*Проект создан в учебных целях для лабораторной работы по технологии ADO.NET.
|
||
ADO.NET используется как основной и единственный способ работы с базой данных.* |