きよくらの備忘録

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

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で提供されているライブラリを利用して同等のことができるのを確認しました。 そのうちブログに書きます。

書きました!https://kiyokura.hateblo.jp/entry/2020/09/16/230331

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

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

ThinkCentre M75q-1 Tiny にSSDとメモリを増設した

kiyokura.hateblo.jp

の続編。

SSDとメモリを購入

先日購入した構成で内蔵SATA 2.5インチベイとSODIMMスロットが一つ空いていたため、せっかくなので増設することにした。前回のエントリで『後々に適当なものを見繕って増設予定』などと書いている割に結局すぐ買ってしまったわけだが、これは以下を考慮した結果「どうせ後から買うつもりなら早い方がよい」という結論に達したからである*1

  • Cドライブの容量が128GBと少々控えめ
  • うっかりCドライブが容量不足でWindows Updateを失敗するようになったりでもしたら面倒で嫌
  • まだOffice他ほとんどのアプリケーションをインストールしていない
  • Office等重量級のアプリケーションを後で別ドライブに移すのはまた面倒で嫌

なお、今回購入したのは以下。 最近のPCパーツ事情がよくわかっていなので特にメーカー等はこだわっておらず、価格と規格、自分が抱いてるメーカーの印象等で適当にチョイスした。

  • メモリ
    • Kingston KCP426SS8/8 : DDR4 2666MHz 8GB / Non-ECC Unbuffered SODIMM / CL19
    • 購入価格 : 4,673円
    • 参考 : ASIN:B07D3NG9MG
  • SSD
    • Western Digital WDS500G2B0A-EC : SSD / 500GB/ WD Blue / 2.5インチ / SATA III 6 Gb/s
    • 購入価格 : 8,565円
    • 参考 : ASIN:B07SM7YJ4F

分解と取付

この程度の取り付けに際してはとてもメンテナンス性の良いケースという印象で、さほど困ったり作業し辛かったような部分は無かった。ただケースの開け方等*2で一瞬悩んだ部分があったので今後のために一応メモしておく。

手順 1. 背面のビスを抜く

背面パネル上のビスを抜く。今回は購入時にツールレスシャーシを選んで無いためドライバーが必要。 f:id:kiyokura:20200408155815j:plain なお今回ドライバーを利用するのはこのビスのみ。

手順 2. 天面パネルをフロントベゼルごと前にスライド

天面側のパネルをフロントベゼルごと前方にスライドして外す。 f:id:kiyokura:20200408160014j:plain

上からみた内部はこんな感じ。2.5インチドライブはのドライブベイに取り付ける。 f:id:kiyokura:20200408160041j:plain

手順 3. 2.5インチドライブベイを取り外す

ノッチ(?)のようになっている部分を矢印方向に押してドライブベイを外す。 f:id:kiyokura:20200408160042j:plain この時テープでコネクタ付きのフィルムケーブルがまとめられベイに固定されているので破損させないように注意しながらテープも外す。 f:id:kiyokura:20200408160624j:plain

手順 4. 2.5インチドライブベイにSSDを取り付け、ケーブルを接続して組み付ける

ツールレスで取り付けれるようになっている……が、このピンをどう扱うのが正式な手順なのかよくわからなかったので、取付ベイの樹脂の柔軟性を信じて無理やりはめた(誰か真のやり方教えてください)。 ケーブルを挿し、元通りドライブベイを組み付けて、SSDの取り付けは完了。 f:id:kiyokura:20200408162214j:plain

手順 4 . 裏返し底面パネルをスライドさせてはずす

メモリ取り付けのスロットは底面側にあるので、裏返して底面パネルをスライドして外し内部を露出させる。 f:id:kiyokura:20200408160355j:plain f:id:kiyokura:20200408160405j:plain なお出荷時から刺さっていたメモリはSAMSUNG製だった。

手順 5. SODIMMスロットにメモリを取り付ける

空きスロットにそのまま取付。 f:id:kiyokura:20200408162239j:plain

手順 6. 元通り組み付けて完了

特に問題なく取付作業は終了。シンプルだが無理なく構成されている印象だった。

結果

こんな感じになりました。 f:id:kiyokura:20200409112047p:plain f:id:kiyokura:20200409112100p:plain

一旦はこれでしばらく(というかずっと)使う予定。もしGPUのパフォーマンスに不満がでたら135仕様ACアダプタを買うかもしれませんが、当分なさそうかな……。

*1:なのでメモリはどちらでもよかったが、どうせケース開けるなら同時にやってしまおうと思ったため同時に購入することとした。どのみちそんなに高くなかったし。

*2:具体的にはビスを外した後のスライドのさせ方で、どことどこが固定されていてどこがどの方向にスライドするか等