きよくらの備忘録

「三日坊主と呼ばせない!日記」改め。主にソフトウェア開発関連の話題。

DacFxで差分更新スクリプトを取得する際に特定のオブジェクトを除外する

更新用T-SQLスクリプト生成時に特定のオブジェクトを除外したい

先日書いたエントリ、 kiyokura.hateblo.jp

……の補足情報です。

特定のオブジェクトを更新スクリプトに含めたくない場合もあるのではないかと思います。私はあります。 DaxFxのSchemaComparisonでこれを実現する方をあれやこれや試行錯誤してみたところ、異なる二つのアプローチにたどり着きました。

SchemaComparisonResult.Exclude で抽出結果から除外してスクリプトを生成するアプローチ

まず一つ目は、SchemaComparison.Compare()で比較結果から除外対象を登録したうえでスクリプトを生成する方法です。

比較結果として返されるSchemaComparisonResultのメンバメソッド、Exclude()を使います。 以下のように、Excludeに除外したいオブジェクトをSchemaDifference側で渡してやればOK。

// パターン1:比較結果から除外するアプローチ
var newverdacpac = @"C:\newver.dacpac";
var oldverdacpac = @"C:\oldver.dacpac";

var newverEndpoint = new SchemaCompareDacpacEndpoint(newverdacpac);
var oldverEndpoint = new SchemaCompareDacpacEndpoint(oldverdacpac);

var comparison = new SchemaComparison(newverEndpoint, oldverEndpoint);
var comparisonResult = comparison.Compare();

// 削除(DROP)になる更新を除外して追加(CREATE)と変更(ALTER)のみのスクリプトを生成する
foreach (var diff in comparisonResult.Differences.Where(x => x.UpdateAction == SchemaUpdateAction.Delete))
{
  comparisonResult.Exclude(diff);
}

// スクリプト生成
var generationResult = comparisonResult.GenerateScript("HogeDb");
var scriptfile = @"C:\update-database-exclude.sql";
using (var sw = new StreamWriter(scriptfile, false, Encoding.UTF8))
{
  sw.Write(generationResult.Script);
  sw.Flush();
}

ポイントはExcludeメソッドを呼ぶたびに比較結果内で依存関係等のチェックが行われる点です。そのためExcludeメソッドの実行にある程度時間がかかることがあります。また依存関係チェックの結果、スクリプト生成時に除外されないケースもあります(除外しようとしたオブジェクトが、更新スクリプトに含まれるオブジェクトから依存されている場合) 端的に言うと、Visual Studio上でSSDTのスキーマ比較ツールを利用して比較結果表からチェックボックスでオブジェクトを選択して除外していくのとまったく同じ操作と結果を得ることがでるようでう。

 

ExcludedSourceObjectsとExcludedTargetObjectsに除外オブジェクトを登録した上で比較を行うアプローチ

もう一点は、SchemaComparison.ExcludedSourceObjectsSchemaComparison.ExcludedTargetObjectsに除外したいオブジェクトを登録した上でCompareメソッドを実行するアプローチです。 ExcludedSourceObjectsとExcludedTargetObjectsに登録されたオブジェクトは比較時に対象から除外されるため、これらに登録されているオブジェクトは(差異があっても)比較結果に差異として抽出されません。

例えば以下のようなコードになります。

// パターン2:除外してから比較するアプローチ
var newverdacpac = @"C:\newver.dacpac";
var oldverdacpac = @"C:\oldver.dacpac";

var newverEndpoint = new SchemaCompareDacpacEndpoint(newverdacpac);
var oldverEndpoint = new SchemaCompareDacpacEndpoint(oldverdacpac);

var comparison = new SchemaComparison(newverEndpoint, oldverEndpoint);
var comparisonResultPre = comparison.Compare();

// 削除(DROP)になる更新を除外して追加(CREATE)と変更(ALTER)のみのスクリプトを生成する
foreach (var diff in comparisonResultPre.Differences.Where(x => x.UpdateAction == SchemaUpdateAction.Delete))
{
  if (diff.SourceObject != null)
    comparison.ExcludedSourceObjects.Add(new SchemaComparisonExcludedObjectId(diff.SourceObject.ObjectType, diff.SourceObject.Name));
  if (diff.TargetObject != null)
    comparison.ExcludedTargetObjects.Add(new SchemaComparisonExcludedObjectId(diff.TargetObject.ObjectType, diff.TargetObject.Name));
}

// 除外オブジェクトを登録した状態で再度比較を実行
var comparisonResult = comparison.Compare();

// スクリプト生成
var generationResult = comparisonResult.GenerateScript("HogeDb");
var scriptfile = @"C:\update-database-add-excludedlist.sql";
using (var sw = new StreamWriter(scriptfile, false, Encoding.UTF8))
{
  sw.Write(generationResult.Script);
  sw.Flush();
}

この方法の場合、ExcludedXxxObjectsに含まれるオブジェクトは依存関係のチェックも行われないようで、更新用T-SQLに含まれるオブジェクトからの依存があっても除外されたままになります。 上記サンプルではExcludedXxxObjectに登録するための情報を得るために一度Compareを行い、リスト登録後に再度Compareを実行するという一見二度手間のような処理をしていますが、依存関係チェックが行われないせいかトータルの処理時間としては短めになりました*1

 

まとめ

具体的な記述のあるドキュメント等が見つからないためあくまでも挙動からの推測にはなりますが、このような形で一応目的を達成することができそうです。

*1:状況によるとは思います