В этом посте показано, как запросить сильно нормализованную базу данных SQL и сопоставить результат с набором сильно вложенных объектов C # POCO.
Ингредиенты:
- 8 строк C #.
- Какой-то достаточно простой SQL, который использует некоторые объединения.
- Две классные библиотеки.
Понимание, которое позволило мне решить эту проблему, - отделить MicroORM
от mapping the result back to the POCO Entities
. Таким образом, мы используем две отдельные библиотеки:
По сути, мы используем Dapper для запроса к базе данных, а затем используем Slapper.Automapper для отображения результата прямо в наши POCO.
Преимущества
- Простота . Это менее 8 строк кода. Я считаю, что это намного проще понять, отладить и изменить.
- Меньше кода . Несколько строк кода - это все, что Slapper.Automapper нужно для обработки всего, что вы ему бросаете, даже если у нас есть сложный вложенный POCO (т.е. POCO содержит,
List<MyClass1>
который, в свою очередь, содержит List<MySubClass2>
, и т. Д.).
- Скорость . Обе эти библиотеки имеют невероятный объем оптимизации и кэширования, благодаря чему они работают почти так же быстро, как вручную настроенные запросы ADO.NET.
- Разделение проблем . Мы можем заменить MicroORM на другой, и отображение все еще будет работать, и наоборот.
- Гибкость . Slapper.Automapper обрабатывает произвольно вложенные иерархии, он не ограничен парой уровней вложенности. Мы легко можем внести быстрые изменения, и все по-прежнему будет работать.
- Отладка . Сначала мы можем увидеть, что SQL-запрос работает правильно, а затем мы можем проверить, что результат SQL-запроса правильно сопоставлен с целевыми объектами POCO.
- Легкость разработки на SQL . Я считаю, что создание плоских запросов с
inner joins
возвращением плоских результатов намного проще, чем создание нескольких операторов выбора с сшиванием на стороне клиента.
- Оптимизированные запросы в SQL . В сильно нормализованной базе данных создание плоского запроса позволяет механизму SQL применять расширенную оптимизацию ко всему, что обычно было бы невозможно, если бы было построено и запущено много небольших индивидуальных запросов.
- Доверие . Dapper - это серверная часть StackOverflow, а Рэнди Бёрден - своего рода суперзвезда. Мне нужно что-то еще сказать?
- Скорость развития. Я смог выполнить несколько чрезвычайно сложных запросов с множеством уровней вложенности, и время разработки было довольно низким.
- Меньше ошибок. Я написал это однажды, он просто работал, и теперь этот метод помогает компании FTSE. Кода было так мало, что не было неожиданного поведения.
Недостатки
- Возвращено масштабирование за пределы 1 000 000 строк. Хорошо работает при возврате <100 000 строк. Однако, если мы возвращаем> 1000000 строк, чтобы уменьшить трафик между нами и SQL-сервером, мы не должны сглаживать его с помощью
inner join
(что возвращает дубликаты), вместо этого мы должны использовать несколько select
операторов и объединить все вместе на на стороне клиента (см. другие ответы на этой странице).
- Этот метод ориентирован на запросы . Я не использовал этот метод для записи в базу данных, но я уверен, что Dapper более чем способен сделать это с дополнительной работой, поскольку сам StackOverflow использует Dapper в качестве уровня доступа к данным (DAL).
Тестирование производительности
В моих тестах Slapper.Automapper добавил небольшие накладные расходы к результатам, возвращаемым Dapper, что означало, что он все еще был в 10 раз быстрее, чем Entity Framework, и комбинация все еще чертовски близка к теоретической максимальной скорости, на которую способен SQL + C # .
В большинстве практических случаев большая часть накладных расходов будет связана с неоптимальным SQL-запросом, а не с некоторым отображением результатов на стороне C #.
Результаты тестирования производительности
Общее количество итераций: 1000
Dapper by itself
: 1,889 миллисекунды на запрос при использовании 3 lines of code to return the dynamic
.
Dapper + Slapper.Automapper
: 2,463 миллисекунды на запрос, используя дополнительный 3 lines of code for the query + mapping from dynamic to POCO Entities
.
Пример работы
В этом примере у нас есть список Contacts
, и каждый Contact
может иметь один или несколько phone numbers
.
POCO Entities
public class TestContact
{
public int ContactID { get; set; }
public string ContactName { get; set; }
public List<TestPhone> TestPhones { get; set; }
}
public class TestPhone
{
public int PhoneId { get; set; }
public int ContactID { get; set; }
public string Number { get; set; }
}
Таблица SQL TestContact
Таблица SQL TestPhone
Обратите внимание, что у этой таблицы есть внешний ключ, ContactID
который ссылается на TestContact
таблицу (это соответствует List<TestPhone>
POCO выше).
SQL, который дает плоский результат
В нашем SQL-запросе мы используем столько JOIN
операторов, сколько нам нужно, чтобы получить все необходимые данные в плоской денормализованной форме . Да, это может привести к дублированию вывода, но эти дубликаты будут автоматически удалены, когда мы будем использовать Slapper.Automapper для автоматического отображения результата этого запроса прямо в нашу карту объектов POCO.
USE [MyDatabase];
SELECT tc.[ContactID] as ContactID
,tc.[ContactName] as ContactName
,tp.[PhoneId] AS TestPhones_PhoneId
,tp.[ContactId] AS TestPhones_ContactId
,tp.[Number] AS TestPhones_Number
FROM TestContact tc
INNER JOIN TestPhone tp ON tc.ContactId = tp.ContactId
Код C #
const string sql = @"SELECT tc.[ContactID] as ContactID
,tc.[ContactName] as ContactName
,tp.[PhoneId] AS TestPhones_PhoneId
,tp.[ContactId] AS TestPhones_ContactId
,tp.[Number] AS TestPhones_Number
FROM TestContact tc
INNER JOIN TestPhone tp ON tc.ContactId = tp.ContactId";
string connectionString =
using (var conn = new SqlConnection(connectionString))
{
conn.Open();
{
dynamic test = conn.Query<dynamic>(sql);
Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(TestContact), new List<string> { "ContactID" });
Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(TestPhone), new List<string> { "PhoneID" });
var testContact = (Slapper.AutoMapper.MapDynamic<TestContact>(test) as IEnumerable<TestContact>).ToList();
foreach (var c in testContact)
{
foreach (var p in c.TestPhones)
{
Console.Write("ContactName: {0}: Phone: {1}\n", c.ContactName, p.Number);
}
}
}
}
Вывод
Иерархия сущностей POCO
Глядя в Visual Studio, мы видим, что Slapper.Automapper правильно заполнил наши объекты POCO, т.е. у нас есть List<TestContact>
и у каждого TestContact
есть List<TestPhone>
.
Ноты
И Dapper, и Slapper.Automapper кешируют все внутренне для скорости. Если вы столкнетесь с проблемами памяти (что очень маловероятно), убедитесь, что вы время от времени очищаете кеш для них обоих.
Убедитесь, что вы назвали возвращаемые столбцы, используя нотацию подчеркивания ( _
), чтобы дать Slapper.Automapper подсказки о том, как отобразить результат в объекты POCO.
Убедитесь, что вы даете Slapper.Automapper подсказки о первичном ключе для каждой POCO Entity (см. Строки Slapper.AutoMapper.Configuration.AddIdentifiers
). Вы также можете использовать Attributes
для этого POCO. Если вы пропустите этот шаг, то все может пойти не так (теоретически), поскольку Slapper.Automapper не знает, как правильно выполнить сопоставление.
Обновление 2015-06-14
Успешно применил этот метод к огромной производственной базе данных с более чем 40 нормализованными таблицами. Он отлично работал для сопоставления расширенного запроса SQL с более чем 16 inner join
и left join
надлежащей иерархией POCO (с 4 уровнями вложенности). Запросы выполняются ослепительно быстро, почти так же быстро, как ручное кодирование в ADO.NET (обычно это составляло 52 миллисекунды для запроса и 50 миллисекунд для отображения плоского результата в иерархию POCO). В этом нет ничего революционного, но он определенно превосходит Entity Framework по скорости и простоте использования, особенно если все, что мы делаем, - это выполняем запросы.
Обновление 2016-02-19
Код безупречно работает в продакшене уже 9 месяцев. В последней версии Slapper.Automapper
есть все изменения, которые я применил для устранения проблемы, связанной с возвращением значений NULL в запросе SQL.
Обновление 2017-02-20
Код безупречно работает в производственной среде в течение 21 месяца и обрабатывает непрерывные запросы от сотен пользователей в компании FTSE 250.
Slapper.Automapper
также отлично подходит для отображения файла .csv прямо в список POCO. Прочтите файл .csv в список IDictionary, затем сопоставьте его прямо с целевым списком POCO. Единственный трюк состоит в том, что вам нужно добавить свойство int Id {get; set}
и убедиться, что оно уникально для каждой строки (иначе автомаппер не сможет различить строки).
Обновление 2019-01-29
Незначительное обновление для добавления дополнительных комментариев к коду.
См .: https://github.com/SlapperAutoMapper/Slapper.AutoMapper