ohalay
Posted on October 26, 2021
Часом буває так, що доводиться працювати з проектом, який використовує старі технології, а нам, як розробникам, хочеться використовувати щось нове, модне, що всі зараз обговорюють. Натомість в клієнта свої цілі - додавання нового функціоналу, який допоможе розвивати бізнес. В цій статті я опишу яким чином мені вдалось задовольнити цілі розробників і клієнта та мігрувати з EF 6 до EF Core 5. В аплікації використовується підхід code-first
.
Використання EF 6 та EF Core 5 паралельно
З точки зору даних, як EF 6 так і EF Core 5 є мапером, який конвертує представлення даних з бази до коду або навпаки. Виходячи з цього, ми можемо реалізувати ще один мапер, використовуючи EF Core 6.
Цей гібридний підхід можна реалізувати досить швидко. Для цього необхідно зробити наступні кроки.
Виділення моделей даних в окремий проект
Так як наші моделі даних, класи, залишаються незмінними при обох підходах, їх можна виділити в окремий проект. Тепер ми можемо їх використовувати в обох підходах. Щоб перевірити чи ми випадково нічого не забули/змінили можна спробувати додати міграцію в EF 6. Якщо вона пуста, то ми нічого не змінили.
EntityFramework6\add-migration <my_test_migration>
Створення EF 5 Core контексту бази даних
Створюємо новий контекст бази даних використовуючи EF Core 5, додаємо існуючі моделі, які ми в попередньому кроці виділили в окремий проект і залишилось тільки додати конфігурацію таблиць. Конфігурація в нас описана за допомогою Data Annotation
та Fluent API
. Data Annotation
частина перевикористовуєтсья, оскільки знахотиься в проекті з моделями даних. Fluent API
частина конфігурації в EF Core 5 відрізняється від EF 6. Тому ми реалізовуємо її для контексту.
З поточним налаштуванням ми вже можемо новий API реалізовувати на EF Core 5 context чи разом з EF 6 context але поки тільки для запитів на читання.
А як бути з операціями модифікації?
З операція модифікації трохи складніше - необхідно забезпечити транзакційність, якщо ми використовуємо одразу два контексти (хочемо перевикористати існуючий, добре протестований код). Оскільки EF 6 та EF Core 5 використовують одні примітиви для транзакцій - DbTransaction
, то ми можемо в EF 6 реалізувати метод, який повертає транзакцію, а в EF Core 5 метод який використовує її. Ось ми і забезпечили транзакційність в двох контекстах.
public DbTransaction BeginTransaction() // EF 6
public UseTransaction(DbTransaction transaction) //EF Core 5
У випадку з одним контекстом наша робота не зміниться, просто викликаємо метод SaveChangesAsync()
А що з міграціями?
В нас джерело даних одне, отже і за міграції повинен відповідати один підхід. Наша ціль використовувати EF Core 5, отже, міграції будемо запускати використовуючи новий підхід. Для реалізації необхідно зробити наступні кроки:
- Додати початкову міграцію в EF Core 5 - яка повторить схему існуючої бази даних
Add-Migration Init-Schema
- Додати міграцію до EF 6 - Яка створить таблицю
__EFMigrationsHistory
і додастьInit-Schema
рекорд в цю таблицю. Це нам необхідно, щоб була можливість створити нову бази даних з міграцій.
public override void Up()
{
Sql(
@"CREATE TABLE [dbo].[__EFMigrationsHistory](
[MigrationId] [nvarchar](150) NOT NULL,
[ProductVersion] [nvarchar](32) NOT NULL
)
go
INSERT INTO __EFMigrationsHistory
VALUES('20210121161711_Init-Schema', '5.0.3')"
);
}
- І тепер достатньо запустити оновлення бази дани
EntityFramework6\update-database
- EF Core 5 міграції готові до використання. Можемо видалити старі міграції з коду та бази даних.
API тести
Тепер ми можемо малими кроками замінювати окремі запити до бази з EF 6 до EF Core 5. Підхід який я застосовув Red-Green-Refactor
. Це працює так - ми пишемо API тест використовуючи Asp Net Core TestHost, далі замінюємо запит(ти) з EF 6 на EF Core 5 і знову запускаємо тест.
Виправлення помилок
Впродовж міграції я наткнувся на кілька проблем.
- В проекті використовується
LinqKit
для EF 6, але є відповідник також для EF Core 5 -LinqKit.Microsoft.EntityFrameworkCore
- Метод
DefaultIfEmpty()
не працює в FE Core. Реалізація змінилась з
var currentHours = await ctx.SomeEntity
.Select(x => x.Hours)
.DefaultIfEmpty()
.SumAsync();
на наступне
var currentHours = await ctx.SomeEntity
.SumAsync(x => (double?)x) ?? default;
- Метод
Concat()
- використовується в проекті при побудові проекцій і після міграції необхідно додавати.AsQurable()
до Navigation properties
entity.InstancesTo
.AsQueryable()
.Where(x => x.Name != name)
.Concat(entity.InstancesFrom.AsQueryable())
Посилання
- https://www.oreilly.com/library/view/modern-c-programming/9781941222423/f_0054.html
- https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-5.0
- https://github.com/scottksmith95/LINQKit
- https://stackoverflow.com/questions/59555696/defaultifempty-exception-bug-or-limitation-with-ef-core
Posted on October 26, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.