EFCore高级Saas系统下一个DbContext如何支持多数据库迁移
EFCore高级玩法单DbContext支持多数据库迁移
前言
随着系统的不断开发和迭代默认的efcore功能十分强大,但是随着Saas系统的引进efcore基于表字段的多租户模式已经非常完美了,但是基于数据库的多租户也是可以用的,但是也存在缺点,缺点就是没有办法支持不同数据库,migration support multi database provider with single dbcontext,本人不才,查询了一下,官方文档只说明了dbcontext的迁移如何实现多数据源,但是缺不是单个dbcontext,这个就让人很头疼。所以秉着尝试一下的原则进行了这篇博客的编写,因为本人只有mmsql和mysql所以这次就用这两个数据库来做测试
广告时间
本人开发了一款efcore的分表分库读写分离组件
https://github.com/dotnetcore/sharding-core
希望有喜欢的小伙伴给我点点star谢谢
那么废话不多说我们马上开始migration support multi database provider with single dbcontext
新建项目
1.按装依赖

2.新建一个User类
[Table(nameof(User))]
public class User
{
public string UserId { get; set; }
public string UserName { get; set; }
}
3.创建DbContext
public class MyDbContext:DbContext
{
public DbSet<User> Users { get; set; }
public MyDbContext(DbContextOptions<MyDbContext> options):base(options)
{
}
4.StartUp配置
var provider = builder.Configuration.GetValue("Provider", "UnKnown");
//Add-Migration InitialCreate -Context MyDbContext -OutputDir Migrations\SqlServer -Args "--provider SqlServer"
//Add-Migration InitialCreate -Context MyDbContext -OutputDir Migrations\MySql -Args "--provider MySql"
builder.Services.AddDbContext<MyDbContext>(options =>
{
_ = provider switch
{
"MySql" => options.UseMySql("server=127.0.0.1;port=3306;database=DBMultiDataBase;userid=root;password=L6yBtV6qNENrwBy7;", new MySqlServerVersion(new Version())),
"SqlServer" => options.UseSqlServer("Data Source=localhost;Initial Catalog=DBMultiDataBase;Integrated Security=True;"),
_ => throw new Exception($"Unsupported provider: {provider}")
};
});
迁移区分数据库
新建一个迁移命名空间提供者
public interface IMigrationNamespace
{
string GetNamespace();
}
mysql和sqlserver的实现分别是项目名称迁移文件夹
public class MySqlMigrationNamespace:IMigrationNamespace
{
public string GetNamespace()
{
return "EFCoreMigrateMultiDatabase.Migrations.MySql";
}
}
public class SqlServerMigrationNamespace:IMigrationNamespace
{
public string GetNamespace()
{
return "EFCoreMigrateMultiDatabase.Migrations.SqlServer";
}
}
efcore扩展
添加efcore扩展
public class MigrationNamespaceExtension : IDbContextOptionsExtension
{
public IMigrationNamespace MigrationNamespace { get; }
public MigrationNamespaceExtension(IMigrationNamespace migrationNamespace)
{
MigrationNamespace = migrationNamespace;
}
public void ApplyServices(IServiceCollection services)
{
services.AddSingleton<IMigrationNamespace>(sp => MigrationNamespace);
}
public void Validate(IDbContextOptions options)
{
}
public DbContextOptionsExtensionInfo Info => new MigrationNamespaceExtensionInfo(this);
private class MigrationNamespaceExtensionInfo : DbContextOptionsExtensionInfo
{
private readonly MigrationNamespaceExtension _migrationNamespaceExtension;
public MigrationNamespaceExtensionInfo(IDbContextOptionsExtension extension) : base(extension)
{
_migrationNamespaceExtension = (MigrationNamespaceExtension)extension;
}
public override int GetServiceProviderHashCode() => _migrationNamespaceExtension.MigrationNamespace.GetNamespace().GetHashCode();
public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) => true;
public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
{
}
public override bool IsDatabaseProvider => false;
public override string LogFragment => "MigrationNamespaceExtension";
}
}
重写MigrationsAssembly支持多数据库
public class EFCoreMultiDatabaseMigrationsAssembly: IMigrationsAssembly
{
public string MigrationNamespace { get; }
private readonly IMigrationsIdGenerator _idGenerator;
private readonly IDiagnosticsLogger<DbLoggerCategory.Migrations> _logger;
private IReadOnlyDictionary<string, TypeInfo>? _migrations;
private ModelSnapshot? _modelSnapshot;
private readonly Type _contextType;
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public EFCoreMultiDatabaseMigrationsAssembly(
IMigrationNamespace migrationNamespace,
ICurrentDbContext currentContext,
IDbContextOptions options,
IMigrationsIdGenerator idGenerator,
IDiagnosticsLogger<DbLoggerCategory.Migrations> logger)
{
_contextType = currentContext.Context.GetType();
var assemblyName = RelationalOptionsExtension.Extract(options)?.MigrationsAssembly;
Assembly = assemblyName == null
? _contextType.Assembly
: Assembly.Load(new AssemblyName(assemblyName));
MigrationNamespace = migrationNamespace.GetNamespace();
_idGenerator = idGenerator;
_logger = logger;
}
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual IReadOnlyDictionary<string, TypeInfo> Migrations
{
get
{
IReadOnlyDictionary<string, TypeInfo> Create()
{
var result = new SortedList<string, TypeInfo>();
var items
= from t in Assembly.GetConstructibleTypes()
where t.IsSubclassOf(typeof(Migration))&& print(t)
&& t.Namespace.Equals(MigrationNamespace)
&& t.GetCustomAttribute<DbContextAttribute>()?.ContextType == _contextType
let id = t.GetCustomAttribute<MigrationAttribute>()?.Id
orderby id
select (id, t);
Console.WriteLine("Migrations:" + items.Count());
foreach (var (id, t) in items)
{
if (id == null)
{
_logger.MigrationAttributeMissingWarning(t);
continue;
}
result.Add(id, t);
}
return result;
}
return _migrations ??= Create();
}
}
private bool print(TypeInfo t)
{
Console.WriteLine(MigrationNamespace);
Console.WriteLine(t.Namespace);
return true;
}
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual ModelSnapshot? ModelSnapshot
=> GetMod();
private ModelSnapshot GetMod()
{
Console.WriteLine("_modelSnapshot:"+ _modelSnapshot);
if (_modelSnapshot == null)
{
Console.WriteLine("_modelSnapshot:null");
_modelSnapshot = (from t in Assembly.GetConstructibleTypes()
where t.IsSubclassOf(typeof(ModelSnapshot)) && print(t)
&& MigrationNamespace.Equals(t?.Namespace)
&& t.GetCustomAttribute<DbContextAttribute>()?.ContextType == _contextType
select (ModelSnapshot)Activator.CreateInstance(t.AsType())!)
.FirstOrDefault();
Console.WriteLine("_modelSnapshot:" + _modelSnapshot);
}
return _modelSnapshot;
}
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual Assembly Assembly { get; }
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual string? FindMigrationId(string nameOrId)
=> Migrations.Keys
.Where(
_idGenerator.IsValidId(nameOrId)
// ReSharper disable once ImplicitlyCapturedClosure
? id => string.Equals(id, nameOrId, StringComparison.OrdinalIgnoreCase)
: id => string.Equals(_idGenerator.GetName(id), nameOrId, StringComparison.OrdinalIgnoreCase))
.FirstOrDefault();
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual Migration CreateMigration(TypeInfo migrationClass, string activeProvider)
{
Console.WriteLine(migrationClass.FullName);
var migration = (Migration)Activator.CreateInstance(migrationClass.AsType())!;
migration.ActiveProvider = activeProvider;
return migration;
}
}
编写startup
参考 https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/providers?tabs=vs
//Add-Migration InitialCreate -Context MyDbContext -OutputDir Migrations\SqlServer -Args "--provider SqlServer"
//Add-Migration InitialCreate -Context MyDbContext -OutputDir Migrations\MySql -Args "--provider MySql"
//update-database -Args "--provider MySql"
//update-database -Args "--provider SqlServer"
builder.Services.AddDbContext<MyDbContext>(options =>
{
options.ReplaceService<IMigrationsAssembly, EFCoreMultiDatabaseMigrationsAssembly>();
_ = provider switch
{
"MySql" => options.UseMySql("server=127.0.0.1;port=3306;database=DBMultiDataBase;userid=root;password=L6yBtV6qNENrwBy7;", new MySqlServerVersion(new Version()))
.UseMigrationNamespace(new MySqlMigrationNamespace()),
"SqlServer" => options.UseSqlServer("Data Source=localhost;Initial Catalog=DBMultiDataBase;Integrated Security=True;")
.UseMigrationNamespace(new SqlServerMigrationNamespace()),
_ => throw new Exception($"Unsupported provider: {provider}")
};
});
到此为止我这边想我们应该已经实现了把,但是如果我们分别执行两个迁移命令会导致前一个迁移命令被覆盖掉,经过一整个下午的debug调试最后发现是因为在迁移脚本生成写入文件的时候会判断当前DbContext'的ModelSnapshot,同一个dbcontext生成的文件是一样的,所以我们这边有两个选择
- 1.让生成的文件名不一样
- 2.让ModelSnapshot不进行深度查询只在当前目录下处理
这边选了第二种
public class MyMigrationsScaffolder: MigrationsScaffolder
{
private readonly Type _contextType;
public MyMigrationsScaffolder(MigrationsScaffolderDependencies dependencies) : base(dependencies)
{
_contextType = dependencies.CurrentContext.Context.GetType();
}
protected override string GetDirectory(string projectDir, string? siblingFileName, string subnamespace)
{
var defaultDirectory = Path.Combine(projectDir, Path.Combine(subnamespace.Split('.')));
if (siblingFileName != null)
{
if (!siblingFileName.StartsWith(_contextType.Name + "ModelSnapshot."))
{
var siblingPath = TryGetProjectFile(projectDir, siblingFileName);
if (siblingPath != null)
{
var lastDirectory = Path.GetDirectoryName(siblingPath)!;
if (!defaultDirectory.Equals(lastDirectory, StringComparison.OrdinalIgnoreCase))
{
Dependencies.OperationReporter.WriteVerbose(DesignStrings.ReusingNamespace(siblingFileName));
return lastDirectory;
}
}
}
}
return defaultDirectory;
}
}
添加designservices
public class MyMigrationDesignTimeServices: IDesignTimeServices
{
public void ConfigureDesignTimeServices(IServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<IMigrationsScaffolder, MyMigrationsScaffolder>();
}
}
迁移
分别运行两个迁移命令

