Перейти к основному содержимому

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.

Когда выбирать ADO.NET и Dapper

Если вам важны точный SQL, прозрачные планы запросов и минимальный "магический" слой ORM, начинайте с этого подхода. Для доменной логики с большим количеством сущностей чаще удобнее EF Core.


Словарь

ТерминПростыми словами
Соединение (connection)Канал к файлу БД или серверу SQL; открывается перед запросом, закрывается после.
Команда (command)Текст SQL + параметры; выполняется через ExecuteNonQuery (без строк) или ExecuteReader (с выборкой).
ПараметрЗначение @title, @year — подставляется драйвером безопасно, отдельно от текста SQL.
DataReaderПоток строк результата; читается в цикле ReadAsync().
Строка подключенияСтрока вида Data Source=books_ado.db — адрес и опции БД.
МаппингПревращение колонок SQL в свойства класса Book.

Что получится

ЭтапТехнологияДействие
1ADO.NET + Microsoft.Data.SqliteСоздать таблицу, INSERT с параметрами, SELECT в цикле
2DapperТе же операции короче: 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 сопоставит их по имени (регистр не важен: Titletitle).


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Создать таблицу, если её ещё нет (повторный запуск безопасен).
AUTOINCREMENTSQLite сам увеличивает 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.NETDapperEF Core
Скорость старта CRUDСредняяБыстраяБыстрая
Контроль SQLПолныйПолныйЧастичный (LINQ → SQL)
Миграции схемыВручную / скриптыВручнуюВстроены
Отслеживание измененийНетНетДа
Типичное применениеETL, отчёты, тонкая настройкаHigh-load read APIДоменные приложения, CRUD

Частые ошибки

ОшибкаРешение
Склейка пользовательского ввода в SQLТолько @параметры
Соединение открыто часамиКороткие using; в вебе — на время запроса
Путаница SqlClient и SqliteSQLite → Microsoft.Data.Sqlite; SQL Server → Microsoft.Data.SqlClient
Dapper: поле null / не маппитсяИмя колонки = имя свойства или AS Alias в SQL
"database is locked"Закрыть reader и connection; в тестах — не держать два writer’а на один файл

Что попробовать

  1. Добавьте операции UPDATE и DELETE с параметрами.
UPDATE Books SET Year = @year WHERE Id = @id
  1. Прочитайте список через ADO.NET, тот же список — через Dapper; сравните строки кода.
  2. Перепишите сценарий на EF Core и сравните, где появляются миграции и SaveChanges.

Мини-чеклист качества SQL-кода

  • Все переменные пользователя передаются через параметры.
  • У запросов есть явная сортировка, если порядок важен.
  • Для массовых операций есть транзакция и контроль ошибок.
  • В логах фиксируется текст операции без секретных данных.

Дальше


В подборках

Статья входит в тематические подборки и блок "С чего начать?" на главной. Соседние шаги того же маршрута:

Первые шаги (маршрут подборки) — EF Core — первая программа, Первая программа на ASP.NET Core, Первая программа на JavaFX, Blazor — первая программа, Первая программа на Spring Framework, .NET MAUI — первая программа.