欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

EntityFrameworkCore 运行时数据迁移

程序员文章站 2023-12-28 12:04:22
...

EntityFrameworkCore(以后简称EFCore)是.net core的一个orm框架,以前在.net framework中使用时候利用code first可以在程序运行的时候自动迁移数据库,更新数据库表结构,但在.net core中好像没有这个功能,反正看了例子查资料都没找到,于是决定自己实现这个机制

实际上EFCore数据库迁移是利用一个迁移历史版本库存下当前模型版本的快照,需要迁移的时候只要根据当前模型新生成一个快照进行比对,发现不同就生成迁移代码,迁移后再将新的版本存起来

在EFCore设计时的类库中有一个IMigrationsSqlGenerator接口用于创建迁移的Sql脚本,IMigrationsModelDiffer接口用于模型的迁移比对,由于是设计时执行的所以要用DesignTimeServicesBuilder来获取相应接口的实例

每次迁移生成的快照实际上是动态生成的类,所以要用动态编辑机制CSharpCompilation和CSharpCompilationOptions

直接把关键代码贴上来了

        private async Task RunUpdateAsync()
        {
            // 创建一个DbContext,具体方法怎样都行
            var _dbContext = await CreateDbContext();

            IModel lastModel = null;
            try
            {
                // 这里用一个表来存迁移记录,迁移时将上一个版本取出来
                var lastMigration = _dbContext.Migrations
                    .OrderByDescending(e => e.MigrationTime)
                    .OrderByDescending(e => e.Id) // mysql下自动生成的时间日期字段时间精度为秒
                    .FirstOrDefault();
                // 上一个版本生成的类型文本是以base64编码存储的,取出来还原成DbContext模型对象
                lastModel = lastMigration == null ? null : (CreateModelSnapshot(Encoding.UTF8.GetString(Convert.FromBase64String(lastMigration.SnapshotDefine))).Result?.Model);
            }
            catch (DbException) { }

            // 需要从历史版本库中取出快照定义,反序列化成类型 GetDifferences(快照模型, context.Model);
            // 实际情况下要传入历史快照
            var modelDiffer = _dbContext.Context
                .GetInfrastructure()
                .GetService<IMigrationsModelDiffer>();
            var hasDiffer = modelDiffer.HasDifferences(lastModel, _dbContext.Context.Model);

            if (hasDiffer)
            {
                // 用 IMigrationsModelDiffer 的 GetDifferences 方法获取迁移的操作对象
                var upOperations = modelDiffer.GetDifferences(lastModel, _dbContext.Context.Model);

                using (var trans = _dbContext.Context.Database.BeginTransaction())
                {
                    try
                    {
                        // 通过 IMigrationsSqlGenerator 将操作迁移操作对象生成迁移的sql脚本,并执行
                        _dbContext.Context.GetInfrastructure()
                            .GetRequiredService<IMigrationsSqlGenerator>()
                            .Generate(upOperations, _dbContext.Context.Model)
                            .ToList()
                            .ForEach(cmd => _dbContext.Context.Database.ExecuteSqlCommand(cmd.CommandText));

                        _dbContext.Context.Database.CommitTransaction();
                    }
                    catch (DbException ex)
                    {
                        _dbContext.Context.Database.RollbackTransaction();
                        throw ex;
                    }

                    // 生成新的快照,存起来
                    var snapshotCode = new DesignTimeServicesBuilder(typeof(ModuleDbContext).Assembly, new ModuleDbOperationReporter(), new string[0])
                        .Build((DbContext)_dbContext)
                        .GetService<IMigrationsCodeGenerator>()
                        .GenerateSnapshot(ContextAssembly, typeof(ModuleDbContext), SnapshotName, _dbContext.Context.Model);

                    _dbContext.Migrations.Add(new MigrationRecord()
                    {
                        SnapshotDefine = Convert.ToBase64String(Encoding.UTF8.GetBytes(snapshotCode)),
                        MigrationTime = DateTime.Now
                    });

                    _dbContext.Context.SaveChanges();
                }
            }
        }

        private Task<ModelSnapshot> CreateModelSnapshot(string codedefine)
        {
            // 生成快照,需要存到数据库*更新版本用
            var references = typeof(ModuleDbContext).Assembly
                .GetReferencedAssemblies()
                .Select(e => MetadataReference.CreateFromFile(Assembly.Load(e).Location))
                .Union(new MetadataReference[]
                {
                    MetadataReference.CreateFromFile(Assembly.Load("netstandard").Location),
                    MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location),
                    MetadataReference.CreateFromFile(typeof(Object).Assembly.Location),
                    MetadataReference.CreateFromFile(typeof(ModuleDbContext).Assembly.Location)
                });

            var compilation = CSharpCompilation.Create(ContextAssembly)
                .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
                .AddReferences(references)
                .AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(codedefine));

            return Task.Run(() =>
            {
                using (var stream = new MemoryStream())
                {
                    var compileResult = compilation.Emit(stream);
                    return compileResult.Success
                        ? Assembly.Load(stream.GetBuffer()).CreateInstance(ContextAssembly + "." + SnapshotName) as ModelSnapshot
                        : null;
                }
            });
        }

只要保证需要迁移的类型在程序集里,而且动态编译时候程序及被加到编译的MetadataReference里,最后别忘了引用设计时相应类库

上一篇:

下一篇: