EntityFrameworkCore 模型自動更新(上)

話題

嗯,距離上一次寫博文已經過去近整整十個月,還是有一些思考,但還是變得懶惰了,心思也不再那麼專注,有點耗費時間,學習也有點停滯不前,那就順其自然,隨心所欲吧,等哪天心血來潮,想寫了就寫寫

模型自動更新方案研究(上)

一般團隊人數很少時,使用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 targetModel

var modelDiffer = context.GetService<IMigrationsModelDiffer>();

var migrationOperations = modelDiffer.GetDifferences(soureModel, targetModel);

那接下來問題來了,拿到差異性遷移操作後,我們應該怎麼處理呢?留着各位思考下,我們下篇見

總結

本文給出了自動更新模型思路以及整個完整實現邏輯,剩餘內容我們下篇再敘,主要是沒心情寫,寫不下去了,今天我們就到此爲止~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章