Розширення Entity Framework 6, про які ви могли і не знати

- Database First без EDMX
- Робота з відокремленими графами
- Модифікація SQL. Додавання табличних вказівок
- Кешування даних за межами часу життя DbContext
- Retry при помилках від SQL Server
- Підмінюємо DbContext, ізолюємося від реальної БД
- Швидка вставка
Database First без EDMX
Не хотілося б розпочинати черговий раунд суперечки "Code First vs. Database First". Краще просто розповім, як полегшити собі життя, якщо ви надаєте перевагу Database First. Багато розробників, які використовують цей підхід, наголошують на незручності роботи з громіздким EDMX-файлом. Цей файл може перетворити на пекло командну розробку, сильно ускладнюючи злиття паралельних змін внаслідок постійного "перемішування" своєї внутрішньої структури. Серед інших недоліків для моделей з кількома сотнями сутностей (звичайний такий legacy-моноліт), ви можете зіткнутися з сильним падінням швидкості будь-якої дії, працюючи зі стандартним EDMX-дизайнером.
Рішення здається очевидним – необхідно відмовитися від EDMX на користь альтернативних засобів генерації POCO та зберігання метаданих. Завдання-то нескладне, і в EF є "з коробки" - це пункт "Generate Code First From Database", доступний Visual Studio (VS2015 точно). Але на практиці дуже незручно накочувати зміни БД на отриману модель через цей інструмент. Далі, хто працює з EF досить давно – може пам'ятати розширення Entity Framework Power Tools, що вирішує схожі завдання – але, на жаль, проект більше не розвивається (на VS2015 без хаку не поставити), а частина розробників цього інструменту нині працює безпосередньо у команді EF.
Здавалося б, все погано – і тут ми знайшли EntityFramework Reverse POCO Generator. Це Т4-шаблон для генерації POCOна основі існуючої БД з великою кількістю налаштувань та відкритим вихідним кодом. Підтримуються всі основні фічі EDMX, і є ряд додаткових смакот: генерація FakeDbContext/FakeDbSet для юніт-тестування, покриття моделей атрибутами (напр. DataContract/DataMember) та ін. Завдяки Т4 є повний контроль за генерацією коду. Резюмуючи: працює стабільно, команді подобається, легко мігрувати існуючі проекти.
Робота з відокремленими графами
Прикріпити до DbContext новий, або раніше отриманий іншому контексті одиничний об'єкт зазвичай легко. Проблеми починаються у разі графів, тобто. сутностей зі зв'язками - EF "з коробки" не відстежує зміни у вмісті navigation properties сутності, що знову приєднується до контексту. Для відстеження змін для кожного об'єкта-сутності під час життя контексту повинен існувати відповідний entry — об'єкт зі службовою інформацією, у тому числі станом сутності (Added, Modified, Deleted тощо). Заповнити entries для приєднання графа можливо як мінімум 2 шляхами:
- Зберігати стан у самих сутностях, самостійно відстежуючи зміни. Таким чином, наш відокремлений граф міститиме всю необхідну інформацію для приєднання.
- Нічого не робити заздалегідь, а при додаванні графа до нового контексту — підтягнути з БД вихідний граф і проставити стани на основі порівняння двох графів.
Прикладрішення #1 можна знайти, наприклад, у свіжому Pluralsight-курсі від Julie Lerman, відомого фахівця з EF. Для його самостійної generic-реалізації необхідна велика кількість рухів тіла. Всі сутності повинні реалізувати інтерфейс IStateObject:
Тим чи іншим способом необхідно забезпечити актуальністьзначень State, щоб після ручного приєднання кожної сутності у графі до контексту:
пройти по всій entry, редагуючи їх стан:
У цьому випадку нам не потрібні додаткові звернення до БД, але рішення виходить об'ємне, тендітне, і потенційно неробоче для відносин "багатьом-багатьом". Ще й моделі засмічили (до речі, вимогу реалізації інтерфейсу можна вирішити шляхом модифікації Т4-шаблонів із попереднього розділу статті).
Розглянеморішення #2. Скажу коротко:
Модифікація SQL. Додавання табличних вказівок
Генерація, начебто, простий інструкції SELECT… FROM Table WITH (UPDLOCK) не підтримується EF. Зате є interceptor'и, що дозволяють модифікувати SQL, що генерується, будь-яким підходящим способом, наприклад, за допомогою регулярних виразів. Давайте додамо UPDLOCK на кожен SELECT, що генерується, в межах часу життя контексту (природно, гранулярність — не обов'язково контекст, все залежить від вашої реалізації):
Для цього оголосимо метод With всередині контексту і зареєструємо interceptor: