Dapper для C# (.NET): полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в Dapper: что это и зачем нужно

Dapper — это легковесная библиотека (Micro ORM) для платформы .NET, разработанная командой Stack Overflow. Она представляет собой расширение для IDbConnection и предназначена для выполнения SQL-запросов к базам данных с минимальными накладными расходами. В отличие от тяжеловесных ORM (например, Entity Framework Core), Dapper не отслеживает изменения объектов, не генерирует сложные SQL-запросы и не требует настройки контекста данных. Его главная цель — максимальная производительность и простота.

Основные преимущества Dapper:

  • Скорость: Dapper работает практически со скоростью сырых ADO.NET команд, так как использует динамическую генерацию IL-кода для маппинга результатов.
  • Простота: Минимум кода для выполнения запросов. Не нужно создавать сложные конфигурации.
  • Контроль над SQL: Вы пишете SQL-запросы вручную, что даёт полный контроль над тем, какие данные и как извлекаются.
  • Гибкость: Работает с любыми ADO.NET провайдерами (SQL Server, PostgreSQL, MySQL, SQLite и др.).
  • Расширяемость: Легко интегрируется с существующими проектами и другими ORM.

Dapper идеально подходит для проектов, где важна производительность и прямой контроль над SQL, а также для микросервисов и высоконагруженных приложений.

Установка Dapper

Dapper распространяется через NuGet — менеджер пакетов для .NET. Установить его можно несколькими способами:

Через консоль диспетчера пакетов (Visual Studio):

Install-Package Dapper

Через .NET CLI (рекомендуемый способ):

dotnet add package Dapper

Через интерфейс NuGet в Visual Studio:

  1. Правый клик на проекте → "Управление пакетами NuGet".
  2. Перейти на вкладку "Обзор".
  3. Найти "Dapper" и нажать "Установить".

После установки в проекте появится ссылка на сборку Dapper.dll. Никаких дополнительных зависимостей не требуется — библиотека работает поверх стандартного System.Data.

Быстрый старт с примером кода

Рассмотрим минимальный пример работы с Dapper. Предположим, у нас есть таблица Users в базе данных SQL Server:

CREATE TABLE Users (
    Id INT PRIMARY KEY IDENTITY,
    Name NVARCHAR(100) NOT NULL,
    Age INT NOT NULL
);

Код на C# для получения всех пользователей:

using System.Data.SqlClient;
using Dapper;

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

class Program
{
    static void Main()
    {
        string connectionString = "Server=localhost;Database=TestDB;Trusted_Connection=True;";
        using (var connection = new SqlConnection(connectionString))
        {
            connection.Open();
            
            // Простой SELECT запрос
            var users = connection.Query<User>("SELECT * FROM Users");
            
            foreach (var user in users)
            {
                Console.WriteLine($"{user.Id}: {user.Name}, {user.Age} лет");
            }
        }
    }
}

Как видите, Dapper автоматически маппит результаты SQL-запроса на объекты C#. Метод Query<T> — это основной метод для выполнения SELECT запросов.

Основные методы/функции Dapper

Dapper предоставляет набор методов-расширений для интерфейса IDbConnection. Рассмотрим 10 ключевых методов.

1. Query<T>

Что делает: Выполняет SQL-запрос и возвращает коллекцию объектов указанного типа T. Используется для SELECT запросов, возвращающих несколько строк.

Параметры:

  • sql (string) — SQL-запрос.
  • param (object, необязательно) — анонимный объект с параметрами запроса.
  • transaction (IDbTransaction, необязательно) — транзакция.
  • buffered (bool, по умолчанию true) — буферизировать ли результат в памяти. Если false, возвращается итератор, читающий данные по мере обхода.
  • commandTimeout (int?, необязательно) — таймаут выполнения команды.
  • commandType (CommandType?, необязательно) — тип команды (Text, StoredProcedure).

Пример:

var users = connection.Query<User>("SELECT * FROM Users WHERE Age > @age", new { age = 18 });

2. QueryFirst<T>

Что делает: Выполняет SQL-запрос и возвращает первый элемент результата. Если результат пуст, выбрасывает исключение InvalidOperationException.

Параметры: Аналогичны Query<T>.

Пример:

var user = connection.QueryFirst<User>("SELECT * FROM Users WHERE Id = @id", new { id = 1 });

3. QueryFirstOrDefault<T>

Что делает: Выполняет SQL-запрос и возвращает первый элемент результата. Если результат пуст, возвращает default(T) (null для ссылочных типов).

Параметры: Аналогичны Query<T>.

Пример:

var user = connection.QueryFirstOrDefault<User>("SELECT * FROM Users WHERE Id = @id", new { id = 999 });

4. QuerySingle<T>

