【发布时间】:2020-03-04 10:13:25
【问题描述】:
我正在开发一个 Xamarin.Forms 应用程序,它使用通过 Entity Framework Core 访问的 SQLite 数据库(版本 3,我目前正在使用预发布版本,因为我错过了官方 3.0 的发布,我将很快升级)。
每次应用程序启动时,在 ApplicationContext 类被实例化后不久,我都会调用 Database.MigrateAsync 以确保将数据库升级到模型的最新版本。我知道我可以通过在每次应用启动时不调用 MigrateAsync 来显着提高性能,但只有在新版本首次运行之后才调用,但这不是重点。
问题是,在我的一小部分用户(大约 0.5%)中,MigrateAsync 会引发 Microsoft.Data.Sqlite.SqliteException: SQLite Error 1: 'no such table: __EFMigrationsHistory' 类型的异常(通过 VS App Center 崩溃报告功能报告)。
现在,我知道 __EFMigrationHistory 是 EF Core 用来跟踪应用迁移的表,第一次创建数据库文件应该由框架自己创建。
那么,这样的错误怎么可能发生呢?因为,如果表不存在,则应该创建它,而如果存在,则应该使用它来考虑已经应用了哪些迁移。也许 EF Core 尝试创建表,由于某些未知原因失败,然后尝试读取它并抛出此异常?但是什么会导致它无法创建表呢?在这种情况下我该怎么办?
我目前无法确定此异常是在应用程序第一次运行时引发(此时不应应用迁移)还是随后引发,因为我目前不跟踪此类信息。另外,我在开发过程中从未遇到过这样的异常。 我可以说的是,异常被捕获在一个catch块中,报告给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)
【问题讨论】:
标签: c# sqlite entity-framework-core