У цій публікації показано, як здійснити запит до сильно нормалізованої бази даних 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. Коду було настільки мало, що не було несподіваної поведінки.
Недоліки
- Повернено масштаб понад 1000000 рядків. Добре працює при поверненні <100 000 рядків. Однак, якщо ми повертаємо> 1 000 000 рядків, щоб зменшити трафік між нами та 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
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-сутності, тобто ми маємо a List<TestContact>
, і кожен TestContact
має a List<TestPhone>
.
Примітки
Як Dapper, так і Slapper. Automapper кешує все внутрішньо для швидкості. Якщо у вас трапляються проблеми з пам'яттю (дуже малоймовірно), переконайтеся, що час від часу очищаєте кеш для обох.
Переконайтесь, що ви називаєте стовпці, що повертаються, використовуючи нотацію підкреслення ( _
), щоб надати Slapper.Automapper підказки про те, як відобразити результат в POCO Entities.
Переконайтеся, що ви даєте підказки Slapper.Automapper на первинному ключі для кожної сутності POCO (див. Рядки Slapper.AutoMapper.Configuration.AddIdentifiers
). Ви також можете використовувати Attributes
для цього на POCO. Якщо ви пропустите цей крок, він може піти не так (теоретично), як Slapper. Automapper не знатиме, як правильно виконати відображення.
Оновлення 14.06.2015
Успішно застосували цю техніку до величезної виробничої бази даних із понад 40 нормалізованими таблицями. Це чудово працювало, щоб відобразити розширений SQL-запит із понад 16 inner join
і left join
до відповідної ієрархії POCO (з 4 рівнями вкладеності). Запити сліпуче швидкі, майже такі ж швидкі, як і ручне кодування в ADO.NET (зазвичай це було 52 мілісекунди для запиту та 50 мілісекунд для відображення з плоского результату в ієрархію POCO). Це насправді нічого революційного, але це, безсумнівно, перевершує Entity Framework за швидкістю та простотою використання, особливо якщо все, що ми робимо, це запуски запитів.
Оновлення 2016-02-19
Code працює бездоганно у виробництві вже 9 місяців. Остання версія Slapper.Automapper
містить усі зміни, які я застосував, щоб вирішити проблему, пов’язану з поверненням нулів у запиті SQL.
Оновлення 2017-02-20
Code працює бездоганно у виробництві вже 21 місяць і постійно обробляє запити сотень користувачів у компанії FTSE 250.
Slapper.Automapper
також чудово підходить для відображення файлу .csv безпосередньо у списку POCO. Прочитайте файл .csv у списку IDictionary, а потім перекладіть його прямо в цільовий список POCO. Єдина хитрість полягає в тому, що вам потрібно додати власність int Id {get; set}
і переконатися, що вона унікальна для кожного рядка (інакше автоматичний програвач не зможе розрізнити рядки).
Оновлення 2019-01-29
Незначне оновлення, щоб додати більше коментарів до коду.
Див .: https://github.com/SlapperAutoMapper/Slapper.AutoMapper