ef 数据迁移mysql_使用EntityFrameworkCore来复制,迁移数据库

文中用到的工具已经开源,欢迎点Starhttps://github.com/vincywindy/EntityFrameworkCore.DBCopy​github.com

有时候会遇到不同数据库之前的迁移同步,这是个很大的问题,市面上有很多同步迁移工具,但是不同数据库之间类型同步是一个大的问题,比如同样的bool,Sql server 默认使用bit来存储,而Mysql,Postgresql使用Boolean来存储,这使得用来查询的sql语句不同,

Sqlserver 是

select * from xxx where isxxx=1

Mysql是

select * from xxx where isxxx=true

本身EntityFramework是解决了这个问题的,因为你不需要写rawsql,所有的sql都是由EF去生成的,所以如果你使用ef去连接数据库,那么用ef去复制迁移数据库是再好不过了。

原理

原理很简单,假设要从A数据库迁移到B数据库,那么就要从A数据库->EF读取实体->实体插入B数据库,原理那么简单,但是很多问题需要解决。

排坑首先第一个问题就是阴影属性的问题,官方有文档解释阴影属性​docs.microsoft.com

简单理解就是,当你实体里面有一些字段没有显式声明的时候,EF会创建一个隐式的属性,最常见的就是用来声明外键的id,比如你有个User和Book

public class User

{

public virtual string UserName { get; set; }

public virtual List Books { get; set; }

}

public class Book

{

}

这里Book里面会隐式创建一个UserId外键用来关联User,但是没写,所以EF把它创建成为了隐式属性。我们迁移的构思是数据库A->EF实体->数据库B,那么因为实体里面没有这个UserId,所以迁移到数据库B的时候,这个UserId实际上是被丢失了!因此我们需要把所有的阴影属性都显示的声明,但是实体太多了,不可能一个一个找吧,所以需要借助代码把它们都找出来,代码很简单,就一行。

context.Model.GetEntityTypes().Where(d => !d.IsAbstract()).SelectMany(d => d.GetProperties().Select(dd => new { prop = dd, entity = d })).Where(d => d.prop.IsShadowProperty()).Select(d => d.entity.DisplayName() + "." + d.prop.Name);;

context就是你要用到的Dbcontext,这里会把现有的所有实体的阴影属性都列出来。

2.其次就是迁移文件的建立,项目本身已经有了Migrations文件,同种数据库还好,那么要改成另一种数据库的话,就需要建立新的Migrations官方有给出建议多个提供程序的 EF Core 的迁移​docs.microsoft.com

自行决定需要的方法,这里给出的最简单办法就是,新增一个DbContext继承于你当前的DbContext,而这个Context使用不同的数据库引擎。

public class AAContext : DbContext

{

//假设这是你的默认使用的那个Context,默认使用Sql Server连接 }

public class MysqlAAContext : AAContext

{

//这个Context使用Mysql }

public class PostgresqlAAContext : AAContext

{

//这个Context使用Postgresql }

然后实现IDesignTimeDbContextFactory接口

///Default factory public class AAContextContextFactory : IDesignTimeDbContextFactory

{

public AAContext CreateDbContext(string[] args)

{

var builder = new DbContextOptionsBuilder();

builder.UseSqlServer(connectionString);

return new AAContext(builder.Options);

}

}

///New factory public class MysqlAAContextContextFactory : IDesignTimeDbContextFactory

{

public MysqlAAContext CreateDbContext(string[] args)

{

var builder = new DbContextOptionsBuilder();

builder.UseMySql(connectionString);

return new MysqlAAContext(builder.Options);

}

}

IDesignTimeDbContextFactory是当你执行数据库迁移命令

dotnet ef migrations add xxx

dotnet ef database update xxx

被调用的创建接口,这样你只需要执行类似这样的命令

dotnet ef migrations add xxx --context MysqlAAContext --output-dir Migrations/MySqlMigrations --project xxxEntityFrameworkCore --startup-project xxx

dotnet ef database update --context MysqlAAContext --project xxxEntityFrameworkCore --startup-project xxx

这样就能给不同数据库的迁移建立不同的独立文件

同时,你操作的时候,依旧使用旧的AAContext,不需要改变其他代码,提供DbContextOptionsBuilder的时候,声明使用哪个数据库+连接字符串就好了。

如果你有使用RawSql,或者其他原因,需要改变迁移目标数据库的类型,只需要把代码加到AAContext的OnModelCreating里面

public class AAContext : DbContext

{

protected override void OnModelCreating(ModelBuilder modelBuilder)

{

if (Database.IsNpgsql())

{//这里以Pgsql为例,citext类型在PG里面是一个不区分大小写的varchar,这对你原始数据库的字符集是CI的尤为有用 modelBuilder.HasPostgresExtension("citext");

modelBuilder.Entity().Property(d => d.xxx).HasColumnType("citext");

}

if (Database.IsMySql())

{

xxxx

}

}

}

3.迁移中发生的意外

如果迁移一个很大的数据库的话,那么要处理迁移中发生的意外导致的数据库中断,这里就需要文件来暂时保存从A数据库读取到的数据,然后在从文件里把数据读取并写入到B数据库,保存文件用简单的办法就是使用json来保存,但是一个新问题又来了,json序列号是全体序列化的,比如一个表有上亿行数据,等读取完生成list再序列化成json的话,那么这样内存吃不消。所以必须把json当作一个流去处理,先读取1W条数据,生成json,再读取1W条数据生成json,插入数据也是同理,这里简单的实现了这个功能,就是读取的时候,每次读取5W条数据,序列化为json,追加到对于的文件尾部。

internal static void AddJson(string filename, IList list)

{

var json = JsonConvert.SerializeObject(list, Formatting.None, setting);

File.AppendAllText(filename, json);

}

读取数据库的时候,不可能一次性Tolist,这样数据量大很慢,而且容易吃完内存,必须拿到IEnumerator一个一个去读取,这个很像http://ado.net的datareader

xxx.AsNoTracking().GetEnumerator();

读取json的时候,因为写入json的时候是多个json写入同一个文件,那么读取也就一个一个json读取就好了,以迭代的方式返回

internal static IEnumerable GetJson(string filename, Type type)

{

var serializer = new JsonSerializer();

using (var stringReader = File.OpenText(filename))

using (var jsonReader = new JsonTextReader(stringReader))

{

jsonReader.SupportMultipleContent = true;//这句话非常重要,告诉jsonreader这个文件有多个reader

while (jsonReader.Read())

{

var json = (IList)serializer.Deserialize(jsonReader, type);

foreach (var j in json)

{

yield return j;

}

}

}

}

写入数据库,这里暂时使用了https://entityframework-extensions.net/​entityframework-extensions.net

这个库,这个库不是免费的,但是可以试用,且试用期可以无限延长,这个库的性能确实非常高,等有时间我自己实现一个

迁移

做好了上面几步,就可以开始迁移了,迁移前务必要先把目标数据库的表结构建立,并且禁用所有外键约束和触发器,等迁移完成再重新启用,并且如果有自增列,那么自增的序列也必须更新。

var fromoptionsBuilder = new DbContextOptionsBuilder();

fromoptionsBuilder.UseSqlServer(xxxx);

var tooptionsBuilder = new DbContextOptionsBuilder();

tooptionsBuilder.UseMySql(xxxx);

var fromdboption = fromoptionsBuilder.Options;

var todboption = tooptionsBuilder.Options;

var copy = new DBCopyWorker(fromdboption, todboption);

copy.Copy();

以我写的这个库为例,具体实现请查看源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值