一 介紹
在使用 Entity Framework Core (下面就叫 EF Core 吧)進行開發時,如果模型有變動,我們要在用 EF Core 提供的命令行工具進行手工遷移,然后再運行程序。但是為了效率,我想能不能在程序的入口處進行 Migration 呢?從個人經驗來說應該是可以,因為 EF Tool 雖然提供了 CLI 但是它最終也是被程序解析這些命令。下面就開始分析,如何通過代碼進行 Migration 。
二 分析
首先我們要先了解,在使用 EF Core 的 CLI 時,要執行兩個步驟:
第一步:生成 Migration 文件;
第二步:更新變更項到數據庫;
既然是先生成 Migration 文件再更新,那么在 EF Core 里面一定有對應的模塊做這件事情。下面我們看一下 EF Core 項目的結構。從中我們確實找到關於 Migration 的模塊。在 Migrations/Design 目錄的類名稱上我們可以看出來,它就是生成 Migration 文件的。這里先到這兒。

找到了生成 Migration 文件的入口,我們再來找一下如何通過代碼將這些變更更新到數據庫中。
在使用 EF Core 的時候,我們都要通過繼承 DbContext 來編寫自己的 DbContext 子類。在 DbContext 類中我們找到了一個 Database 屬性。如下圖所示:

然后查看了 DatabaseFacde 這個類,並沒有發現執行遷移相關的函數。通過代碼搜索,我在 RelationalDatabaseFacadeExtensions 這個類中有一個 Migration() 擴展方法。通過注釋的解析,我也確定了它就是執行 Migration 文件,並將變更更新到數據庫。
這兩個步驟對應的代碼我們都找到了,下面我們就編寫一段兒代碼,完成自動將模型變更更新到數據庫的功能。
1 public class AutoMigration 2 { 3 private readonly IServiceProvider _serviceProvider; 4 private InformationDbContext _context; 5 6 public AutoMigration(IServiceProvider serviceProvider) 7 { 8 _serviceProvider = serviceProvider; 9 _context = serviceProvider.GetService<InformationDbContext>(); 10 } 11 12 public void Migrator() 13 { 14 var path = Path.Combine(AppContext.BaseDirectory, "..\\..\\..\\Migrations\\"); 15 if (!Directory.Exists(path)) 16 { 17 Directory.CreateDirectory(path); 18 } 19 else 20 { 21 Directory.GetFiles(path).ToList().ForEach(File.Delete); 22 } 23 24 using (_context) 25 { 26 var services = ((IInfrastructure<IServiceProvider>) _context).Instance; 27 var codeHelper = new CSharpHelper(); 28 var scaffolder = ActivatorUtilities.CreateInstance<MigrationsScaffolder>(services, 29 new CSharpMigrationsGenerator(codeHelper, new CSharpMigrationOperationGenerator(codeHelper), 30 new CSharpSnapshotGenerator(codeHelper))); 31 32 var projectDir = Path.Combine(path, "..\\"); 33 var migrationAssembly = new MigrationsAssembly(new CurrentDbContext(_context), _context.Options, new MigrationsIdGenerator()); 34 scaffolder.GetType().GetField("_migrationsAssembly", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(scaffolder, migrationAssembly); 35 36 var readonlyDic = new ReadOnlyDictionary<string,TypeInfo>(new Dictionary<string, TypeInfo>()); 37 migrationAssembly.GetType().GetField("_migrations", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(migrationAssembly, new LazyRef<IReadOnlyDictionary<string, TypeInfo>>(readonlyDic)); 38 var migration = scaffolder.ScaffoldMigration("Information.Migrations", "Information"); 39 40 scaffolder.Save(projectDir, migration, path); 41 42 //另外一種保存方式 43 //File.WriteAllText($"Migrations\\{migration.MigrationId}{migration.FileExtension}", migration.MigrationCode); 44 //File.WriteAllText("Migrations\\" + 45 // migration.MigrationId + ".Designer" + migration.FileExtension, 46 // migration.MetadataCode); 47 //File.WriteAllText("Migrations\\" + migration.SnapshotName + migration.FileExtension, 48 // migration.SnapshotCode); 49 } 50 51 using(_context = (InformationDbContext)_serviceProvider.GetService<IDbContext>()) 52 { 53 _context.Database.Migrate(); 54 } 55 } 56 }
另外一個注意點:我們需要指定一下遷移文件所在項目。
1 services.AddDbContext<InformationDbContext>(opt => 2 { 3 var connectionString = configuration["ConnectionStrings:DefaultConnection"]; 4 opt.UseSqlServer(connectionString, optionBuilder => 5 { 6 optionBuilder.MigrationsAssembly("Information"); 7 }); 8 });
三 總結
通過上面的分析可以知道,其實我們就是把 CLI 的兩個命令通過代碼實現了一下。在 Startup 文件中進行調用即可。為什么想這么干?因為在實際開發的時候,來回切換窗口心里覺得不爽了唄。:)