运行更新数据库命令

记得我们需要在参数里面添加选项
下期预告
下期我们将实现efcore在Saas系统下的多租户+code-first(迁移)+分表+分库+读写分离+动态分表+动态分库+动态读写分离+动态添加多租户 全程零sql脚本的解决方案
是不是buffer叠满
最后的最后
附上demo:EFCoreMigrateMultiDatabase https://github.com/xuejmnet/EFCoreMigrateMultiDatabase
您都看到这边了确定不点个star或者赞吗,一款.Net不得不学的分库分表解决方案,简单理解为sharding-jdbc在.net中的实现并且支持更多特性和更优秀的数据聚合,拥有原生性能的97%,并且无业务侵入性,支持未分片的所有efcore原生查询
- github地址 https://github.com/xuejmnet/sharding-core
- gitee地址 https://gitee.com/dotnetchina/sharding-core
EFCore高级Saas系统下一个DbContext如何支持多数据库迁移的更多相关文章
- efcore在Saas系统下多租户零脚本分表分库读写分离解决方案
efcore在Saas系统下多租户零脚本分表分库读写分离解决方案 ## 介绍 本文ShardinfCore版本x.6.0.20+ 本期主角: - [`ShardingCore`](https://gi ...
- Linux系统下授权MySQL账户访问指定数据库和数据库操作
Linux系统下授权MySQL账户访问指定数据库 需求: 1.在MySQL中创建数据库mydata 2.新建MySQL账户admin密码123456 3.赋予账户admin对数据库mydata具有完全 ...
- Linux系统下 解决Qt5无法连接MySQL数据库的方法
Linux平台下解决Qt5连接mysql数据库的问题:输入sudo apt-get install libqt5sql5-mysql解决,这种方法只能解决Qt是用sudo apt-get instal ...
- Windows系统下安装MySQL 8.0.11数据库
MySQL数据库是常用的数据库之一,而且该数据库开源免费,所以很多公司在使用.本文记录如何在Windows系统下安装MySQL数据库,本次安装的版本号为8.0.11,这个版本是当前的最新版本,据宣传, ...
- Linux系统下一个冷门的RAID卡ioc0及其监控mpt-status
新接手了一台Linux服务器,准备检查是否有配置RAID.参考(http://mip.0834jl.com) 先查看是否有RAID卡: 复制代码 代码如下: # dmesg|grep -i raid ...
- MacOS系统下简单安装以及配置MongoDB数据库(一)
最近写了一个用node来操作MongoDB完成增.删.改.查.排序.分页功能的示例,并且已经放在了服务器上地址:http://39.105.32.180:3333. 项目一共四部分: 1.MacOS下 ...
- Mac系统下安装ipython分别支持python2和python3
操作系统:Mac10.11.5 python2.7.13 python3.6.1 安装python2: brew install python 安装python3: brew install pyth ...
- windows系统下mysql5.5查看和设置数据库编码
1.显示当前编码命令: show variables like 'char%'; 2.设置编码为utf8命令:set names 'utf8';
- 在linux 系统下 使用命令行对mysql 数据库进行操作
1.连接mysql root@test:/home# mysql -uroot -proot <uroot是用户名,proot是密码> 2.查询所有的库 mysql> show da ...
随机推荐
- nova服务的基本使用
创建flavor类型 [root@controller ~]# openstack help flavor create usage: openstack flavor create [-h] [-f ...
- 203. Remove Linked List Elements - LeetCode
Question 203. Remove Linked List Elements Solution 题目大意:从链表中删除给定的数 思路:遍历链表,如果该节点的值等于给的数就删除该节点,注意首节点 ...
- linux篇-linux iptables配置
1 iptables默认系统自带 setup 2重启防火墙 /etc/init.d/iptables restart 3接受端口 Vi /etc/sysconfig/iptables -A INPUT ...
- “摆地摊“都找不到全栈工程师?JNPF帮你分分钟搞定!
大街上捕捉野生程序员 都这样了还找不到全栈工程师 全栈工程师(Full-Stack Engineer)图鉴: 全栈工程师,也叫全端工程师(同时具备前端和后台能力),英文Full Stack deve ...
- PHP时间轴函数
PHP时间轴函数可以更好的去进行用户体验.让用户动态的知道最近是什么时候,而不是死板的datatime去转换成固定的时间. 后续版本会考虑添加这个功能,代码先贴出来. function tranTim ...
- 12.web基础与HTTP协议
web基础与HTTP协议 目录 web基础与HTTP协议 web基础 域名概述 HTML概述 HTML基本标签 HTML语法规则 HTML文件结构 头标签中常用标签 内容标签中常用标签 静态网页与动态 ...
- JS:对象调方法1
找调用者 1.如果有this,就先看this在哪个函数中,就是离this最近的function,没有就是window 2.找到函数后,辨别哪个是调用者 例1: 点击查看代码 function fn() ...
- 教你如何用网页开发APP
用到的工具: HBuilderX app开发版1.首先你得网站必须是上线的,然后明确这一点后,点击打开HBuilderX.在文件里找到新建项目,选择wap2App,将下面信息填写完整,然后创建. 2. ...
- .NET6接入Skywalking链路追踪完整流程
一.Skywalking介绍 Skywalking是一款分布式链路追踪组件,什么是链路追踪? 随着微服务架构的流行,服务按照不同的维度进行拆分,一次请求往往需要涉及到多个服务.互联网应用构建在不同的软 ...
- RPA应用场景-日终清算操作
场景概述 日终清算操作 所涉系统名称 登记过户管理系统(TA),投资交易系统(032) 人工操作(时间/次) 60-80分钟 所涉人工数量 2 操作频率 每日 场景流程 这两个流程一般在晚上8-9点开 ...