SQL Server データベース プロジェクトを使用してDBのリファクタリングを行う
はじめに
明けましておめでとうございます。本年もよろしくお願いいたします。去年はあまり更新できなかったので、今年はもっと増やしていきたいですね。
さて、Visual Studio で作成できるプロジェクトの中には、「SQL Server データベース プロジェクト」なんてものがあります。
これを利用すると、SQLのDDL文(Create Table等)やストアドプロシージャをプロジェクトとして管理することが出来ます。
プロジェクトをバージョン管理システムに追加すれば、変更履歴の管理も可能。
DDLの新規追加・編集・削除などプロジェクトに加えた変更を、画面操作でDBに反映する!なんてことも出来てしまう。
以下のブログで、画面つきの詳しい説明がされています。
2014年の記事だけど、Visual Studio 2017 でも同様に操作可能です。
上記記事でも取り上げていますが、以下の点がとても便利です。
プロジェクトを公開して、データベースを作成・更新する
テーブルスキーマの比較を行い、同期する
複数の発行先を切り替えることが出来る
これがあれば、Code First Migrations をわざわざ使う必要は無いですね。
DBのリファクタリングを行う
これだけだとタイトルに偽りありになってしまうので、自分の行っているリファクタリング手順を載せておきます。既にDBが存在する前提です。
ローカルPCでSQL Serverを実行し、DBを作成してSQLを実行してテーブルを作成する
ローカルPCのDBとデータベースプロジェクトのスキーマ比較を行って、データベースプロジェクトを更新
Entity Framework を使用した開発を行っている場合は、さらに以下を行います。
データベースからのCode First を行い、Models配下のクラス、DBContextクラスを更新する
アプリケーションを起動し、LocalDB の再作成を行う
動作確認を行った後、バージョン管理システムに登録し、テスト環境に反映します。
変更内容をコミットして、プルリクエストを作成し、レビューを行う
マージされたら、データベースプロジェクトを公開し、テスト環境のDBに反映する
アプリケーションプロジェクトを公開し、テスト環境へデプロイする
以上です。
(小ネタ)Entity Framework でdbo以外のスキーマを使用する
Entity FrameworkのCode FirstでDBを作成すると、テーブルはdboスキーマで作成されます。今回スキーマを分ける必要が出てきたので調べました。非常に簡単に設定できたので小ネタです。
Entity Frameworkでdbo以外のスキーマを使用するには、モデルクラスのクラス宣言で設定するTable属性のSchemaプロパティにスキーマ名を設定する。これだけです。
ソースコードで表現するとこんな感じです。testスキーマに"雑誌"テーブルが作成されます。
[Table("雑誌", Schema = "test")] public class Journal { [Key] [DatabaseGenerated(DatabaseGeneratedOption.None)] public int ID { get; set; } }
以上です。
データベースからドメインモデルを作るのも有りな気がしてきた
Entity Framework 6が発表された当時は、Code Firstが盛んに宣伝されていたと思う。Code First以前はデザイナーで操作できると言っても自動生成されるxmlファイルの差分を見るのは辛そうだった。
そこでEntity Frameworkを使い始めた頃はCode Firstを使用していたのだが、ここ最近のプロジェクトではデータベースを先に設計して、そこからドメインモデルを作るやり方で進めていて困らなかったので、それで良い気がしてきた。
データベースからドメインモデルを作るやり方だが、ADO.NET Entity Data Modelの作成時に「データベースからのCode First」を使うと、テーブルに対応したCode Firstのモデルクラスを作成してくれる。
DB設計をしっかりとやり出すと、Entity Framework 6のCode Firstはデータベース構造を100%再現できているわけではないことがわかったので、気になった点を以下に書く。(もしかしたら設定があるのかもしれないが見つけられなかった)
- カラムの初期値が設定できない。(日付カラムとかで使いたい場合がある)
- DB初期化の際にモデルのstringに空文字を設定しても、
nullと扱われてしまいNot Nullのカラムに設定できない。(設計上、Not Nullカラムに空文字を入れるのはどうなの?というのはひとまず置いておく)
Required属性にAllowEmptyStringsプロパティがあった。これをtrueにすれば良かった。 - テーブルに設定されている主キー以外のインデックスは、ドメインモデル作成時に無視される。作成したソースコードを修正して、後から設定することは可能。
- DBのビューからドメインモデルを作ると、ナビゲーションプロパティが作られない。元がビューなので正しい動きなのだろうが、気になった。
テーブルが変更になったら同じ名前で作成することでクラスが上書きされる。ソースコードのバージョン管理をしていれば、前のバージョンとの比較や元に戻すことも容易である。「データベースからのCode First」を使ったことがない方は一度試してみるのをおススメする。
開発環境:
Windows 10 Pro
VisualStudio 2017 Professional
Entity Framework 6.1.3
ユニットテストでのDBトランザクションのロールバックを共通化する
以前こんな記事を書きましたが、
毎回テストメソッドの中で using 文やデータ投入処理を書くのが面倒になってきたので、テストフレームワーク(MSTest)の機能を使って TestInitialize 属性でDB接続、データ投入を行い、TestCleanup 属性でDB切断、ロールバックを行うようにします。
ソースコードは以下のようになりました。
[TestClass] public class DatabaseAccessTest { private TestContext context; private DbContextTransaction transaction; [TestInitialize] public void SetUp() { context = new TestContext(); transaction = context.Database.BeginTransaction(); // トランザクションを使用する // Usersテーブルの初期化,他のテーブルは残す context.Users.RemoveRange(context.Users); context.SaveChanges(); // テストデータの追加を記述する ... } [TestCleanup] public void TearDown() { transaction.Dispose(); // 終了時にロールバックする context.Dispose(); } [TestMethod] public void ConnectionTest() { // テストの実施を記述する ... } }
テストメソッドがスッキリして良い感じです。
開発環境:
Windows 10 Pro
VisualStudio 2017 Professional
Entity Framework 6.1.3
MSTest 1.2
Entity Framework で楽観的同時実行制御
基本的な内容は以下の書籍に書かれているので、今回はもう少し踏み込んだ話です。 www.shuwasystem.co.jp
上記書籍の5-4-4(236ページ)では、バージョン管理用のプロパティ名と付与する属性名の両方をTimestampとしていますが、どちらを指しているのかわかりにくいので以下ではプロパティ名を「RowVersion」としています。
書籍の通りに、コントローラーの引数にRowVersionプロパティを指定して、Models配下のクラス(データモデル)にBindすると楽観的同時実行制御が行われます。
しかし、データモデルとは別に画面表示用クラス(View Model)を用意していて、オブジェクトの詰め替え作業を行っている場合は、中身を理解して行わないと正しく動作しないため、調べた内容を記述します。
newしたオブジェクトは、Updateで自動判定
プログラム上でnewした(Entity Frameworkによって管理されていない)オブジェクトの場合は、rowversion型のプロパティに値を設定しておけば、Update時に自動的に判定して処理してくれます。
Entity Frameworkから取得したオブジェクトは工夫が必要
Entity FrameworkからFind等で取得したオブジェクトの場合は、以下のようにEntryメソッドの返り値のオブジェクトに対して値を設定する必要があります。
var departmentToUpdate = context.Departments.Find(id); //Entity Frameworkで管理されたオブジェクト context.Entry(departmentToUpdate).OriginalValues["RowVersion"] = rowVersion; //rowVersionはbyte[]型のパラメータ
当然ですが、どちらの場合でもSaveChangesメソッドが呼ばれた時に更新処理が行われます。
以上です。
(小ネタ)Chaining AssertionのParameterized TestでどのTest Caseで失敗したかを記録する
Chaining Assertionでは、Parameterized Testがサポートされているが、テストが失敗した時のメッセージにはどのケースで失敗したかの情報は含まれていないため、多くのテストケースを試す場合は以下のように工夫すると良い感じです。
//公式サンプルコードを一部修正 [TestClass] public class UnitTest { public TestContext TestContext { get; set; } [TestMethod] [TestCase(1, 1, 2, 3)] //第1引数を追加 [TestCase(2, 10, 20, 30)] //第1引数を追加 [TestCase(3, 100, 200, 300)] //第1引数を追加 public void TestMethod2() { TestContext.Run((int i, int x, int y, int z) => { Console.WriteLine($"ケースNo.{i:D2} x={x}, y={y}, z={z}") //第1引数をケースNo.としてコンソール出力 (x + y).Is(z); (x + y + z).Is(i => i < 1000); }); } }
テスト結果の出力画面を表示すると、テストが失敗したところでコンソール出力が終了しているので、どこで失敗したかがわかるようになります。
実行環境
- Visual Studio 2015 Professional
- ChainingAssertion 1.7.1.0
C#でExcelファイルを扱うライブラリごとのファイルクローズ方法(EPPlus、NPOI)
C#(ASP.NET MVC)でExcelファイルを扱う方法を調べていて、EPPlusが良さそうだったので簡単なプログラムを作成して動作を確認した。しばらく経って実際に使用する時になって、ファイル形式が .xls であることが判明。
EPPlusでは .xls のファイルは読み込めないため、NPOIについての調べ始めたところ、NPOIのサンプルがコンソールアプリケーションばかりでファイルストリームのクローズについてあまり書かれていなかったので、EPPlusと並べて記録しておきます。
EPPlus
EPPlusでは以下のように実装していました。dataは、コントローラのアクションメソッドで受け取ったHttpPostedFileBaseクラスのインスタンスです。ExcelPackageがIDisposableインターフェイスを実装しているのでスマートに書けます。
using (ExcelPackage inputFile = new ExcelPackage(data.InputStream)) { ExcelWorksheet sheet = inputFile.Workbook.Worksheets["Sheet1"]; // ...以下何らかの処理 }
NPOI
NPOIも、WorkbookFactoryクラスのCreateメソッドや、HSSFWorkbookクラスのコンストラクタ等でストリームを受け取ることが出来るのですが、メソッドの戻り値であるIWorkbookがIDisposableインターフェイスを実装していないので、usingを使用することが出来ません。
さらに調べていくと、NPOIのGitHubリポジトリのexamplesフォルダに似たようなサンプルがあったので、その方法を使用します。
IWorkbook book; using (Stream fs = data.InputStream) { book = WorkbookFactory.Create(fs); } ISheet sheet = book.GetSheet("Sheet1"); // ...以下何らかの処理
以上です。