Dapper – micro-ORM — II
Продвинутые возможности
К продвинутым возможностям можем отнести выполнение пакетной вставки данных, получение и разбор нескольких запросов за раз, множественный маппинг.
Пакетная вставка данных как пример использования списка параметров
С помощью Dapper можно осуществлять пакетную вставку данных передав список данных. Т.е. у нас есть список людей для внесения в базу данных, и с помощью единственной команды можно это провернуть. Лучше сразу на примере показать:
public void ExecuteNonSelectBulkCommand() {
con.Open();
var list = new List();
for (var i = 1; i < 5; i++) {
list.Add(new Person {
PersonId = Guid.NewGuid(),
Birth = new DateTime(1980, i, i),
Name = "Pers " + i,
});
}
con.Execute("INSERT INTO Person (PersonId, Name, Birth) VALUES(@PersonId, @Name, @Birth)"
, list);
var persons = con.Query("Select * from Person");
foreach (var p in persons)
Console.WriteLine("{0} {1} {2}", p.Name, p.Birth, p.Resident);
con.Close();
}
Создаем несколько экземпляров класса Person, и передаем список в качестве параметра в команду Execute. Потом можно визуально проверить результат.
В качестве параметра для пакетной обработки может выступать любая коллекция, которая реализует интерфейс IEnumerable<T>.
Упс!
Ничего же не менялось в коде, и все переменные указаны верно, в чем же дело?
Дело в особенностях реализации Dapper и то, как построен класс Person. На данный момент PersonId является полем, так попробуем его изменить на свойство. Дописываем get, set каждому полю и пробуем снова. Все заработало! Данный момент надо иметь в виду при передаче параметров списком.
Список параметров в запросах
Dapper достаточно интеллектуален и, когда надо, по списку параметров формирует единственный запрос вместо множества.
Пример:
public void ListEnumParams() {
con.Open();
var sql = "Select * from Person where name in @names";
var names = new List {"Pers 1", "Pers 2"};
var persons = con.Query(sql, new {names});
foreach (var p in persons)
Console.WriteLine("{0} {1} {2}", p.Name, p.Birth, p.Resident);
con.Close();
}
В данном случае необходимо список параметров оборачивать в анонимный класс, так как параметры представлены элементарными типами.
Если посмотреть активность по запросам в профайлере, то можно увидеть, что был единственный запрос:
exec sp_executesql N'Select * from Person where name in (@names1,@names2)',N'@names1 nvarchar(4000),@names2 nvarchar(4000)',@names1=N'Pers 1',@names2=N'Pers 2'
Маппинг сложносоставных методов
Можно научить фреймворк восстанавливать составные типы по сложным запросам в базу данных. Например, нам надо получить всех наших товарищей с заполненными адресами. Для этого не требуется вносить никаких дополнительных данных в исходные классы, все делается на уровне получения данных.
Мы снова воспользуемся Query, однако перегруженным вариантом метода, который принимает несколько шаблонных типов.
public void MultiMappings() {
con.Open();
var sql = "Select * from Person p join Address a on p.AddressId = a.AddressId";
var persons = con.Query(sql, (pers, address) => {
pers.Address = address;
return pers;
});
foreach (var p in persons)
Console.WriteLine("{0} {1}", p.Name, p.Address.City);
con.Close();
}
В угловых скобках сначала идут типы, на которые надо постараться замапить ответ, последним типом указывается, какие данные будут результирующими. В параметрах метода надо указать текст запроса и правило, по которому надо создавать сложносоставные объекты. Правилом в данном случае выступает лямбда-функция, но как вы понимаете, в общем случае туда можно передавать Func.
Запускаем.
И опять беда. Дело в том, что запрос в текущем виде вернет результат в виде составного набора полей из двух таблиц. Логическое разделение данных идет по первичным ключам, и Dapper исходит из предположения что они названы «Id». Если это не так, то требуется явно указать поле по которому происходит конкатенация таблиц.
Модифицируем код:
public void MultiMappings() {
con.Open();
var sql = "Select * from Person p join Address a on p.AddressId = a.AddressId";
var persons = con.Query(sql, (pers, address) => {
pers.Address = address;
return pers;
},
splitOn: "AddressId");
foreach (var p in persons)
Console.WriteLine("{0} {1}", p.Name, p.Address.City);
con.Close();
}
В метод Query добавили именованный атрибут splitOn с именем поля, по которому идет объединение таблиц.
Мульти запрос
Помимо всего прочего есть возможность сделать запрос из нескольких не связанных между собой таблиц. Может быть удобно, если процедура возвращает несколько наборов данных.
Для обработки таких запросов следует применять метод расширения QueryMultiple, который принимает текст запроса.
public void MultiSelect() {
con.Open();
var sql = @"Select * from Person
select * from Address";
using (var multi = con.QueryMultiple(sql)) {
// iterated once
var person = multi.Read().ToList();
var address = multi.Read().ToList();
foreach (var p in person)
Console.WriteLine("{0}", p.Name);
foreach (var a in address)
Console.WriteLine("{0}", a.City);
}
con.Close();
}
Ключевым моментом здесь является то, что надо сразу материализовать запрос:
var person = multi.Read<Person>().ToList();
Если не использовать метод ToList(), то при итерации в цикле foreach получите исключение говорящие о том, что возможен единственный проход по списку.
Varchar
Иногда могут быть конфликты в связи с типом параметра, тогда можно задавать их более явно. Например:
new DbString { Value = "abcde", IsFixedLength = true, Length = 10, IsAnsi = true }
Больше контроля над параметрами процедуры
Можно явно указывать какие параметры в процедуре будут модифицированы, и что является результатом.
public void FancyParameters() {
var p = new DynamicParameters();
p.Add("@a", 11);
p.Add("@b", dbType: DbType.Int32, direction: ParameterDirection.Output);
p.Add("@c", dbType: DbType.Int32, direction: ParameterDirection.ReturnValue);
con.Execute("spMagicProc", p, commandType: CommandType.StoredProcedure);
var b = p.Get("@b");
var c = p.Get("@c");
}
Заключение
Рассмотрев Dapper можно сказать, что работая с ним, надо будет вести отдельный класс для запросов, с учетом возможных параметров. Очень интересно посмотреть его использование в реальном проекте, как идет работа с запросами, как они хранятся. Насколько гибко они построены. У меня есть некоторые мысли по этому поводу, но они требуют проверки и более основательного подхода и времени.
Dapper оставляет приятное впечатление, особенно с учетом его скорости работы, что немаловажный фактор.
Несмотря на предостережение построения запросов на лету, хотелось бы все же какой-то функционал по этой теме, чтобы можно было строить запросы в духе LINQ и затем переводить их в текст. При определенной сноровке, мне кажется, можно к этому приспособить Linq2Sql, следует поэкспериментировать в деле построения запросов и передачи параметров в запрос. Параметры в L2S именуются порядковыми номерами.
В таблице сравнения скоростей работы с POCO объектами упоминается PetaPoco, который имеет встроенный составитель запросов. Так что следующий на очереди PetaPoco, думаю, что это не менее интересный и быстрый микро-ORM. После этого уже можно будет выбирать, зная потенциальные возможности и недостатки исходя из конкретных задач и исходных данных.
Hard’n’heavy!
Нет обратных ссылок на эту запись.
