きよくらの備忘録

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

CLIツールを使わずにコード中からプロジェクトをビルドしたりdacpacをデプロイしたりする(その2)

前回の続きです。

Microsoft.SqlServer.DACFx でdacpacをデータベースにデプロイする

続いて、出来上がったdacpacファイルから Microsoft.SqlServer.DACFx を使ってデータベースにデプロイしてみる。

www.nuget.org

上記NuGetッケージを取り込んだ上で、以下のコードでごくシンプルに実現できた。

var connectionString = "<デプロイ先データベースに接続するための接続文字列>";
var databaseName = "<デプロイ先のデータベース名>";

var dac = new DacServices(connectionString);
var dacpac = DacPackage.Load(@"C:\hoge\output.dacpac");
dac.Deploy(dacpac, databaseName , true);

まとめ

以下、前回のエントリも含めてまとめる。

  • CLIツールを使用せず、NuGetで提供されているパッケージを利用することで.NETのコードだけでプロジェクトdacpacの生成とデプロイが可能
  • 今回参照したパッケージは以下
  • Microsoft.Build.Evaluation.ProjectクラスBuildメソッドによるプロジェクトのビルドについてのメモ:
    • ビルド成否はboolで返る
    • ビルドログは引数として渡したロガー経由で取得する必要がある
    • SetPropertyメソッドでプロパティをコード中から設定することが可能
    • sqlproj をビルドしてdacpacを生成する場合は Microsoft.SqlServer.DacFx の参照が必要
  • Microsoft.SqlServer.Dac.DacServicesクラスDeployメソッドによるdacpacのデプロイについてのメモ:
    • DacPackageクラスのLoadメソッド でdacpacをロードし、Deployの引数に設定してデプロイする

サンプルコード

今回検証したサンプルコードを以下に公開した github.com

CLIツールを使わずにコード中からプロジェクトをビルドしたりdacpacをデプロイしたりする(その1)

先日、こんなエントリを書いた。 kiyokura.hateblo.jp

要約すると『コード中からMSBuild.exeを使ってプロジェクトファイルからdacpacをビルドして、それをSqlPackage.exeを使ってデプロイする』という内容である。 上記エントリではMSBuildやSqlPackageを利用するために割と無理やり泥臭くそれらのパスを取得しているところがなんだかアレであったり、そもそもローカルの環境に事前にインストールされているものに強く依存しているあたりもアレだったりするので、そのあたりで若干悶々としていた。

そんな折にこの上記内容をチームのメンバに展開したところ、優秀なチームメンバから

  • SqlPackage.exeのところDacFxで置き換えれるのではないか?
  • MsBuild.exeのところはMicrosoft.Build.Evaluation 名前空間のライブラリで置き換えれるのではないか?

という素晴らしい指摘をいただいた。 ので、さっそく試してみることにした。

Microsoft.Build.Evaluation でプロジェクトをビルドする(その1:csproj編)

Microsoft.Build.Evaluation名前空間の機能は主に NuGetパッケージ の Microsoft.Build で提供されている模様。 www.nuget.org

早速、新規にコンソールアプリを作成して上記パッケージを取り込み、以下のようなコードで別のシンプルなクラスライブラリのビルドを試みた。

var p1 = ProjectCollection.GlobalProjectCollection.LoadProject(@"C:\MyClassLib\MyClassLib.csproj");
p1.Build();

だが、上記コードでコンパイルは通るのだがBuild()メソッド実行箇所で以下の例外が発生した。

Microsoft.Build.Shared.InternalErrorException
  HResult=0x80131500
  Message=MSB0001: Internal MSBuild Error: Type information for Microsoft.Build.Utilities.ToolLocationHelper was present in the whitelist cache as Microsoft.Build.Utilities.ToolLocationHelper, Microsoft.Build.Utilities.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a but the type could not be loaded. unexpectedly null
  Source=Microsoft.Build

調べてみるとビルド実行には Microsoft.Build.Tasks.Core パッケージも必要になる模様。

www.nuget.org

これのパッケージを追加すると無事に上記コードでクラスライブラリが生成された。

Microsoft.Build.Evaluation でプロジェクトをビルドする(その2:sqlproj編)

次は同様にSQL Server Databaseプロジェクトのビルドを試みるため、以下のようなコードを実行してみた。

var p2 = ProjectCollection.GlobalProjectCollection.LoadProject(@"C:\MyDataBase\MyDataBase.sqlproj");
p2.Build();

しかし実行で例外などは発生しないものの、成果物が生成されていなかった。 少し調べてみると以下のことが分かった。

  • 実行されたビルドが失敗に終わっても例外は発生しない(Buildメソッドの実行自体は成功してるのでそれはそうだろう)
  • ビルドの成否はBuildメソッドの戻り値としてbool値で返される
  • ビルドの実行ログはBuildメソッドにロガーを渡すことで取得できる
    • ロガーは Microsoft.Build.Framework.ILogger の実装である必要がある
    • 既定でFileLoggerやConsoleLoggerが実装されている

