ホーム > ASP.NET, Visual Studio > Viewのマイグレーションを考える

Viewのマイグレーションを考える


異なるスキーマを参照するViewを作って、Entity Frameworkから参照したいという要件があって、Viewの更新管理をどうにかしたいなーと思っていたんだけれど、だいたい固まったのでメモしておく

方針としてはこんな感じ

  1. Viewの形のEntityを作り、DbContextにDbQueryとして公開する
  2. OnModelCreatingで、Query~ToViewで関連付ける
  3. 空のマイグレーションファイルを作り、upでcreate viewを、downでdrop viewを行う。スキーマの差し替えもこのタイミングで行う。

サンプル
https://github.com/karuakun/EntityFrameworkSchemaAcrossDbViewSample

EntityとDbContext

1,2はササッとこんな感じで
Data/Entities/QueryTest.cs

    public class Test1
    {
        [Key]
        public string Id { get; set; }

        public string Name { get; set; }
        public string QueryTest2Id { get; set; }
    }

Data/SampleDbContext.cs

    public class SampleDbContext: DbContext
    {
        public const string DefaultConnectionStringName = "sampledb";

        public SampleDbContext(DbContextOptions options) : base(options)
        {
        }

        public virtual DbQuery QueryTest2 { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Query(entity =>
            {
                entity.ToView("v_seconddb_test2");
            });
        }
    }

マイグレーションの作成

DbContextにマイグレーションに関わる変更がない状態で、マイグレーションファイルを追加すると、空のマイグレーションファイルが出来上がるので、この子にViewの作成と削除を書いてあげる。

dotnet migrations add addsampleview

追加されたマイグレーションファイル

    public partial class addview : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
        }
    }

まずは単純に

create view にスキーマ名が入っているので気持ち悪い。。。

    public partial class addview : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.Sql(
                "create view v_seconddb_test2 as select Id, Name, Property1 from schema2.test2");
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.Sql("drop view v_seconddb_test2");
        }
    }

Configurationもしくは環境変数から、マッピングを取得する

Startup時に作るIConfigurationRootにアクセスできれば良いんだけれど、MigrationだとDIでConfigurationを受け取ることができない。
ConfigurationをStartupのstaticにしてあげるのは嫌なので、、、DesignTimeContextを使う

こんなクラスをプロジェクトのルートに置いておくと、dotnet efで起動した場合にこっちのコンテキストで初期化してくれる。これなら多少無茶してもいいかなぁ、、、と言うことで、IConfigurationRootをstaticで生やしてあげる。

    public class DesignTimeSampleDbContextFactory : IDesignTimeDbContextFactory
    {
        public static IConfigurationRoot DesignTimeConfiguration { get; private set; }

        public SampleDbContext CreateDbContext(string[] args)
        {
            DesignTimeConfiguration = DesignTimeConfigurationBuilder.BuildConfiguration(args);
            var builder = new DbContextOptionsBuilder()
                .UseMySql(DesignTimeConfiguration.GetConnectionString(SampleDbContext.DefaultConnectionStringName)
                );
            return new SampleDbContext(builder.Options);
        }
    }

Configurationを参照できれば、あとはJSONと環境変数の世界なので、appsettings.jsonにこんな感じでエントリを追加して

  "MigrationSettings": {
    "MigrationType": "Local",

    "SchemaMappings": {
      "Local": {
        "AppDb": "appdb", 
        "SecondDb": "seconddb"
      },
      "Stating": {
        "AppDb": "stg_appdb",
        "SecondDb": "stg_seconddb"
      }
    }
  },

設定を受けるようにこんなクラスを作っておく

    public class MigrationSchemaMappings
    {
        public string AppDb { get; set; }
        public string SecondDb { get; set; }
    }

で、手作業で作ったSQLがマイグレーションの海に埋もれるのは悲しいので、手作業で作った子はマイグレーション配下にこんな感じでmanualフォルダーを作って、そこに物理ファイルとしておいておきたい。
無題

マイグレーションの拡張メソッドとして、こんな感じのものを作っておけば、

    public static class MigrationExtensions
    {
        public static string GetSqlText(this Migration source, string migrationId, string fileName)
        {
            var sqlPath = Path.Combine(Environment.CurrentDirectory, "Migrations", "manual", migrationId, fileName);
            if (!File.Exists(sqlPath))
                throw new FileNotFoundException(sqlPath);

            var sqlText = File.ReadAllText(sqlPath);
            if (string.IsNullOrEmpty(sqlText))
                throw new InvalidOperationException($"sqlfile is empty: {sqlText}");

            var settings = GetMigrationSettings();
            var schemaReplacedSqlText = sqlText.Replace("$SecondDb$", settings.SecondDb);
            return schemaReplacedSqlText;
        }

        private static MigrationSchemaMappings GetMigrationSettings()
        {
            var configuration = DesignTimeSampleDbContextFactory.DesignTimeConfiguration;
            var migrationType = configuration.GetValue("MigrationSettings:MigrationType");
            return configuration.GetSection($"MigrationSettings:SchemaMappings:{migrationType}")
                .Get();
        }
    }

マイグレーションスクリプトはこうかける。

    public partial class addview : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.Sql(this.GetSqlText(this.GetId(), "up.v_seconddb_test2.sql"));
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.Sql(this.GetSqlText(this.GetId(), "down.v_seconddb_test2.sql"));
        }
    }
カテゴリー:ASP.NET, Visual Studio
  1. まだコメントはありません。
  1. No trackbacks yet.

コメントを残す