Я занимаюсь разработкой приложения Xamarin.Forms, которое использует базу данных SQLite, доступ к которой осуществляется через Entity Framework Core (версия 3, в настоящее время я использую предварительную версию, потому что пропустил официальную версию 3.0, скоро обновлю).
Каждый раз, когда приложение запускается, вскоре после создания экземпляра класса ApplicationContext, я вызываю Database.MigrateAsync, чтобы убедиться, что база данных обновлена до последней версии модели. Я знаю, что мог бы значительно улучшить производительность, не вызывая MigrateAsync при каждом запуске приложения, а только после первого запуска новой версии, но здесь дело не в этом.
Проблема в том, что в очень небольшом процентемои пользователи (примерно 0,5%) MigrateAsync выдает исключение типа Microsoft.Data.Sqlite.SqliteException: SQLite Error 1: 'no such table: __EFMigrationsHistory'
(сообщается с помощью функциональности VS App Center Crash Report). Теперь я знаю, что __EFMigrationHistory - это таблица, используемая EF Core для отслеживания примененных миграций, и первый раз, когда создается файл базы данных, должен быть создан самой платформой.
Итак, как такая ошибка, как этаэто вообще возможно? Поскольку, если таблица не существует, ее следует создать, а если она существует, ее следует использовать для определения того, какие миграции уже применены. Может быть, EF Core пытается создать таблицу, по каким-то неизвестным причинам происходит сбой, а затем пытается прочитать ее, выдав это исключение? Но что может привести к сбою при создании таблицы? И что я могу сделать в этом случае?
В настоящее время я не могу сказать, генерируется ли это исключение при первом запуске приложения (когда миграция никогда не должна была применяться) или впоследствии, потому что яВ настоящее время не отслеживают такого рода информацию. Кроме того, я никогда не испытывал такого исключения во время разработки. Что я могу сказать, исключение фиксируется в блоке перехвата, передается в App Center, а затем повторно генерируется, что, кстати, должно вызвать сбой, который, в свою очередь, кажется, не происходит, согласно отчетам App Center. Это может быть связано с тем, что исключение выдается в асинхронном коде, который, возможно, не ожидается или обрабатывается вызывающей стороной (код инициализации вызывается Autofac после создания экземпляра моего класса ApplicationContext), но это не является существенным для моего вопроса.
Это код, где я вызываю MigrateAsync:
public class ApplicationContext : DbContext
{
// Other code...
public async Task<ApplicationContext> Initialize()
{
if (IsInitialized) return this;
IsInitialized = true;
try
{
await Database.MigrateAsync();
Analytics.TrackEvent("Migration OK");
}
catch (Exception ex)
{
Crashes.TrackError(ex, new Dictionary<string, string> { { Globals.CrashProperty.Context.ToString(), "Migration" } });
throw;
}
return this;
}
}
Вот код, где я регистрируюсь в Autofac для создания единственного экземпляра ApplicationContext:
public class AppModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
// Other services registrations…
builder.Register(ctx => new ApplicationContext(ctx.Resolve<IFileService>(
new TypedParameter(typeof(string), Globals.DbFileName),
new TypedParameter(typeof(FilePathType), FilePathType.AppFolder)).FilePath, ctx.Resolve<IResourceContainer>())).SingleInstance();
base.Load(builder);
}
protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
{
registration.Activated += async (_, args) =>
{
if (args.Instance is ApplicationContext context && !context.IsInitialized)
{
await context.Initialize();
}
};
base.AttachToComponentRegistration(componentRegistry, registration);
}
}
}
А вот трассировка стека исключительной ситуации, сообщенная VS App Center:
Microsoft.Data.Sqlite
SqliteException.ThrowExceptionForRC (System.Int32 rc, SQLitePCL.sqlite3 db)
Microsoft.Data.Sqlite
SqliteCommand+<PrepareAndEnumerateStatements>d__62.MoveNext ()
Microsoft.Data.Sqlite
SqliteCommand.ExecuteReader (System.Data.CommandBehavior behavior)
Microsoft.Data.Sqlite
SqliteCommand.ExecuteReader ()
Microsoft.Data.Sqlite
SqliteCommand.ExecuteNonQuery ()
System.Data.Common
DbCommand.ExecuteNonQueryAsync (System.Threading.CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.Storage.Internal
RelationalCommand.ExecuteAsync (Microsoft.EntityFrameworkCore.Storage.IRelationalConnection connection, Microsoft.EntityFrameworkCore.Diagnostics.DbCommandMethod executeMethod, System.Collections.Generic.IReadOnlyDictionary`2[TKey,TValue] parameterValues, System.Threading.CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.Migrations
MigrationCommand.ExecuteNonQueryAsync (Microsoft.EntityFrameworkCore.Storage.IRelationalConnection connection, System.Collections.Generic.IReadOnlyDictionary`2[TKey,TValue] parameterValues, System.Threading.CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.Migrations.Internal
MigrationCommandExecutor.ExecuteNonQueryAsync (System.Collections.Generic.IEnumerable`1[T] migrationCommands, Microsoft.EntityFrameworkCore.Storage.IRelationalConnection connection, System.Threading.CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.Migrations.Internal
Migrator.MigrateAsync (System.String targetMigration, System.Threading.CancellationToken cancellationToken)