という事で、以下のようにコードを書き換えて再度実行してみた。

var p2 = ProjectCollection.GlobalProjectCollection.LoadProject(@"C:\MyDataBase\MyDataBase.sqlproj");
var p2Result = p2.Build(new[] { new ConsoleLogger() });
Console.WriteLine($"p2 result : {p2Result}");

すると以下のようなビルドエラーが出力された。つまるところSQL Server Databaseプロジェクトをビルドするためのtarget、Microsoft.Data.Tools.Schema.SqlTasks.targets が参照しているアセンブリが足らない(見れていない)模様だ。

C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Microsoft\VisualStudio\v16.0\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets(491,5): error MSB4062: The "SqlModelResolutionTask" task could not be loaded from the assembly C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\Microsoft\SQLDB\Dac\150\Microsoft.Data.Tools.Schema.Tasks.Sql.dll. アセンブリ 'Microsoft.Data.Tools.Schema.Tasks.Sql, Version=16.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' からの型 'Microsoft.Data.Tools.Schema.Tasks.Sql.SqlBuildTask' にあるメソッド 'get_BuildEngine' に実装が含まれていません。 Confirm that the declaration is correct, that the assembly and all its dependencies are available, and that the task contains a public class that implements Microsoft.Build.Framework.ITask.

しばらく悩んだのだが、ふと「Microsoft.Data.Tools.Schema.Tasks.Sql.dllとかってDacFxのパッケージに含まれてるちゃうか??」と思い、Microsoft.SqlServer.DACFx をインストールして再度実行してみた。 www.nuget.org

すると、以下の通りビルドが成功し、dacpacファイルも生成されていた。 f:id:kiyokura:20200916225548p:plain

さらに、ビルドスクリプトのプロパティを設定してdacpacを出力先を任意に任意の名前で出力することもできた。