Что делает: Выполняет SQL-запрос и возвращает единственный элемент. Если результат содержит более одной строки, выбрасывает исключение. Если результат пуст — также исключение.

Параметры: Аналогичны Query<T>.

Пример:

var user = connection.QuerySingle<User>("SELECT * FROM Users WHERE Id = @id", new { id = 1 });

5. QuerySingleOrDefault<T>

Что делает: Выполняет SQL-запрос и возвращает единственный элемент. Если результат пуст, возвращает default(T). Если строк больше одной — исключение.

Параметры: Аналогичны Query<T>.

Пример:

var user = connection.QuerySingleOrDefault<User>("SELECT * FROM Users WHERE Id = @id", new { id = 999 });

6. Execute

Что делает: Выполняет SQL-команду, не возвращающую строки (INSERT, UPDATE, DELETE, DDL). Возвращает количество затронутых строк.

Параметры:

  • sql (string) — SQL-запрос.
  • param (object, необязательно) — параметры.
  • transaction (IDbTransaction, необязательно) — транзакция.
  • commandTimeout (int?, необязательно) — таймаут.
  • commandType (CommandType?, необязательно) — тип команды.

Пример:

int affected = connection.Execute("UPDATE Users SET Age = @age WHERE Id = @id", new { age = 25, id = 1 });

7. ExecuteScalar<T>

Что делает: Выполняет SQL-запрос и возвращает первый столбец первой строки результата в виде значения указанного типа. Идеально для запросов с агрегатными функциями (COUNT, SUM).

Параметры: Аналогичны Execute.

Пример:

int count = connection.ExecuteScalar<int>("SELECT COUNT(*) FROM Users");

8. QueryMultiple

Что делает: Выполняет SQL-запрос, который возвращает несколько результирующих наборов (например, при использовании нескольких SELECT или хранимой процедуры). Возвращает объект SqlMapper.GridReader, из которого можно последовательно читать наборы.

Параметры: Аналогичны Query<T>.

Пример:

using (var multi = connection.QueryMultiple("SELECT * FROM Users; SELECT * FROM Orders"))
{
    var users = multi.Read<User>();
    var orders = multi.Read<Order>();
}

9. ExecuteReader

Что делает: Выполняет SQL-запрос и возвращает IDataReader для низкоуровневого чтения данных. Полезно, когда нужен полный контроль над процессом чтения.

Параметры: Аналогичны Execute.

Пример:

using (var reader = connection.ExecuteReader("SELECT Id, Name FROM Users"))
{
    while (reader.Read())
    {
        Console.WriteLine($"{reader["Id"]}: {reader["Name"]}");
    }
}

10. ExecuteAsync / QueryAsync и асинхронные версии

Что делает: Все перечисленные методы имеют асинхронные версии с суффиксом Async. Они возвращают Task или Task<T> и поддерживают CancellationToken.

Параметры: Аналогичны синхронным версиям + cancellationToken (необязательно).

Пример:

var users = await connection.QueryAsync<User>("SELECT * FROM Users");

Реальный практический пример использования

Рассмотрим реальный сценарий: интернет-магазин с таблицами Products и Categories. Нам нужно получить список товаров с названиями категорий, а также добавить новый товар.

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public int CategoryId { get; set; }
    public string CategoryName { get; set; } // из JOIN
}

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class ProductRepository
{
    private readonly string _connectionString;

    public ProductRepository(string connectionString)
    {
        _connectionString = connectionString;
    }

    // Получение всех товаров с категориями
    public async Task<IEnumerable<Product>> GetAllProductsAsync()
    {
        const string sql = @"
            SELECT p.Id, p.Name, p.Price, p.CategoryId, c.Name AS CategoryName
            FROM Products p
            INNER JOIN Categories c ON p.CategoryId = c.Id";

        using (var connection = new SqlConnection(_connectionString))
        {
            return await connection.QueryAsync<Product>(sql);
        }
    }

    // Добавление нового товара
    public async Task<int> AddProductAsync(Product product)
    {
        const string sql = @"
            INSERT INTO Products (Name, Price, CategoryId)
            VALUES (@Name, @Price, @CategoryId);
            SELECT CAST(SCOPE_IDENTITY() AS INT);";

        using (var connection = new SqlConnection(_connectionString))
        {
            return await connection.QuerySingleAsync<int>(sql, product);
        }
    }

    // Получение товаров с пагинацией
    public async Task<IEnumerable<Product>> GetProductsPagedAsync(int pageNumber, int pageSize)
    {
        const string sql = @"
            SELECT p.Id, p.Name, p.Price, p.CategoryId, c.Name AS CategoryName
            FROM Products p
            INNER JOIN Categories c                    

Рекомендаций