首页 > 解决方案 > 在 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)

标签: c#sqliteentity-framework-core

解决方案


我知道这是一个非常古老的问题,但我遇到了同样的问题并认为值得更新,因为这是我通过谷歌找到的第一个结果。

使用以下方法创建数据库时会出现此错误:

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()

这将确保如果不存在迁移,则删除数据库并使用迁移表重新初始化,并应用所有挂起的迁移。


推荐阅读