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.pswp.cn/news/284445.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/284445.shtml
英文地址,請注明出處:http://en.pswp.cn/news/284445.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

poj 1679: The Unique MST【次小生成樹】

題目鏈接 參考博客 希望注釋足夠清楚。。歡迎指出不足~ #include<cstdio> #include<cstring> #include<algorithm> using namespace std;const int maxn110; const int INF0x3f3f3f3f;int n,m; int mp[maxn][maxn]; int maxlen[maxn][maxn]; //maxlen…

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. 硬盤分區 本文講解利用固態硬盤+機械硬盤的分區方式。 固態硬盤:為了絕對提高系統運行的速度,將固態硬盤作為雙系統的…

[矩形并-掃描線-線段樹]Picture

最近在補數學和幾何&#xff0c;沒啥好寫的&#xff0c;因為已經決定每天至少寫一篇了&#xff0c;今天隨便拿個題水水。 題目大意&#xff1a;給你N個邊平行于坐標軸的矩形&#xff0c;求它們并的周長。(N<5000) 思路&#xff1a;這個數據范圍瞎暴力就過了&#xff0c;但我…

聊聊研發團隊中的“人”

大家好&#xff0c;我是Z哥。漢字博大精深&#xff0c;很多時候我們可以通過拆字來更形象地理解一個詞的含義。比如“團隊”這個詞的兩個字"團"和“隊”單獨看也都是表示一種由多人組成的組織。再做一下拆字就是“口”“才”和“耳”“人”。前者表示一個人才如果沒有…

[轉]【分布式系統】唯一ID生成策略總結

文章目錄 全局唯一id介紹 全局唯一id特點:常見全局唯一id生成策略 1、數據庫自增長序列或字段生成id 2、UUID 3、Redis生成ID 4、zookeeper生成ID 5、Twitter的snowflake算法全局唯一id介紹 系統唯一id是我們在設計階段常常遇到的問題。在復雜的分布式系統中&#…

shell在一個大文件找出想要的一段字符串操作技巧

昨天端午&#xff0c;晚上的時候接了一個電話&#xff0c;我朋友的公司&#xff0c;數據庫被兩個工作沒多久的phper給弄壞了&#xff0c;具體就是把一個字段值&#xff0c;給全表弄成一個了名字了&#xff0c;當然這個是可以配置了禁止全表更新數據庫,這下可急壞了&#xff0c;…

CentOS7安裝EPEL源

CentOS7安裝EPEL [lijiayuncentos-*** ~]$ yum install epel-release已加載插件&#xff1a;fastestmirror, langpacks您需要 root 權限執行此命令。[lijiayuncentos-*** ~]$ su密碼&#xff1a;[rootcentos-*** lijiayun]# yum install epel-release已加載插件&#xff1a;fas…

超全的開源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瓦片架構支持。您只需要選擇適當…

LVS/keepalived配置

LVS/DR keepalived配置注意&#xff1a;前面雖然我們已經配置過一些操作&#xff0c;但是下面我們使用keepaliave操作和之前的操作是有些沖突的&#xff0c;所以若是之前配置過DR&#xff0c;請首先做如下操作&#xff1a;dr上執行&#xff1a;$ipv -Cifconfig eth0:0 down前…

Mysql清空表(truncate)與刪除表中數據(delete)的區別

2019獨角獸企業重金招聘Python工程師標準>>> 為某基于wordpress搭建的博客長久未除草&#xff0c;某天升級的時候發現已經被插入了幾萬條垃圾留言&#xff0c;如果一條條刪除那可真是累人的活。遂考慮直接進入mysql直接清空表或者刪除表中數據。 本文記錄一下這2種操…

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

&#x1f4cb; 個人簡介 &#x1f496; 作者簡介&#xff1a;大家好&#xff0c;我是阿牛&#x1f61c; &#x1f4dd; 個人主頁&#xff1a;館主阿牛&#x1f525; &#x1f389; 支持我&#xff1a;點贊&#x1f44d;收藏??留言&#x1f4dd; &#x1f4ac;格言&#xf…

【GlobalMapper精品教程】008:如何根據指定區域(shp、kml、cad)下載衛星影像?

本文講解在Globalmapper中根據指定的范圍(shp、kml、cad等格式)進行在線衛星影像的下載方法。 文章目錄 一、根據shp范圍下載谷歌影像1. 加載谷歌影像2. 加載shp矢量范圍3. 根據范圍導出影像二、根據kml范圍下載谷歌影像1. 生成研究區范圍kml2. 根據kml范圍下載影像三、根據CAD…