Ник Ковальский
Онбординг
Если вы, как и я, являетесь поклонником EF и хотели бы использовать его в своем мобильном приложении, я думаю, с появлением .Net Maui на рынке самое время начать.
Небольшое напоминание: для лучшего времени запуска приложения может быть лучше хранить данные boostrap в локальном хранилище мобильного устройства в форме json. А когда дело доходит до управления большими локальными данными с помощью фильтров, упорядочивания и т. д., EF определенно подходит.
Цель этой статьи — помочь избежать всех хлопот, связанных с поиском различных решений небольших проблем при реализации готовой к работе мобильной локальной базы данных и создании для нее миграций кода в первую очередь как в Windows, так и в >Mac. Я приглашаю вас просмотреть и повторно использовать исходный код примера этой статьи, вы найдете ссылку в конце. Как вы увидите, это шаблон приложения Maui с добавленной логикой базы данных EF.
Проверенным стандартом базы данных мобильного клиента является SQLite. Мы мгновенно найдем пакет nuget Microsoft.EntityFrameworkCore.Sqlite для установки вместе с SQLitePCLRaw.bundle_e_sqlite3 для собственных реализаций sqlite. Для создания миграции EF нам также потребуется установить Microsoft.EntityFrameworkCore.Tools.
Дело в том, что мы не можем установить эти nugets в наш единственный проект maui, потому что, если мы попытаемся создать миграции, мы получим хороший результат.
Стартап-проект MauiEF.Client нацелен на платформу Android. Инструменты консоли Entity Framework Core Package Manager не поддерживают эту платформу. См. https://aka.ms/efcore-docs-pmc-tfms для получения дополнительной информации.
.. поэтому нам нужно найти способ обойти это.
Для тех, кто не уверен, для чего нужны миграции, это в основном код, который инструктирует EF о структуре базы данных для создания и использования. Важным преимуществом здесь является то, что когда вы меняете свои модели, вам просто нужно автоматически генерировать дополнительные миграции для EF, чтобы соответствующим образом изменить существующую базу данных, добавляя-удаляя таблицы, столбцы и т. д.
Проекты установки
Теперь, когда мы знаем, что инструменты проектирования EF имеют неподдерживаемые платформы, нам нужно будет создать специальный проект «Мигратор», который будет напрямую запускаться EF и нацелен на чистую net-7.0, и мы сможем создавать миграции на виндовс или яблочная машина.
И Мигратору, и нашему Клиенту потребуется доступ к базе данных, поэтому мы переместим весь наш код, связанный с контекстом, в отдельный проект Общий. Тогда наша структура решения будет выглядеть так:
Проект Shared будет ссылаться
<!--Local database--> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.1" ></PackageReference> <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.3" ></PackageReference>
в то время как Migrator потребуются дополнительные инструменты для создания миграции:
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.1"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference>
Создать базу данных
Мы определим контекст нашего примера внутри проекта Shared следующим образом:
LocalDatabase.cs
/// <summary> /// Constructor for creating migrations /// </summary> public LocalDatabase() { File = Path.Combine("../", "Data1.db3"); Initialize(); } /// <summary> /// Constructor for mobile app /// </summary> /// <param name="filenameWithPath"></param> public LocalDatabase(string filenameWithPath) { File = filenameWithPath; Initialize(); } void Initialize() { if (!Initialized) { Initialized = true; SQLitePCL.Batteries_V2.Init(); Database.Migrate(); } } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder .UseSqlite($"Filename={File}"); }
Обратите внимание на два конструктора: один для средства миграции EF, а другой — для нашего приложения. Database.Migrate(); создает файлы базы данных, если она не существует, и применяет предоставленные миграции.
Возможно, вы захотите реализовать метод Database.EnsureDeleted(); в целях отладки, чтобы стереть данные при запуске.
Если у вас есть критическое изменение приложения, вы также можете изменить имя файла базы данных, чтобы воссоздать базу данных с нуля для существующих пользователей.
Хорошо, теперь мы можем внедрить контекст в наш MauiProgram.cs:
builder.Services.AddTransient<LocalDatabase>((services) => { return new LocalDatabase(Path.Combine(FileSystem.AppDataDirectory, "SQLite001.db3")); });
Создание миграций
Предоставленный исходный код не включает миграции. Если вы просто скомпилируете и запустите решение, оно вызовет исключение, так как EF не будет знать, как вам его выполнить Migrate();.
Но, пожалуйста, не волнуйтесь, миграцию очень легко создать.
Если вы используете Visual Studio для Windows:
- Измените стартовый проект с Client на Migrator.
- Откройте Консоль диспетчера пакетов: перейдите в Вид->Другие Windows->Консоль диспетчера пакетов.
- Установите для проекта по умолчанию значение Shared, EF будет искать контекстную модель внутри и добавлять туда миграции.
- Введите следующую команду, чтобы создать начальную миграцию:
add-migration Initial -Context MauiEF.Shared.Services.LocalDatabase -Verbose
Следующий метод с помощью команды like будет проиллюстрирован с использованием Visual Studio для Mac.
1 Откройте консоль: щелкните правой кнопкой мыши имя своего решения и выберите «Открыть в терминале». Вы должны попасть в папку решения.
2 Введите следующую команду, чтобы создать начальную миграцию через командную строку:
dotnet ef migrations add Initial -s Migrator -p Shared -c MauiEF.Shared.Services.LocalDatabase
Вы заметите, что мы указали вложенную папку стартового проекта -s Migrator и вложенную папку проекта по умолчанию -p Shared.
Если вы столкнулись с ошибкой из-за отсутствия команды ef, установите ее и добавьте в глобальный путь:
dotnet tool install --global dotnet-ef export PATH="$PATH:/Users/YOUR_USERNAME/.dotnet/tools"
Каждый раз, когда вы меняете модели контекста, вам приходится создавать дополнительные миграции. Таким образом, ваше приложение не сломается, если вы выпустите новую версию с измененными миграциями, EF (скорее всего) сохранит существующую базу данных пользователя и модифицирует ее в соответствии с новой схемой при инициализации вашей модели контекста. Почему скорее всего? Поскольку вы можете изменить свои модели таким образом, что связь или свойство внешнего ключа будут потеряны, в этом случае EF предупредит вас при создании миграции с чем-то вроде «Предупреждающие данные будут потеряны», так что все под вашим контролем, чтобы управлять данные о старых версиях приложений существующих пользователей.
Чтобы создать новую миграцию, просто создайте для нее уникальное имя (пример Visual Studio для Windows):
add-migration Change1 -Context MauiEF.Shared.Services.LocalDatabase -Verbose
Пример приложения
Теперь мы можем скомпилировать наш образец и запустить его, чтобы созданные пользователем данные сохранялись между запусками приложения. Контекстные операции выполняются асинхронно, поэтому мы не блокируем UI-поток.
MainPage.cs
public MainPage() { _context = App.Services.GetService<LocalDatabase>(); InitializeComponent(); var mainAuthor = _context.Authors .Include(i => i.Books) .FirstOrDefault(x => x.FirstName == "John" && x.LastName == "Doe"); if (mainAuthor == null) { Task.Run(async () => { mainAuthor = new Author() { FirstName = "John", LastName = "Doe" }; _context.Authors.Add(mainAuthor); await _context.SaveChangesAsync(); _author = mainAuthor; Update(); }).ConfigureAwait(false); } else { _author = mainAuthor; Update(); } } private void OnCounterClicked(object sender, EventArgs e) { count++; var title = $"My Story Part {count}"; var book = _author.Books.FirstOrDefault(x => x.Title == title); if (book == null) { CounterBtn.Text = $"Wrote \"{title}\""; Task.Run(async () => { _author.Books.Add(new Book { Title = title }); _context.Authors.Update(_author); await _context.SaveChangesAsync(); Update(); }).ConfigureAwait(false); } else { CounterBtn.Text = $"Reading \"{title}\""; } SemanticScreenReader.Announce(CounterBtn.Text); }
Заключительные слова
Когда вы компилируете свое первое приложение EF Maui для выпуска iOS, оно может аварийно завершать работу во время выполнения на реальном устройстве из-за того, что компиляция iOS AOT не поддерживает некоторые методы EF. Я бы не стал говорить здесь точнее, вы можете узнать об этом подробнее, но выход из ситуации заключается в том, чтобы добавить в ваш файл .csprj некую изюминку для этого конкретного случая:
<!--IOS RELEASE--> <PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net7.0-ios|AnyCPU'"> <!--to be able to use some EF core methods--> <MtouchExtraArgs>--interpreter</MtouchExtraArgs> <UseInterpreter>True</UseInterpreter> <!--your codesign parameters will go below--> </PropertyGroup>
Приложения, скомпилированные с такими настройками, уже были одобрены для AppStore, и о влиянии на производительность не сообщалось.
Я надеюсь, что вы найдете это полезным, пожалуйста, не стесняйтесь задавать вопросы, если они есть: @nickkovalsky
Исходный код: https://github.com/taublast/MauiEF