Не так давно я утверждал, что нужно использовать высокоуровневую ORM и даже делал опрос на эту тему. Как показал опрос, не многие пишут SQL-запросы руками.
Проблематика
Для работы с типовыми проектами действительно не надо углубляться в вопросы специфики SQL. Но сейчас я веду проект, где без этого просто не обойтись. Речь про базы данных по 700ГБ и требования к отклику на UI менее 1 секунды. При этом происходит много разных расчетов, строятся графики и т.п.
Для начала я по привычке использовал NHibernate. Уже через пару итераций оказалось, что эта ORM мне не подойдет (не говоря уже про EntityFramework) по следующим причинам:
- Скорость: создание сессии, маппинг и другие сопутствующие вещи отнимали до 200 мс. В моем случае это 20% времени, которое тратилось впустую;
- Гибкость запросов: сначала я писал запросы на Linq, потом понял, что трудно сделать хитрые запросы и добавлять хинты через высокоуровневые интерфейсы типа Linq, QueryOver или Criteria. Из всех возможностей NHibernate я начал использовать только функцию ExecuteSql, но скорость маппинга была слишком низкой;
- Утечки памяти: увы, но в NHibernate есть утечки памяти. Когда в вашей системе делается много запросов, это становится критичным. Сервисы через 20-30 часов работы падали с OutOfMemoryException и MemProfiler указал на NHibernate;
- Много чтения, мало записи: в проекте мне надо считывать очень много данных, получается такой cRRRRud, поэтому UoW от NHibernate оказался тоже не нужен.
Dapper = Data Mapper
Раз уж я пишу SQL-запросы и выполняю их через ExecuteSQL, то было принято решение отказаться от ORM и уйти на более низкий уровень.
Выбор пал на библиотеку Dapper. Она представляет из себя несколько методов расширений для интерфейса IDbConnection.
Эта библиотека умеет из результатов запроса создавать объекты или просто выполнять SQL-скрипты, фактически, реализуя шаблон DataMapper. Запуск скриптов на SQL-сервере происходит как обычно через exec sp_executesql.
Самым популярным пользователем этой библиотеки является Stack Overflow, у нее почти 200 форков на GitHub и 42 тыс. скачиваний в NuGet. Всё это дает некоторую надежду на ее дальнейшее развитие и поддержку.
Ключевые особенности
В данный момент я использую Dapper в продакшене по нескольким причинам:
- Высокая скорость маппинга: различные сравнения можно посмотреть по ссылке Performance of SELECT mapping...;
- Низкоуровневое управление запросами: появилась возможность писать любые SQL-запросы, Dapper очень гибко делает маппинг, в том числе и в dynamic;
- Отсутствие накладных расходов: фактически мы управляем подключением к БД. Никаких маппингов, сгенерированных файлов, конфигурирования и т.п.
Примеры
Разработчики подготовили отличную демонстрацию возможностей: Tests.cs. Из примеров видно, на сколько легко и просто использовать эту библиотеку.
Шаблон QueryObject
Писать запросы, которые быстро выполняются, это хорошо, но приложение нужно развивать и поддерживать. Написание запросов на чистом SQL может привести к дублированию в коде и сложностям при рефакторинге.
Я выбрал стандартное решение - взял шаблон QueryObject. Суть шаблона в том, чтобы инкапсулировать всю логику запросов.
После 2-х месяцев работы с Dapper я выделил решение, которое можно будет использовать в других проектах. В этом решении используется связка Dapper, шаблона QueryObject и MiniProfiler.
Получается довольно компактный код:
// запуск профайлера можно делать опционально при старте приложения MiniProfiler.Start(); using (IDbConnection dbConnection = ConnectionFactory.Create()) { var selectProduct = new SelectProduct(); ProductDto productDto = dbConnection.Query<ProductDto>(selectProduct.ById(1)) .SingleOrDefault(); }
Кэширование SQL на стороне сервера
Способ кэширования запросов SQL-сервером является еще одной причиной для инкапсуляции SQL-запросов. Дело в том, что кэшируется сам текст запроса. Это значит, что лишний пробел или изменение регистра будут влиять на выполнение запросов. Если мы сводим все запросы в одно место, то вероятность ошибки уменьшается.
Оценим решение
Как любое решение в разработке ПО, это имеет свои за и против.
Преимущества
- Высокая скорость работы
- Стабильность, отсутствие утечек памяти;
- Гибкость при создании запросов;
- Легок для работы приложения, не требователен к ресурсам;
- Чистый домен приложения, без дополнительных интерфейсов и изменения кода для работы ORM;
- Надо больше думать при работе с данными.
Недостатки
- Написание SQL-кода вручную;
- Нет готового кэша и готовых провайдеров для кэширования;
- Надо больше думать при работе с данными;
- SQL-код будет зависеть от СУБД.
Эти за и против я отмечал во время перехода с NHibernate на Dapper в своем проекте.
Расширения для Dapper
Для этой библиотеки набролось уже довольно много разных расширений и улучшений, ниже список самых популярных:
Аналоги
Dapper не единственная micro-ORM на .NET Framework, ниже список аналогов, которые могут больше подойти для вашего проекта:
Живая демонстрация
20 марта на очередной встрече INETA я буду делать доклад на тему Dapper+QueryObject и покажу несколько живых примеров. Ссылка на регистрацию: http://ineta.ru/MPPC/Meeting/2013-03-20-18-30. Данные для подключения трансляции появятся ближе к началу события.