var p2 = ProjectCollection.GlobalProjectCollection.LoadProject(@"C:\MyDataBase\MyDataBase.sqlproj");
p2.SetProperty("OutputPath", @"C:\hoge\");
p2.SetProperty("SqlTargetName", "output");
var p2Result = p2.Build(new[] { new ConsoleLogger() });
Console.WriteLine($"p2 result : {p2Result}");

f:id:kiyokura:20200916230246p:plain

続く

ちょっと長くなったので今回のエントリはいったんここまでとする。 次回はMicrosoft.SqlServer.DACFxを利用したdacpacのデプロイについて書く予定だ。

SQL Server データベースプロジェクトでSSDTの単体テスト機能を使わずに単体テストを行うアレコレ(主にデプロイ面の話)

前書き:SQL Server データベースプロジェクトはとても便利なのだけどSSDTの単体テスト機能は割と辛みがあるでNUnitとかxUnitとかでやりたい

いくつかの案件で、Visual StudioのSQL Server データベースプロジェクト*1(以下、DBプロジェクト)を用いてSQL Server上の各種オブジェクトの開発や構成管理を行っている。その開発・保守に際して、ストアドプロシージャや関数等のいわゆる自動単体テスト*2を行いたい要求があった。

SQL Server Data Tools(以下、SSDT)ではDBプロジェクトの単体テスト機能も提供しており、このSSDTの単体テスト機能はそれなりに便利で手軽ではある……のだが、私は現在ではあまり利用していない。

当初はそれなりに使ってはいたのだが、このSSDTの単体テスト機能の少々使い勝手が微妙な部分が、使い込むにしたがって許容できない判断するに至った。そのため現状はNUnitなどを用いてC#のコードからテスト用のデータベースに接続してテストするという手法に落ち着いている。

テスト対象オブジェクトのデータべースへのデプロイをどうするかという問題

単体テスト自体にSSDTは利用しないものの、DBプロジェクト内のソースをデータベースにデプロイするのは正直とても楽なので、その部分だけ無理やり(?)使っていた。

……いたのだが、紆余曲折あり*3、デプロイにもなるべく……少なくとも単体テストのプロジェクトから直接SSDTのクラスを参照するのは避けたいという思いが強くなった。

そこで代わりの方法を模索することにした。

達成したいポイントは以下だった。

  • SSDT関連のアセンブリに直接依存しない(プロジェクトからアセンブリ参照しない)
  • 事前に手動操作など挟まず単体テスト実行時に自動でDBプロジェクト内のソースを任意のデータベースにデプロイしたい

今回やってみたこと

前置きが長くなったが、これらを解決するために以下を試してみた。

  • 単体テストのセットアップ中に以下を行うことでDBにソースからオブジェクトをデプロイする
    • MSBuildをキックしてDBプロジェクトからdacpacを生成
    • SqlPackage.exeでデータベースに発行
  • MSBuild.exeと SqlPackage.exeを単体テストのSetupから実行するためのこれらのパスの解決を以下の方法で実施
    • 単体テストプロジェクトのビルド時、ビルド後イベントでVSのマクロ変数から拡張機能のインストールパスを取得、テキストファイルに書き出す
    • 単体テストのセットアップ時に前述のテキストファイルを読み出す

【2020.09.16追記】 コマンドラインツールそのものを実行するのではなく、NuGetで提供されているライブラリを利用して同等のことができるのを確認しました。 そのうちブログに書きます。

コマンドラインツールによるビルドとデプロイ

DBプロジェクトはMSBuildでビルドすると普通にdacpacを生成するので、MSBuild.exeのパスさえわかれば特に問題はない。

これについては、先日のエントリ https://kiyokura.hateblo.jp/entry/2020/09/01/114511 を参照。

またSSDTで同時にインストールされるSqlPackage.exeを利用すればdacpacをデータベースへデプロイすることが可能で、これもSqlPackage.exeのパスさえわかれば問題ない。

こちらについては https://kiyokura.hateblo.jp/entry/2020/09/01/154255 を参照。

コマンドラインツールのパスの解決

少し考える必要がったのは、MSBuildやSqlPackgeの実行パスの取得方法である。 設定ファイルなどに個別に書いても良いが、当然実行環境ごとにパスは異なる可能性は当然あり(VS2017以降ではVSのバージョンだけでなくエディションによっても拡張機能インストールパスが異なる)、リポジトリの管理などでも別途考えることが出てくるので避けたかった。

そこで思いついたのがビルドイベント。ここであれでば $(MSBuildBinPath)$(DevEnvDir) などでビルドのタイミングでその環境で有効なパスが取得できるので、例えば以下のようにしてやればあとは単体テスト実行時に読んでやれば良い。

echo $(MSBuildBinPath)\msbuild.exe > "$(TargetDir)msbuildpath.txt"
echo $(DevEnvDir)Extensions\Microsoft\SQLDB\DAC\150\sqlpackage.exe > "$(TargetDir)sqlpackagepath.txt"

サンプルプロジェクト

これらを実際に組み込んだサンプルプロジェクトを以下に置いてたので、興味ある方は見ていただければ。

github.com

(ちなみに上記サンプルでは、データベースには SQL Server LocalDBを利用し、テスト実行時にその都度データベースインスタンスとDBを作り直してまっさらな状態でテストするようにしています)

*1:「Visual StudioのSQL Server データベースプロジェクトとは?」という方は https://docs.microsoft.com/ja-jp/sql/ssdt/project-oriented-offline-database-development?view=sql-server-ver15 や手前みそですが https://www.slideshare.net/kiyokura/sql-server-238387166/kiyokura/sql-server-238387166 など参照していただければと

*2:コードでテストを記述してテストランナーとかで実行するアレ

*3:書くと長くなりそうなので割愛

SqlPackage.exeを使ってdacpacをコマンドラインからデータベースにデプロイする方法

メモ。 dacpacの形になっているSQL Server Databaseを接続文字列を用いてDBサーバにデプロイする方法のメモ。

凡例:

sqlPackage.exe /Action:publish /SourceFile:<source dacpac file> /TargetConnectionString:<destination database connection string>

例(ローカルDBにデプロイ):

sqlPackage.exe /Action:publish /SourceFile:C:\hoge\fuga.dacpac /TargetConnectionString:"Data Source=(localdb)\HogeDbInstance;Initial Catalog=HogeDb;Integrated Security=True;Persist Security Info=False;Pooling=False;"

SqlPackage.exeは、SSDTが入ってる環境だと C:\Program Files (x86)\Microsoft Visual Studio\<Visual Studio Version>\<Visual Studio Edition>\Common7\IDE\Extensions\Microsoft\SQLDB\DAC\<SQL Server Version>\ にインストールされている。 もしくはインストーラーから単体インストール可能。 インストーラー:https://docs.microsoft.com/ja-jp/sql/tools/sqlpackage-download?view=sql-server-ver15

参考: docs.microsoft.com

SQL Serverデータベースプロジェクトをコマンドラインでビルドして任意のパス・名称のdacpacを出力する方法

メモ。 SQL Server データベースプロジェクト(*.sqlproj)からコマンドラインでMsBuild.exeを用いて、任意のパスに任意のファイル名でdacpacを出力するには以下のようにコマンド実行する。 (OutputPathやSqlTargetNameを省略するとプロジェクトファイルで設定されている値で出力される)

凡例:

msbuild.exe <project file name> /p:OutputPath=<directory>;SqlTargetName=<file name>

実行例(C:\hoge\fuga.dacpac が生成される):

msbuild.exe sample.sqlproj /p:OutputPath=c:\hoge\;SqlTargetName=fuga