【導讀】嗯,距離上一次寫博文已經過去近整整十個月,還是有一些思考,但還是變得懶惰了,心思也不再那么專注,有點耗費時間,學習也有點停滯不前,那就順其自然,隨心所欲吧,等哪天心血來潮,想寫了就寫寫。
模型自動更新(上)
一般團隊人數很少時,使用EF Core內置遷移基本已滿足,滿足的基本前提首先要生成遷移文件,然后和數據庫進行對比,但團隊人數一多,遷移文件等等涉及提交沖突等等,所以大部分情況下,我個人認為EF Core遷移基本沒啥用,這玩意用不起來
尤其涉及到版本分支很多情況下,切換不同分支所使用的數據可能也會不同,我們常用MySql數據庫,同時適配了人大金倉數據庫、高斯數據庫(GaussDb for opengauss),其對應的表結構有一些差異性,列類型也會有很大差異性,這對開發人員和測試人員來講就是深深的折磨和痛苦,大部分時間會花在保持數據庫表結構和模型一致,否則要么運行不起來,要么測試功能各種有問題,所以想想通過自動化方式來解決這個問題,本文還是分上下兩篇寫好了。
那么解決此問題的思路是怎樣的呢?同時適配多套數據庫,重寫一套?
那是不闊能的,既然我們可以通過dotnet ef命令來進行遷移,通過和數據庫表結構進行對比,從而生成遷移文件,遷移文件包含即將要執行的差異性腳本,從這個角度來看的話,我們從遷移類反堆即可得到生成的腳本以及和數據庫進行對比操作方法
初步設想理論上應該行得通,只需花時間了解下源碼就好,通過前期兩天的啃源碼,最終啃出百把行代碼即可自動更新模型到數據庫,當然這個過程中還涉及一些要考慮的細節,我們一一再敘。接下來我們以MySql為例講講整個過程,其他數據庫依葫蘆畫瓢就好,首先甩出如下代碼:
var?services?=?new?ServiceCollection();services.AddEntityFrameworkMySql();services.AddEntityFrameworkDesignTimeServices();services.AddDbContext<EfCoreDbContext>((serviceProvider,?options)?=>
{options.UseInternalServiceProvider(serviceProvider);options.UseMySql("server=localhost;Port=3306;Database=test;Username=root;Password=root;",ServerVersion.AutoDetect("server=localhost;Port=3306;Database=test;Username=root;Password=root;"));
});services.AddScoped<IDatabaseModelFactory,?MySqlDatabaseModelFactory>();var?serviceProvider?=?services.BuildServiceProvider();
EF Core有屬于它自己的容器,所以我們將全局容器和上下文所屬容器做了區分,同時呢,我們將遷移中要用到的操作依賴也手動添加,比如上面的設計服務,存在于 Microsoft.EntityFrameworkCore.Design 包內,最后將獲取數據庫表結構模型工廠手動注入即MySqlDatabaseModelFactory。接下來我們要獲取模型定義以及屬性一些定義等等,也就是我們最終要生成的目標模型結構
using?var?scope?=?_serviceProvider.CreateScope();var?currentServiceProvider?=?scope.ServiceProvider;var?context?=?(DbContext)currentServiceProvider.GetRequiredService<T>();var?connectionString?=?context.Database.GetDbConnection().ConnectionString;var?targetModel?=?context.GetService<IDesignTimeModel>().Model.GetRelationalModel();if?(targetModel?==?null)
{return?Enumerable.Empty<MigrationOperation>().ToList();
}
接下來則是獲取數據庫表結構也就是數據庫模型
var?databaseFactory?=?currentServiceProvider.GetService<IDatabaseModelFactory>();var?factory?=?currentServiceProvider.GetService<IScaffoldingModelFactory>();var?tables?=?context.Model.GetEntityTypes().Select(e?=>?e.GetTableName()).ToList();if?(!tables.Any())
{return?Enumerable.Empty<MigrationOperation>().ToList();
}//?僅查詢當前上下文模型所映射表,否則比對數據庫表差異時,將會刪除非當前上下文所有表
var?databaseModel?=?databaseFactory.Create(connectionString,?new?DatabaseModelFactoryOptions(tables:?tables));if?(databaseModel?==?null)
{return?Enumerable.Empty<MigrationOperation>().ToList();
}
這里稍微需要注意的是,若是有多個不同上下文,肯定只查詢當前上下文所對應的模型結構,不然最后生成的腳本會將其他上下文對應的表結構給刪除。緊接著,我們需要將數據庫模型轉換為上下文中的模型,即類型一致轉換,這就演變成了我們的源模型
var?model?=?factory.Create(databaseModel,?new?ModelReverseEngineerOptions());if?(model?==?null)
{return?Enumerable.Empty<MigrationOperation>().ToList();
}var?soureModel?=?model.GetRelationalModel();
接下來自熱而然就進行源模型和目標模型差異性比對,得到實際要進行的遷移操作
var?soureModel?=?model.GetRelationalModel();//TODO?Compare?sourceModel?vs?targetModelvar?modelDiffer?=?context.GetService<IMigrationsModelDiffer>();var?migrationOperations?=?modelDiffer.GetDifferences(soureModel,?targetModel);
那接下來問題來了,拿到差異性遷移操作后,我們應該怎么處理呢?留著各位思考下
本文給出了自動更新模型思路以及整個完整實現邏輯,剩余內容我們下篇再敘,主要是沒心情寫,寫不下去了,今天我們就到此為止~