<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Ef Core花里胡哨系列(8) 如何可控管理Ef Core的遷移?

      Ef Core花里胡哨系列(8) 如何可控管理Ef Core的遷移?

      通常使用Ef Core遷移時,可能就是簡單的使用命令dotnet-ef migrations add或者dotnet ef database update等等,基本都需要靠命令維護,非常的繁瑣。特別是現在很多項目都是迭代型項目,很容易造成開發人員和運維人員的負擔,所以,我們是否可以將其自動化?

      自動遷移

      自動遷移顧名思義,就是可以讓程序啟動的時候自己執行遷移,不需要運維人員參與,開發人員只需要保證遷移順序的正確性即可。

      自動建庫

      如果想使用首次自動建庫,那我們就需要生成首次遷移時,直接刪除首次遷移的文件,留下[Sample]DbContextModelSnapshot快照文件即可,當然,這些不是主要內容,只是用來引出下面的可控遷移

      var app = builder.Build();
      
      
      await using var scoped = app.Services.CreateAsyncScope();
      using var db = scoped.ServiceProvider.GetRequiredService<SampleDbContext>();
      try
      {
          // db.Database.Migrate(); // 需要保留首次遷移文件,并且后續啟動可以自動遷移
          db.Database.EnsureCreated(); // 不需要保留首次遷移文件
      }
      catch
      {
          Console.WriteLine("init database error.");
      }
      

      可控遷移

      可控遷移即我們可以通過封裝Ef Core內置的各種Service來幫助我們實現控制遷移的效果。

      EfMigrationHistory

      我們要可控遷移,那么我們就需要想辦法操控__EFMigrationsHistory這張表,它是Ef Core內置的表,用來記錄遷移的記錄,這張表是一張無狀態的表,他只負責存儲成功的遷移名稱和遷移時Ef Core的版本,其它沒有關聯,我們如何管理它呢?我們只需在DbContext中創建一個同名的表即可,并且可以預先設計好其它審計用字段,后續不可更改。

      例如我們重新設計這張表,除了遷移IdMigrationIdEf Core的版本ProductVersion外,我們添加遷移應用時間,和遷移應用類型字段。

      [Table("__EFMigrationsHistory")]
      public class EFMigrationsHistory
      {
          [Key]
          [MaxLength(150)]
          public required string MigrationId { get; set; }
      
          [MaxLength(32)]
          public string ProductVersion { get; set; } = null!;
      
          [NotMapped]
          public string Sort => MigrationId.Split("_")[0];
      
          [Comment("遷移時間")]
          public DateTime? MigrationTime { get; set; } = DateTime.Now;
      
          [Column(TypeName = "varchar(20)")]
          public MigrationType MigrationType { get; set; } = MigrationType.Success;
      }
      

      添加遷移類型的目的是為了增加遷移執行順序的豐富性,下面提供了成功Success、嘗試但失敗TryFail、嘗試但成功TrySuccess以及跳過Skip等多種方式。其中HistoryInstall為記錄型,主要是為了標記歷史記錄和安裝時間。

      public enum MigrationType
      {
          Success,
          TryFail,
          TrySuccess,
          Skip,
          History,
          Install
      }
      

      隨后我們將其添加到DbContext上下文中即可,我們的新結構會替代原來的結構實行職能。

      public interface IMigrationDbContext
      {
          DbSet<EFMigrationsHistory> EFMigrationsHistory { get; set; }
      }
      
      public class SampleDbContext : IMigrationDbContext
      {
          public SampleDbContext(DbContextOptions<SampleDbContext> options) : base(options) 
          { 
      
          }
      
          public DbSet<EFMigrationsHistory> EFMigrationsHistory { get; set; }
      }
      

      遷移控制

      我們已經將__EFMigrationsHistory注冊到了DbContext的上下文中,成為了我們可用的表,我們接下來就是了解Ef Core是如何遷移的,我們如何加入自己的邏輯。

      我們之前有提到__EFMigrationsHistory只記錄了成功的遷移,如果遷移沒有成功,則會立即中斷,那么他是怎么實現的?其實就是讀取本地的文件列表,然后按照遷移名稱進行排序并和表中的對比,然后開始逐一執行。

      如此我們就可以模仿他的操作,來實現我們自己的邏輯。

      遷移管理

      我喜歡將遷移輸出到類庫,這個方便讀取和管理,需要調用的地方只需引用該類庫即可。

      services.AddDbContext<SampleDbContext>(opts =>
      {
          optionsBuilder.UseMySql(connStr, new MySqlServerVersion(new Version(8, 0)), opts => opts.MigrationsAssembly("Sample.Migrations"));
      });
      

      每個遷移文件都分為兩部分,xxxx.csxxxx.Designer.cs,其中xxxx.Designer.cs中以下部分是我們需要的部分。

      • [DbContext(typeof(SampleDbContext))]這個特性向我們指明了遷移對應的DbContext是哪個,也就是說,我們可以自定義多個DbContext從其判斷執行不同的遷移。
      • [Migration("20231108075812_XXXXX")]這個特性向我們指明了遷移名稱,當然,也代表了遷移的順序。
      [DbContext(typeof(SampleDbContext))]
      [Migration("20231108075812_XXXXX")]
      partial class 20231108075812_XXXXX
      {
      
      }
      

      而且,我們可以根據需要擴充這些特性,來滿足我們不同的順序需求:

      /// <summary>
      /// 標記的遷移失敗會自動跳過
      /// </summary>
      [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
      public class TryMigrationsAttribute : Attribute
      {
          public TryMigrationsAttribute()
          { }
      }
      
      /// <summary>
      /// 標記需要跳過的Migration
      /// </summary>
      [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
      public class SkipMigrationsAttribute : Attribute
      {
          public SkipMigrationsAttribute()
          { }
      }
      

      好了,所有的東西都準備完成了,我們來實現真正的遷移管理。

      我們首選需要獲取遷移所在的程序集:

      private static Assembly EfAssembly => Assembly.Load("Sample.Migrations");
      

      然后根據需要,獲取DbContext中的遷移服務,就可以遷移指定的遷移了:

      var migrator = dbContext.GetService<IMigrator>();
      

      以下是完整的代碼,包括多個DbContext如何實現互不干擾以及指定遷移,自動歸檔遷移等等。

      public class MigrationManager<TMainDbContext, TMySqlDbContext, TOracleDbContext, TPostgreSQLDbContext, TSqlServerDbContext>
          where TMainDbContext : DbContext, IMigrationDbContext, new()
          where TMySqlDbContext : DbContext, IMigrationDbContext, new()
          where TOracleDbContext : DbContext, IMigrationDbContext, new()
          where TPostgreSQLDbContext : DbContext, IMigrationDbContext, new()
          where TSqlServerDbContext : DbContext, IMigrationDbContext, new()
      {
          private readonly IServiceProvider ServiceProvider;
      
          private readonly ILogger Logger;
      
          protected TMainDbContext DB => ServiceProvider.GetRequiredService<TMainDbContext>();
      
          public MigrationManager(IServiceProvider serviceProvider, ILogger logger)
          {
              ServiceProvider = serviceProvider;
              Logger = logger;
          }
      
          private static Assembly EfAssembly => Assembly.Load("Sample.Migrations");
      
          private static DatabaseType DbType => AppSettings.Get<DbOptions>()?.GetUseableWriteHost()?.DbType ?? DatabaseType.MySql;
          private static string DbTypeName => DbType.ToString();
      
          public static string GetEfVersion()
          {
              return Microsoft.EntityFrameworkCore.Infrastructure.ProductInfo.GetVersion();
          }
      
          /// <summary>
          /// 獲取的是本地文件中的所有版本,而不是數據庫
          /// </summary>
          /// <returns></returns>
          public IEnumerable<EFMigrationsHistory> GetList()
          {
              var migrations = EfAssembly.GetTypes().Where(x =>
                      x.Namespace != null && x.Namespace.Contains(DbTypeName) &&
                      x.GetCustomAttributes<MigrationAttribute>().Any() && x.GetCustomAttribute<DbContextAttribute>()?.ContextType.BaseType == typeof(TMainDbContext))
                  .Select(x =>
                      new EFMigrationsHistory()
                      {
                          MigrationId = x.GetCustomAttribute<MigrationAttribute>()!.Id,
                          ProductVersion = GetEfVersion(),
                      })
                  .AsQueryable();
      
              return migrations;
          }
      
          public IEnumerable<Type> GetClassList()
          {
              var migrations = EfAssembly.GetTypes().Where(x =>
                      x.Namespace != null && x.Namespace.Contains(DbTypeName) &&
                      x.GetCustomAttributes<MigrationAttribute>().Any() && x.GetCustomAttribute<DbContextAttribute>()?.ContextType.BaseType == typeof(TMainDbContext))
                  .AsQueryable();
      
              return migrations;
          }
      
          /// <summary>
          /// 將部署前的遷移都加入遷移記錄表中
          /// </summary>
          /// <returns></returns>
          public async Task DiscardNoPendingMigrationsHistoryAsync()
          {
              Logger.LogInformation("Migration-Discard: 準備部署前寫入歷史遷移");
      
              var lastVersion = await GetLastVersion();
      
              Logger.LogInformation($"Migration-Discard: 部署前遷移最后版本 [{lastVersion?.MigrationId}]");
      
              var applied = await GetAppliedMigrationsAsync();
      
              Logger.LogInformation($"Migration-Discard: 部署前已經應用的遷移 [{applied.Count()}] 條記錄");
      
      
              var migrations = lastVersion is null
                  ? GetList().ToList()
                  : GetList().Where(x => String.CompareOrdinal(x.Sort, lastVersion.Sort) <= 0 && (applied != null && !applied.Contains(x.MigrationId))).ToList();
      
              if (migrations is not null && migrations.Any())
              {
                  migrations.ForEach(x=>x.MigrationType = MigrationType.History);
                  DB.AddRange(migrations);
      
                  await DB.SaveChangesAsync();
              }
      
              Logger.LogInformation($"Migration-Discard: 部署前寫入的歷史遷移 [{migrations?.Count ?? 0}] 條記錄");
          }
      
          /// <summary>
          /// 獲取最后一個版本
          /// </summary>
          /// <returns></returns>
          public async Task<EFMigrationsHistory?> GetLastVersion()
          {
              var applied = await DB.EFMigrationsHistory.ToListAsync();
      
              var thisContextLocal = GetList().Select(x => x.MigrationId);
      
              var thisContextVersions = applied.Where(x => thisContextLocal.Contains(x.MigrationId));
      
              return thisContextVersions.MaxBy(x => x.Sort);
          }
      
          /// <summary>
          /// 獲取未應用的遷移
          /// </summary>
          /// <returns></returns>
          public async Task<IEnumerable<EFMigrationsHistory>> GetPendingMigrationsAsync()
          {
              var lastVersion = await GetLastVersion();
      
              if (lastVersion is null)
              {
                  return GetList();
              }
      
              return GetList().Where(x => String.CompareOrdinal(x.Sort, lastVersion.Sort) > 0).OrderBy(x => x.Sort);
          }
      
          public async Task<IEnumerable<Type>> GetPendingMigrationsClassAsync()
          {
              var lastVersion = await GetLastVersion();
      
              if (lastVersion is null)
              {
                  return GetClassList();
              }
      
              return GetClassList().Where(x => String.CompareOrdinal(x.GetCustomAttribute<MigrationAttribute>()!.Id, lastVersion.MigrationId) > 0).OrderBy(x => x.GetCustomAttribute<MigrationAttribute>()!.Id);
          }
      
          private IMigrator GetMigrator()
          {
              DbContext dbContext = DbType switch
              {
                  DatabaseType.SqlServer => new TSqlServerDbContext(),
                  DatabaseType.Oracle => new TOracleDbContext(),
                  DatabaseType.PostgreSQL => new TPostgreSQLDbContext(),
                  DatabaseType.MySql or _ => new TMySqlDbContext(),
              };
              return dbContext.GetService<IMigrator>();
          }
      
          /// <summary>
          /// 應用遷移
          /// </summary>
          /// <param name="migrations"></param>
          /// <returns></returns>
          public async Task ApplyMigrationsAsync(IEnumerable<EFMigrationsHistory> migrations)
          {
              if (!migrations.Any())
              {
                  return;
              }
      
              var migrator = GetMigrator();
      
              foreach (var migration in migrations)
              {
                  await migrator.MigrateAsync(migration.MigrationId);
              }
          }
      
          /// <summary>
          /// 應用遷移, 會跳過嘗試遷移失敗的遷移
          /// </summary>
          /// <param name="migrations"></param>
          /// <returns></returns>
          public async Task ApplyMigrationsClassAsync(IEnumerable<Type> migrations)
          {
              if (!migrations.Any())
              {
                  return;
              }
      
              var migrator = GetMigrator();
      
              var skipMigrations = new List<EFMigrationsHistory>();
      
              foreach (var migration in migrations)
              {
                  var id = migration.GetCustomAttribute<MigrationAttribute>()!.Id;
                  if (migration.GetCustomAttributes<TryMigrationsAttribute>().Any())
                  {
                      try
                      {
                          await migrator.MigrateAsync(id);
                      }
                      catch (Exception ex)
                      {
                          var skip = new EFMigrationsHistory
                          {
                              MigrationId = id,
                              ProductVersion = GetEfVersion(),
                              MigrationType = MigrationType.TryFail
                          };
      
                          var errMsg = @$"遷移 [{id}] 已嘗試并跳過!\n
      遷移失敗!\n
      提示信息為: {ex.Message}\n
      請檢查 Migrations 文件! 或者手動更改 __MigrationsHistory 表, 將該遷移添加入表中標記為已應用即可.
                          ";
      
                          Logger.LogWarning(ex, errMsg);
      
                          DB.AddRange(skip);
                          await DB.SaveChangesAsync();
                      }
                  }
                  else if (migration.GetCustomAttributes<SkipMigrationsAttribute>().Any())
                  {
                      var skip = new EFMigrationsHistory
                      {
                          MigrationId = id,
                          ProductVersion = GetEfVersion(),
                          MigrationType = MigrationType.Skip
                      };
      
                      var errMsg = @$"遷移 [{id}] 已跳過!\n
      或者手動更改 __MigrationsHistory 表, 將該遷移添加入表中標記為已應用即可.
                          ";
      
                      Logger.LogWarning(errMsg);
      
                      DB.AddRange(skip);
                      await DB.SaveChangesAsync();
                  }
                  else
                  {
                      try
                      {
                          await migrator.MigrateAsync(id);
                      }
                      catch (Exception ex)
                      {
                          var errMsg = @$"遷移 [{id}] 失敗!\n
      遷移失敗!\n
      提示信息為: {ex.Message}\n
      請檢查 Migrations 文件! 或者手動更改 __MigrationsHistory 表, 將該遷移添加入表中標記為已應用即可.
                          ";
      
                          Logger.LogWarning(ex, errMsg);
                      }
                  }
      
                  Logger.LogInformation($"遷移 [{id}] 已應用.");
              }
          }
      
          /// <summary>
          /// 獲取已應用的遷移
          /// </summary>
          /// <returns></returns>
          public async Task<IEnumerable<string>> GetAppliedMigrationsAsync()
          {
              return await DB.Database.GetAppliedMigrationsAsync();
          }
      
          /// <summary>
          /// 開始遷移入口
          /// </summary>
          /// <returns></returns>
          public async Task StartupInitMigrationsAsync()
          {
              Logger.LogInformation($"Migration-Main: 遷移程序開始執行...");
      
              var migrationsHistory = await GetAppliedMigrationsAsync();
      
              Logger.LogInformation($"Migration-Main: 已應用的遷移 [{string.Join(",", migrationsHistory)}]");
      
              if (migrationsHistory.Any())
              {
                  var pendingMigrations = await GetPendingMigrationsClassAsync();
      
                  Logger.LogInformation($"Migration-Main: 獲取未應用的遷移 [{string.Join(",", pendingMigrations.Select(x => x.GetCustomAttribute<MigrationAttribute>()!.Id))}]");
      
                  await ApplyMigrationsClassAsync(pendingMigrations);
              }
      
              await DiscardNoPendingMigrationsHistoryAsync();
          }
      
          public async Task EnsureHasEfMigrationsHistoryTableAsync()
          {
              if (!await DB.IsExistTableAsync(typeof(EFMigrationsHistory)))
              {
                  await DB.EnsureTableCreatedAsync(typeof(EFMigrationsHistory));
      
                  var migrationInit = new EFMigrationsHistory()
                  {
                      MigrationId = $"{DateTime.UtcNow.AddMonths(-6).ToString("yyyyMMddHHmmss")}_Install",
                      MigrationType = MigrationType.Install,
                      ProductVersion = MigrationManager<DataDbContext, MySqlDbContext, OracleDbContext, PostgreSQLDbContext, SqlServerDbContext>.GetEfVersion()
                  };
      
                  DB.Add(migrationInit);
      
                  await DB.SaveChangesAsync();
              }
          }
      }
      
      posted @ 2024-01-03 10:15  胖紙不爭  閱讀(310)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 人成午夜免费大片| 亚洲AV无码一二区三区在线播放| 免费无码毛片一区二三区| 清远市| 国产精品色哟哟在线观看| 依依成人精品视频在线观看| 92精品国产自产在线观看481页 | 一出一进一爽一粗一大视频| 日日躁夜夜躁狠狠久久av | 人妻无码不卡中文字幕系列| 国产偷国产偷亚洲高清人| 久久久久国产一级毛片高清版A| 国产精品二区中文字幕| 伊人春色激情综合激情网| 给我播放片在线观看| 久热这里只有精品视频六| 清河县| 亚洲色婷婷综合开心网| 欧美激欧美啪啪片| 国产粉嫩美女一区二区三| 国产日韩精品视频无码| 无码人妻丰满熟妇啪啪| 国产成人综合亚洲第一区| 人妻系列中文字幕精品| 精品国偷自产在线视频99| 国产一区国产二区在线视频| 亚洲国产欧美在线人成| 亚洲乱熟乱熟女一区二区| 婷婷丁香五月激情综合| 精品人妻中文字幕av| 高清性欧美暴力猛交| 99精品伊人久久久大香线蕉| 国产精品一区二区三区四区| 国精品无码一区二区三区左线| 一区二区三区综合在线视频| 亚洲色婷婷综合开心网| 久久精品第九区免费观看 | 国产一区二区三区四区激情| 亚洲av综合av一区| 国产午夜视频在线观看| 伊春市|