MS SQL Server для відстежування змін в Entity Framework
MS SQL Server має дуже корисну change tracking функціональність. Вона дозволяє відстежування зміни в базі даних.
Ця функціональність описана в документації, і я не буду розповідати, як сконфігурувати та використати її на рівні бази даних. У себе ми використовуємо Entity Framework для доступу до даних у наших проєктах, і дуже часто нам потрібно синхронізувати дані між двума чи більше базами даних. Один зі сценарієв, які можуть бути використані, це SQL Server change tracking. Але як його адаптувати до Entity Framework? Подивимісь, що дає нам change tracking:
По-перше, SQL Server надає нам системну функцію CHANGE_TRACKING_CURRENT_VERSION, яка просто повертає останню версію на рівні бази даних, це просто ціле число. Досить просто навчити наш контекст повертати це значення:
Далі нам потрібно навчити Entity Framework повертати дані відносно версії, яку ми передамо. SQL Server дозволяє нам це зробити використовуючи системну функцію CHANGETABLE, щось на кшталт такого:
де 42 — передана версія.
Щоб дозволити Entity Framework повертати ці дані, ми можемо використати FromSqlRaw-метод, але перед цим нам необхідно згенерувати відповідний запит. Щоб це зробити, ми додали наступний метод:

Ок, зараз в нас є можлисвіть генерувати tracking-запит для певною сутності та зараз нам треба додати можливість повертати змінені сутності (їхні екземпляри) до нашого класу контекста. Ідея в тому, щоб контекст реалізував такий інтерфейс:
Визначення IVersionedIEntity: 
Дуже просто, чи не так? Версійована сутність просто огортає наявну сутність та декорує її Operation та EntityVersion властивостями.
Погляньмо, як один з методів, що повертає змінені дані реалізований:
Щоб дозволити огортання сутності ми додали такий метод конфігурації:
Як можна побачити, між версіонною сутністю та основною сутністю налаштований зв’язок один до одного, що дозволяє завантажти версіону сутність разом з самими даними, зміни з яких потрібно відстежувати.
Як це виглядає для реальної сутності:
Уявімо, що в нас є сутність Movie:
А зараз створімо версіонну обгортку:

Дуже важлива річ: обидві сутності повинні мати однакову ключову властивість (чи властивості, якщо ключ містить більш одного поля), тому обидві сутності реалізують IMovie-інтерфейс.
І конфігурація цих сутностей виглядає так:

І базова конфігурація BaseMovieConfiguration:
Маючи все перераховане, ви можете дуже просто отримати змінені дані з вашої бези даних:
Додавання:

Оновлення:

Видалення:

Цей підхід може бути використаним в багатьох сценаріях, коли вам необхідно синхронізувати дані між базами даних, чи вам просто потрібно відстежувати зміни у вашій базі даних.
Реальний сценарій:
В нас є дуже велика база даних, яка використовується великим корпоративним застосунком (App1).
Також в нас є невеличкий саморобний застосунок (App2), який використовую деякі дані з App1.
App2 потребує дані з App1 оновлені фактично в реальному часі. В App2 ми використали два EF-контексти: контекст, що відстежується, підключений до бази App1, та основний контекст App2.
Обидва контексти використовують одні й ті ж сутності, але для контексту, що відстежує зміни, додатково зконфігуровані версіонні сутності-обгортки. Ми створили сервіс, який використовує обидва контексти, читає зміни з бази даних App1 та оновлює цільову базу даних, використовуючи основний контекст.
Вихідний код рішення доступний на GitHub також рішення опубліковане у вигляді nuget package.
10 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарівНадихаюча стаття.
Запитання з приводу Entity Framework. Навіщо використовувати Entity Framework, якщо можна реалізувати те ж саме з допомогою SQL запитів? Особливо якщо треба отримати дані з трьох таблиць
я чесно кажучи трошки гублюсь, коли бачу такі питання. звісно можна без EF. можна всі операції з даними реалізувати без EF. Ви очикуєте у якості відповіді перелік переваг та недоліків ORM-ів?
Я думав тут буде CDC (changed data capture)
А подумал про вот эту фичу
learn.microsoft.com/...08-r2/ms175110(v=sql.105
можна ще на це github.com/...e-with-sqltabledependency подивитись, але справа в тому, що там є ньюанси (тригери на таблицях джерела), що в нашому випадку нас не влаштовує.
Дякую за коментар.
Подивися temporal tables
Дякую за пораду, в нашому випадку ми не можемо їх використовувати
Можете, будь ласка, розповісти чому? Зараз якраз працюю над кількома проектами: перший був розгорнутий мною, і я використовую temporal tables. На даний момент особливих проблем, крім невеликого геморою з додаванням/видаленням стовпців та відсутністю можливості додати індекси в хісторiкал таблицю не бачу. Другий проект — використовує тригери для аудиту даних, і я планую незабаром це переписати. Ваша стаття дуже вдало потрапила під руку, т.к. схоже якраз що можна ці дані перенести кудись у монгу, і вбити тригери. Ну і другий варіант — використовувати temporal tables, хоча бачу ряд проблем з міграцією даних у цьому випадку
Тому що ми не можемо вносити великі зміни до бази джерела, так, для вмикання трекінгу треба також внести зміни в налаштування бази та таблиць (включити трекінг), але ці зміни мінімальні в порівнянні з temporal tables. Ну і взагалі, ціль моєї статі — просто подемонструвати як можна використати функціонал трекінгу в SQL Server в Entity Framework. Темпоральні таблиці підтримуються EF нативно, тому сенс про це говорити :)
Стосовно аудіту в нас також є рішення, але також не на рівні бази а на рівні EF. Аудіт трейл сериалізується в джейсон, правда в ту жу базу, але класти його в монгу — це цікавий варіант. Як на мене с більшими перспективами масштабування. Спочатку ми намагались реалізувати аудіт за допомогою темпоральних таблиць, але досить швидко стало зрозуміло, що це шлях в нікуди, досить важко відстежувати залежності.
забув ще додати, що використання темпоральних таблиць призвело б до зростання розміру і так не маленької бази даних джерела