Як я мігрував з EF 6 до EF Core 5

ohalay

ohalay

Posted on October 26, 2021

Як я мігрував з EF 6 до EF Core 5

Часом буває так, що доводиться працювати з проектом, який використовує старі технології, а нам, як розробникам, хочеться використовувати щось нове, модне, що всі зараз обговорюють. Натомість в клієнта свої цілі - додавання нового функціоналу, який допоможе розвивати бізнес. В цій статті я опишу яким чином мені вдалось задовольнити цілі розробників і клієнта та мігрувати з 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>
Enter fullscreen mode Exit fullscreen mode

Створення 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
Enter fullscreen mode Exit fullscreen mode

У випадку з одним контекстом наша робота не зміниться, просто викликаємо метод 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')"
    );
}
Enter fullscreen mode Exit fullscreen mode
  • І тепер достатньо запустити оновлення бази дани 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 і знову запускаємо тест.

Виправлення помилок

Впродовж міграції я наткнувся на кілька проблем.

  1. В проекті використовується LinqKit для EF 6, але є відповідник також для EF Core 5 - LinqKit.Microsoft.EntityFrameworkCore
  2. Метод DefaultIfEmpty() не працює в FE Core. Реалізація змінилась з
var currentHours = await ctx.SomeEntity
  .Select(x => x.Hours)
  .DefaultIfEmpty()
  .SumAsync();
Enter fullscreen mode Exit fullscreen mode

на наступне

var currentHours = await ctx.SomeEntity
  .SumAsync(x => (double?)x) ?? default;
Enter fullscreen mode Exit fullscreen mode
  • Метод Concat() - використовується в проекті при побудові проекцій і після міграції необхідно додавати .AsQurable() до Navigation properties
entity.InstancesTo
  .AsQueryable()
  .Where(x => x.Name != name)
  .Concat(entity.InstancesFrom.AsQueryable())
Enter fullscreen mode Exit fullscreen mode

Посилання

  1. https://www.oreilly.com/library/view/modern-c-programming/9781941222423/f_0054.html
  2. https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-5.0
  3. https://github.com/scottksmith95/LINQKit
  4. https://stackoverflow.com/questions/59555696/defaultifempty-exception-bug-or-limitation-with-ef-core
💖 💪 🙅 🚩
ohalay
ohalay

Posted on October 26, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related