EFCore高级Saas系统下单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.按装依赖
46e2f1d0817c39a35b137cabd00bc338.png

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 == _contextTypelet id = t.GetCustomAttribute<MigrationAttribute>()?.Idorderby idselect (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 == _contextTypeselect (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>();}}

迁移

分别运行两个迁移命令
4590d2d6a679b25f5d6942acdeb11df2.png
运行更新数据库命令
9fa584ab2990d23b58158d1bbf5d7e37.png
记得我们需要在参数里面添加选项

下期预告

下期我们将实现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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/284445.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

51. Python 数据处理(2)

1.Python 修改excel文件import xlrd import xlutils.copy excelr xlrd.open_workbook("hello.xlsx") excelw xlutils.copy.copy(excelr) sheet1 excelw.get_sheet(0) sheet1.write(3, 5, "xlutils.copy test test") excelw.save("hello.xlsx"…

人工智能十大流行算法

导读&#xff1a;本文为有志于成为数据科学家或对此感兴趣的读者们介绍最流行的机器学习算法。 作者&#xff1a;Fahim ul Haq 译者&#xff1a;刘志勇&#xff0c;策划&#xff1a;赵钰莹 来源&#xff1a;InfoQ&#xff08;ID&#xff1a;infoqchina&#xff09; 机器学习是…

Win7+Win10双系统安装全攻略

安装双系统,不仅能给你非凡的体验,还可以满足工作中因系统版本,兼容性,处理器等原因带来的不便。本文讲解Win7+Win10双系统安装全攻略,亲测可用。 1. 硬盘分区 本文讲解利用固态硬盘+机械硬盘的分区方式。 固态硬盘:为了绝对提高系统运行的速度,将固态硬盘作为双系统的…

聊聊研发团队中的“人”

大家好&#xff0c;我是Z哥。汉字博大精深&#xff0c;很多时候我们可以通过拆字来更形象地理解一个词的含义。比如“团队”这个词的两个字"团"和“队”单独看也都是表示一种由多人组成的组织。再做一下拆字就是“口”“才”和“耳”“人”。前者表示一个人才如果没有…

[转]【分布式系统】唯一ID生成策略总结

文章目录 全局唯一id介绍 全局唯一id特点:常见全局唯一id生成策略 1、数据库自增长序列或字段生成id 2、UUID 3、Redis生成ID 4、zookeeper生成ID 5、Twitter的snowflake算法全局唯一id介绍 系统唯一id是我们在设计阶段常常遇到的问题。在复杂的分布式系统中&#…

超全的开源Winform UI库,满足你的一切桌面开发需求!

本文有dotnet9站长整理 网址&#xff1a;https://dotnet9.com/本站曾介绍过一款Winform开源控件库HZHControls&#xff0c;Winform在大家心中的地位还是挺高的&#xff0c;今天小编再分享一款新鲜出炉的 Winform UI库——SunnyUI&#xff0c;一起跟 Dotnet9 往下看吧。项目名称…

告别国外 IDE,阿里 蚂蚁自研 IDE 研发框架 OpenSumi 正式开源

经历近 3 年时间&#xff0c;在阿里集团及蚂蚁集团共建小组的努力下&#xff0c;OpenSumi 作为国内首个强定制性、高性能&#xff0c;兼容 VS Code 插件体系的 IDE 研发框架&#xff0c;今天正式对外开源。 一 OpenSumi 是什么&#xff1f; OpenSumi 是一款面向垂直领域&#…

window-memcache技术随笔

memcached.exe软件放置到非中文,非空格的目录,把MSVCR71.DLL文件放在memcached.exe同目录下启动,控制面板中打开window功能-Telnet客户端memcache服务方法一:管理员身份打开黑窗口 d:(mem的所在盘)cd memmemcached.exe -p 11211方法二: 安装为Windows的系统服务memcached.exe -…

将不确定变为确定~老赵写的CodeTimer是代码性能测试的利器

首先&#xff0c;非常感谢赵老大的CodeTimer&#xff0c;它让我们更好的了解到代码执行的性能&#xff0c;从而可以让我们从性能的角度来考虑问题&#xff0c;有些东西可能我们认为是这样的&#xff0c;但经理测试并非如何&#xff0c;这正应了我之前的那名话&#xff1a;“机器…

聊聊 C++ 中的几种智能指针(下)

一&#xff1a;背景 上一篇我们聊到了C 的 auto_ptr &#xff0c;有朋友说已经在 C 17 中被弃用了&#xff0c;感谢朋友提醒&#xff0c;今天我们来聊一下 C 11 中引入的几个智能指针。unique_ptrshared_ptrweak_ptr看看它们都怎么玩。二&#xff1a;三大智能指针详解 1. uniq…

iOS回顾笔记( 02 ) -- 由九宫格布局引发的一系列“惨案”

iOS回顾笔记&#xff08; 02 &#xff09; -- 由九宫格布局引发的一系列“惨案” 前言&#xff08;扯几句淡先&#xff09; 回顾到学习UI过程中的九宫格布局时&#xff0c;发现当时学的东西真是不少。 这个阶段最大的特点就是&#xff1a;知识点繁多且琐碎。 我们的目标就是要将…

【GlobalMapper精品教程】007:如何加载谷歌卫星影像?

“Global Mapper支持所有OGC标准数据源类型,例如用于流式栅格地图的WMS / WMTS,用于矢量数据集的WFS和用于为指定区域下载单个数据文件的WCS。预先切片的图像和地形数据集也可以使用OSM(OpenStreetMaps)、TMS(Tiled Map Service)和Google Maps瓦片架构支持。您只需要选择适当…

Mysql清空表(truncate)与删除表中数据(delete)的区别

2019独角兽企业重金招聘Python工程师标准>>> 为某基于wordpress搭建的博客长久未除草&#xff0c;某天升级的时候发现已经被插入了几万条垃圾留言&#xff0c;如果一条条删除那可真是累人的活。遂考虑直接进入mysql直接清空表或者删除表中数据。 本文记录一下这2种操…

[转]云原生到底是什么?

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#x1f61c; &#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525; &#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4ac;格言&#xf…

膛目结舌的代码技巧!一看就是冷暴力~~~~

你见过哪些令你膛目结舌的代码技巧&#xff1f; 代码世界有很多令人大呼小叫的技巧&#xff01;有的代码像魔术师一样巧妙地隐藏了自己&#xff0c;有的像魔法师一样让你眼花缭乱&#xff0c;还有的像瑜伽大师一样灵活自如。它们让我们惊叹不已&#xff0c;让我们觉得自己仿佛置…

联合线程

联合线程实际上就是把多线程又联合成了一个线程&#xff0c;但这里还是要比单线程灵活很多&#xff0c;比如说&#xff0c;我可以让一个线程到运行到某一个条件再联合其他线程。当前线程与其他线程联合在一起&#xff0c;又一种让出cpu&#xff0c;而且直到别个线程运行完&…

Kafka学习征途:不再依赖ZK的KRaft

【Kafka】| 总结/Edison Zhou1新的KRaft架构在Kafka 2.8之前&#xff0c;Kafka重度依赖于Zookeeper集群做元数据管理和集群的高可用&#xff08;即所谓的共识服务&#xff09;。在Kafka 2.8之后&#xff0c;引入了基于Raft协议的KRaft模式&#xff0c;支持取消对Zookeeper的依赖…

探索java世界中的日志奥秘

java日志简单介绍 对于一个应用程序来说日志记录是必不可少的一部分。线上问题追踪&#xff0c;基于日志的业务逻辑统计分析等都离不日志。JAVA领域存在多种日志框架&#xff0c;目前常用的日志框架包括Log4j&#xff0c;Log4j 2&#xff0c;Commons Logging&#xff0c;Slf4j&…

RabbitMQ细说之开篇

前言关于消息中间件的应用场景&#xff0c;小伙伴们应该都耳熟能详了吧&#xff0c;比如经常提到的削峰填谷、分布式事务、异步业务处理、大数据分析等等&#xff0c;分布式消息队列成为其中比较关键的桥梁&#xff0c;也就意味着小伙伴们得掌握相关技能&#xff1b;当下相对比…

【Java】五种常见排序之-----------冒泡排序

冒泡排序&#xff1a; 原理: 将关键字较小的值不断地上浮&#xff0c;将关键字值较大的不断下沉&#xff1b;时间复杂度&#xff1a;O(n^2)空间复杂度&#xff1a;最优&#xff08;即已经排好序&#xff09;为0&#xff0c;平均空间复杂度为O(1);核心代码&#xff1a;for(int i…