Если вы, как и я, уже являетесь большим поклонником Microsoft Entity Framework и хотели бы начать использовать его локально в своем мобильном приложении, с появлением .Net Maui на рынке, это стало возможным.

Небольшое замечание: для ускорения времени запуска мобильного приложения может быть лучше хранить данные, используемые во время загрузки, в локальном хранилище мобильного устройства в форме json. Но, когда дело дойдет до работы с большими локальными данными, использования фильтров, сортировки и т. д., EF определенно подойдет идеально.

Цель этой статьи — помочь избежать хлопот, связанных с поиском различных решений небольших проблем при реализации production-ready мобильной локальной базы данных и создании для нее миграций на компьютерах Windows и Mac. Исходный код примера приложения доступен по ссылке, указанной в конце. Как вы сможете увидеть, это будет типовой шаблон приложения Maui с добавленной логикой базы данных EF.

Проверенным стандартом базы данных мобильного клиента является SQLite. Ну что ж, отправимся за пакетом nuget Microsoft.EntityFrameworkCore.Sqlite, также установим SQLitePCLRaw.bundle_e_sqlite3 для нативной поддержки sqlite. Для создания миграций EF нам также потребуется установить Microsoft.EntityFrameworkCore.Tools.

Для тех, кто не уверен, для чего нужны миграции, в основном, это код, который инструктирует EF о структуре базы данных для создания и использования. Важным преимуществом здесь является то, что когда вы меняете свои модели, вам лишь нужно будет сгенерировать утилитой дополнительные миграции для EF, чтобы автоматически изменить соответствующим образом существующую базу данных, добавляя-удаляя таблицы, столбцы и т. д.

Теперь, нюанс в том, что мы не можем установить эти нагеты в наш проект maui, потому что, как только мы попытаемся создать миграцию, то получим ошибку:

Startup project 'MauiEF.Client' targets platform 'Android'. The Entity Framework Core Package Manager Console Tools don't support this platform. See https://aka.ms/efcore-docs-pmc-tfms for more information.

.. поэтому нужно будет найти способ это обойти. Поскольку мы уже знаем, что инструменты проектирования EF имеют неподдерживаемые платформы, нам нужно будет создать специальный проект «Мигратор», который будет напрямую запускаться EF и будет таргетить чистый .net 7.0, тогда сможем создавать миграции на своем компьютере, Windows или Mac.

И проект «Мигратор», и наш проект-клиент должны будут иметь доступ к базе данных, поэтому мы переместим весь наш код, связанный с ней, в отдельный общий проект. Тогда структура решения будет выглядеть так:

Проект 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 следующим образом:

Обратите внимание на два конструктора: один для средства миграции 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.

Откройте Консоль диспетчера пакетов: View->Other Windows->Package Manager Console. У меня нет VS на русском языке, подозреваю, что там это будет называться Вид->Другие окна->Консоль диспетчера пакетов.

Установите проект по умолчанию (default project) как Shared, EF будет искать модель контекста внутри и добавит в этот проект миграции.

Введите следующую команду, чтобы создать начальную миграцию:

add-migration Initial -Context MauiEF.Shared.Services.LocalDatabase -Verbose

Следующий метод, использующий командной строку, используем в 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 предупредит вас при создании миграции с чем-то вроде «Warning data will be lost», так что управление данными в старых версиях приложения находится под вашим контролем.

Чтобы создать новую миграцию, просто создайте для нее уникальное имя (пример для Visual Studio для Windows):

add-migration Change1 -Context MauiEF.Shared.Services.LocalDatabase -Verbose

Теперь мы можем скомпилировать наше приложение и запустить его, созданные пользователем данные будут сохраняться локально на мобильном устройстве. Операции для работы с базой данных мы будем запускать асинхронно, не блокируя поток пользовательского интерфейса.

Напоследок, небольшое примечание: когда вы скомпилируете ваше приложение EF Maui под iOS в Release-конфигурации, оно может вылететь на реальном устройстве, из-за того, что iOS AOT компиляция не поддерживает некоторые методы EF. Я бы не хотел быть здесь вдаваться в детали, вы можете прочитать об этом подробнее, но решение состоит в том, чтобы добавить немного специй в файл проекта клиента для этого конкретного случая:

<!--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, влияние на производительность не ощущается.

Надеюсь, эта небольшая статья окажется для вас полезной!

Исходный код примера с вариантом этой статьи на английском языке доступен на гитхаб.

Комментарии (1)


  1. Dansoid
    14.01.2023 21:03

    Какой-то ужас при использовании асинхронности. Получается окно апдейтнут из другого потока? А что будет если после появления окна кто-то что-то нажмет и оно полезет в базу? Думаю свалится сразу.