標籤:
使用 Entity Framework Core 時,通過代碼自動 Migration一 介紹
在使用 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 else20 {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 檔案中進行調用即可。為什麼想這麼幹?因為在實際開發的時候,來回切換視窗心裡覺得不爽了唄。:)
使用 Entity Framework Core 時,通過代碼自動 Migration