ADO.NET и Dapper — первая программа
Play ITЗагрузка интерактивного демо…
ADO.NET и Dapper — первая программа
Где применяют прямой SQL
В EF Core вы описываете классы, а фреймворк строит SQL. Иногда нужен полный контроль над текстом запроса — сложные отчёты, оконные функции SQL, массовая загрузка, legacy-база со странной схемой, микросервис "только чтение" с жёсткими требованиями к производительности.
Для этого в .NET есть два уровня:
| Уровень | Что это |
|---|---|
| ADO.NET | Низкоуровневый API: открыть соединение, выполнить команду, прочитать строки по одной. |
| Dapper | Тонкая обёртка над ADO.NET: вы по-прежнему пишете SQL, но результат сразу маппится в объекты C#. |
Обзор всех способов: Работа с базами данных и ORM в C#. Высокоуровневый путь: EF Core — первая программа. Историческая модель COM-ADO: ADO.NET (платформа .NET). Полный API LINQ для SQL-пайплайнов и коллекций: Справочник по LINQ.
Если вам важны точный SQL, прозрачные планы запросов и минимальный "магический" слой ORM, начинайте с этого подхода. Для доменной логики с большим количеством сущностей чаще удобнее EF Core.
Словарь
| Термин | Простыми словами |
|---|---|
| Соединение (connection) | Канал к файлу БД или серверу SQL; открывается перед запросом, закрывается после. |
| Команда (command) | Текст SQL + параметры; выполняется через ExecuteNonQuery (без строк) или ExecuteReader (с выборкой). |
| Параметр | Значение @title, @year — подставляется драйвером безопасно, отдельно от текста SQL. |
| DataReader | Поток строк результата; читается в цикле ReadAsync(). |
| Строка подключения | Строка вида Data Source=books_ado.db — адрес и опции БД. |
| Маппинг | Превращение колонок SQL в свойства класса Book. |
Что получится
| Этап | Технология | Действие |
|---|---|---|
| 1 | ADO.NET + Microsoft.Data.Sqlite | Создать таблицу, INSERT с параметрами, SELECT в цикле |
| 2 | Dapper | Те же операции короче: ExecuteAsync, QueryAsync<Book> |
Требования
- .NET SDK 8+
- Базовый SQL (что такое
SELECT,INSERT,WHERE): Основы БД
Проект
dotnet new console -n BooksAdo -o BooksAdo
cd BooksAdo
dotnet add package Microsoft.Data.Sqlite
dotnet add package Dapper
Microsoft.Data.Sqlite — современный драйвер SQLite от Microsoft (для SQL Server используют Microsoft.Data.SqlClient — другой пакет, другая строка подключения).
Модель
Models/Book.cs:
namespace BooksAdo.Models;
public class Book
{
public int Id { get; set; }
public string Title { get; set; } = "";
public int Year { get; set; }
}
Тот же класс, что в Entity Framework Core — первая программа: три поля соответствуют трём колонкам таблицы. Dapper сопоставит их по имени (регистр не важен: Title ↔ title).
ADO.NET — создать таблицу и записать данные
Program.cs (фрагмент):
Код ITЗагрузка примера кода…
Построчный разбор
| Строка | Смысл |
|---|---|
const string cs = "Data Source=books_ado.db" | Строка подключения: файл БД в текущей папке. |
new SqliteConnection(cs) | Объект соединения (ещё закрыт). |
await conn.OpenAsync() | Открыть канал к файлу; без этого команды не выполнятся. |
""" ... """ | Raw string literal в C# — многострочный SQL без экранирования кавычек. |
CREATE TABLE IF NOT EXISTS | Создать таблицу, если её ещё нет (повторный запуск безопасен). |
AUTOINCREMENT | SQLite сам увеличивает Id для новых строк. |
ExecuteNonQueryAsync() | Выполнить SQL без возврата таблицы (CREATE, INSERT, UPDATE, DELETE). |
@title, @year в SQL | Именованные параметры — плейсхолдеры для значений. |
Parameters.AddWithValue("@title", "Clean Code") | Подставить значение в параметр; драйвер экранирует спецсимволы. |
Почему параметры обязательны
Если склеивать SQL из строк пользователя ($"INSERT ... '{userInput}'"), злоумышленник может вставить свой фрагмент SQL (SQL-инъекция). С @параметрами текст запроса и данные разделены: СУБД получает готовый шаблон и значения отдельно.
await using
Автоматически вызывает Dispose у соединения и команд — освобождает файл и сокеты. В ASP.NET соединение держат коротко: открыли → один-два запроса → закрыли; пул соединений на сервере переиспользует физические подключения.
ADO.NET — чтение DbDataReader
Код ITЗагрузка примера кода…
| Шаг | Объяснение |
|---|---|
ExecuteReaderAsync() | Запрос с возвратом набора строк (курсор). |
ReadAsync() | Перейти к следующей строке; false — строк больше нет. |
GetInt32(0), GetString(1) | Взять значение колонки по индексу (0 — первая колонка в SELECT). |
Индексы хрупки: если в SELECT поменять порядок колонок, код сломается. В продакшене чаще:
var titleOrdinal = reader.GetOrdinal("Title");
var title = reader.GetString(titleOrdinal);
Dapper — тот же сценарий короче
После создания таблицы (как выше):
await using var conn = new SqliteConnection(cs);
await conn.OpenAsync();
await conn.ExecuteAsync(
"INSERT INTO Books (Title, Year) VALUES (@Title, @Year);",
new { Title = "CLR via C#", Year = 2012 });
var books = await conn.QueryAsync<Book>(
"SELECT Id, Title, Year FROM Books WHERE Year >= @minYear ORDER BY Title;",
new { minYear = 2010 });
foreach (var b in books)
Console.WriteLine($"{b.Id}: {b.Title}");
| Вызов | Что делает |
|---|---|
ExecuteAsync(sql, obj) | Выполняет команду без чтения строк; свойства анонимного объекта new { Title = ..., Year = ... } становятся параметрами @Title, @Year. |
QueryAsync<Book>(sql, obj) | Выполняет SELECT и для каждой строки создаёт Book, заполняя свойства по именам колонок. |
Имена в SQL (@minYear) и в объекте (minYear) должны совпадать (без @ в свойстве). Если колонка называется book_title, в SQL можно alias: SELECT book_title AS Title.
Dapper не отслеживает изменения: чтобы обновить книгу, пишете отдельный UPDATE (или переходите на EF Core).
Транзакция (два INSERT "всё или ничего")
Если второй INSERT упадёт, первый откатится:
Код ITЗагрузка примера кода…
Транзакция — группа команд с общим исходом: либо все применились, либо ни одна.
Когда что выбирать
| Критерий | ADO.NET | Dapper | EF Core |
|---|---|---|---|
| Скорость старта CRUD | Средняя | Быстрая | Быстрая |
| Контроль SQL | Полный | Полный | Частичный (LINQ → SQL) |
| Миграции схемы | Вручную / скрипты | Вручную | Встроены |
| Отслеживание изменений | Нет | Нет | Да |
| Типичное применение | ETL, отчёты, тонкая настройка | High-load read API | Доменные приложения, CRUD |
Частые ошибки
| Ошибка | Решение |
|---|---|
| Склейка пользовательского ввода в SQL | Только @параметры |
| Соединение открыто часами | Короткие using; в вебе — на время запроса |
Путаница SqlClient и Sqlite | SQLite → Microsoft.Data.Sqlite; SQL Server → Microsoft.Data.SqlClient |
Dapper: поле null / не маппится | Имя колонки = имя свойства или AS Alias в SQL |
| "database is locked" | Закрыть reader и connection; в тестах — не держать два writer’а на один файл |
Что попробовать
- Добавьте операции
UPDATEиDELETEс параметрами.
UPDATE Books SET Year = @year WHERE Id = @id
- Прочитайте список через ADO.NET, тот же список — через Dapper; сравните строки кода.
- Перепишите сценарий на EF Core и сравните, где появляются миграции и
SaveChanges.
Мини-чеклист качества SQL-кода
- Все переменные пользователя передаются через параметры.
- У запросов есть явная сортировка, если порядок важен.
- Для массовых операций есть транзакция и контроль ошибок.
- В логах фиксируется текст операции без секретных данных.
Дальше
В подборках
Статья входит в тематические подборки и блок "С чего начать?" на главной. Соседние шаги того же маршрута:
Первые шаги (маршрут подборки) — EF Core — первая программа, Первая программа на ASP.NET Core, Первая программа на JavaFX, Blazor — первая программа, Первая программа на Spring Framework, .NET MAUI — первая программа.