c# - 在 EF Core 3.0 中针对 SQLite 数据库调用 Database.MigrateAsync 时出现错误
问题描述
我正在开发一个 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)
解决方案
我知道这是一个非常古老的问题,但我遇到了同样的问题并认为值得更新,因为这是我通过谷歌找到的第一个结果。
使用以下方法创建数据库时会出现此错误:
context.Database.EnsureCreated();
如果最初使用它来创建数据库,则永远不会创建 __EFMigrationsHistory 表,并且会阻止迁移工作。
如果您有完整的迁移历史记录,则以下内容可能不适合您。EF 将尝试并应用所有迁移,包括那些可能已经存在的表。如果不使用正确的迁移手动填充 __EFMigrationsHistory 表以匹配状态 - 您可能必须参考下面的最终答案。
根据此问题的答案,您可以执行以下操作来纠正此问题:
创建 __EFMigrationsHistory 表后,应该运行其余的更新。
CREATE TABLE `__EFMigrationsHistory` ( `MigrationId` nvarchar(150) NOT NULL, `ProductVersion` nvarchar(32) NOT NULL, PRIMARY KEY (`MigrationId`) );
或者,生成迁移脚本并在包管理器控制台中使用此命令手动应用到数据库:
Script-Migration
如果需要生成所有脚本,可以使用这个命令:
Script-Migration -from 0
除了这个答案,如果您已经有几个迁移因为表和列已经存在而不适用,您可以执行以下操作:
(警告,这将删除现有数据库及其所有数据 - 这是最后一个选项 - 不是第一个)
if (!context.Database.GetAppliedMigrations().Any())
context.Database.EnsureDeleted()
然后跟进:
content.Database.Migrate()
这将确保如果不存在迁移,则删除数据库并使用迁移表重新初始化,并应用所有挂起的迁移。
推荐阅读
- asp.net - 字体真棒图标在 .NET/MVC 应用程序中不起作用
- c++ - 在 macOS high sierra 中调试使用 Bazel 构建的 C++ 项目时断点不起作用
- css - 如何为占位符设置颜色?
- sql - plpgsql 错误:“:”处或附近的语法错误
- vb.net - VB.NET 如何在任务栏中隐藏输入框?
- java - 将 Java 流拆分为两个惰性流,无需终端操作
- php - 如何使用 php 将日期差异的结果插入 mysql 数据库?
- javascript - 有人可以帮我理解这段代码吗?
- reactjs - 设置 react-select 的输入字段的字体系列
- python - 如何在 Python 中自动弯曲鼠标移动(使用 pyautogui 或任何其他类似模块)