Мои контакты


пятница, 15 августа 2014 г.

Работа с Dapper + Query Object

На проекте, который я сейчас веду с командой из четырех человек, мы решили отказаться от использования полноценной ORM в пользу Dapper. Чем это обусловлено, какие дает плюсы и минусы, я расскажу в этой заметке.


Dapper - это так называемая micro-ORM. По сути, это просто набор extension-методов для интерфейса IDbConnection, которые позволяют просто выполнить SQL-запрос, либо выполнить SQL-запрос и "замапить" результаты на DTO. Эту библиотеку используют, например, на StackOverflow.

В чем профит от использования Dapper?

  1. Быстрый старт: не нужно никаких конфигов, маппингов, программист сам управляет подключением.
  2. Самописные SQL-запросы: как правило, запросы, генерируемые ORM, получаются неоптимальными, а в случаем с Dapper запросы будут такие, какие нужно.
  3. Быстрый маппинг: ссылки для сравнения скорости приложу ниже.
Основной очевидный минус, который в то же время и плюс, - это написание SQL-кода вручную. На дистанции это создает проблемы при рефакторинге, а так же дублирование логики SQL-запросов. Отчасти с этим помогает справляться шаблон Query Object, о котором чуть ниже.

Когда использовать Dapper?

Мне видятся два варианта использования этой micro-ORM, зависящие от типа проекта. 
  • Простой небольшой проект. Быстрый старт и простая работа с библиотекой, позволят очень быстро показать работающий результат. Накидать DTO, QueryObject'ов и уже работать с БД. На текущем проекте это позволило нам достаточно быстро создать Back-end часть довольно крупного CRUD'a.
  • Highload проект или когда много взаимодействия с БД. Самописные SQL-запросы + высокая скорость маппинга дают ощутимый результат. 

Шаблон Query Object

Разрабатываемое приложение нужно расширять и поддерживать, а написание SQL-кода в проекте, может привести к сложностям при рефакторинге и дублированию. Например, когда мы изменяем название колонки в таблице БД, то соответственно нужно изменить название поля и в DTO и в запросе.

Отчасти с этой проблемой нам помогает справиться паттерн Query Object. По сути, это объект, который представляет собой запрос к БД, то есть инкапсулирует в себе логику запроса.

Пример кода без использования шаблона Query Object.
using (var connection = _factory.CreateConnection())
{
   var accounts = connection.Query <AccountDto> ("select Id, Name from Accounts").ToList();
}


Используя такой подход к работе с запросами, мы наплодим в коде кучу дублирования. Решим эту проблему добавлением Query Object.
using (var connection = _factory.CreateConnection())
{
   var selectAccount = new SelectAccount();
   var accounts = connection.Query<AccountDto>(selectAccount.All()).ToList();
}

То есть мы инкапсулировали сам SQL-запрос в некий класс, который создает QueryObject через вызов All, что означает выборку всех элементов.

Реализация Query Object может быть, например, такой:
public class QueryObject
{
   public QueryObject(string sql, object queryParams = null)
   {
      Sql = sql;
      QueryParams = queryParams;
   }

   public string Sql { get; private set; }
   public object QueryParams {get; private set;}
}


Метод-расширения IDbConnection, для работы с QueryObject:
public static IEnumerable<T> Query<T>(this IDbConnection connection, QueryObject queryObject)
{
   return connection.Query<T>(queryObject.Sql, queryObject.QueryParams);
}


Тогда реализация класса SelectAccount может быть такой:
public class SelectAccount
{
   public QueryObject All()
   {
      return new QueryObject("select Id, Name from Accounts");
   }

   public QueryObject ById(int id)
   {
      return new QueryObject(All().Sql + " where Id=@Id", new {Id = id});
   }
}

Теперь видно, что код запросов может быть переиспользуем в разных запросах.

Ссылки