【问题标题】:Erratic error when calling Database.MigrateAsync in EF Core 3.0 against a SQLite database在 EF Core 3.0 中针对 SQLite 数据库调用 Database.MigrateAsync 时出现错误
【发布时间】: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


    【解决方案1】:

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

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

    context.Database.EnsureCreated();
    

    如果最初用于创建数据库,则永远不会创建 __EFMigrationsHistory 表,并且会阻止迁移工作。

    如果您有完整的迁移历史记录,以下内容可能不适合您。 EF 将尝试并应用所有迁移,包括那些可能已经存在的表。如果不使用正确的迁移手动填充 __EFMigrationsHistory 表以匹配状态 - 您可能必须参考下面的最终答案。

    根据this question 的回答,您可以执行以下操作来解决此问题:

    创建 __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()
    

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

    【讨论】:

    • OP 的问题可能是并发问题。如果您想在启动时进行迁移,则需要确保在迁移完成之前不能运行其他任何东西。
    • 感谢@Joel 的回答。我知道 EnsureCreated 和 Migrate 之间的冲突,实际上我没有使用 EnsureCreated。我的应用程序的问题要简单得多,这与我注册应用程序以打开多种文件(不仅是 sqlite 文件)的事实有关。所以一些用户试图使用我的应用程序打开根本不是数据库的文件,这导致了错误。事实上,有时会报告更合理的错误“文件不是数据库”,但其他时候有关迁移的错误让我感到困惑。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-08
    • 1970-01-01
    • 2012-10-30
    • 1970-01-01
    • 2016-11-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多