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里,最后别忘了引用设计时相应类库
推荐阅读
-
EntityFrameworkCore 运行时数据迁移
-
.net core 数据迁移【Microsoft.EntityFrameworkCore.Tools】
-
EntityFrameworkCore5知识点简要 - 删除数据
-
【UWP】自带的EntityFrameWorkCore数据库(新数据库)的配置教程
-
EntityFrameworkCore-EF Core加密存储数据
-
示例:EntityFrameWorkCore迁移添加初始种子数据
-
关于Oracle数据库迁移(跨版本)以及空表不导出
-
用perl做数据库迁移,从MSSQL到MYSQL(-)大概思路_MySQL
-
SqlServer数据库迁移
-
mysql 数据迁移有关问题,所有的.frm文件报错·