検索
リンク
タグ
SQL Server
ASP.NET MVC
AngularJS
Visual Studio
Plone
Selenium
AJAX
ASP.NET
JavaScript
PowerShell
Fizz-Buzz
jQuery
C#
ADO.NET Entity Framework
ライトニングトーク
WebMatrix
LINQ
Azure
F#
.NET
カテゴリ
最新の記事
最新のコメント
記事ランキング
最新のトラックバック
以前の記事
2019年 01月 2018年 12月 2018年 11月 2018年 10月 2018年 09月 2018年 08月 2018年 07月 2018年 06月 2018年 05月 2018年 04月 2018年 03月 2018年 02月 2018年 01月 2017年 12月 2017年 11月 2017年 10月 2017年 09月 2017年 08月 2017年 07月 2017年 06月 2017年 05月 2017年 04月 2017年 02月 2017年 01月 2016年 12月 2016年 11月 2016年 10月 2016年 09月 2016年 08月 2016年 07月 2016年 06月 2016年 05月 2016年 04月 2016年 03月 2016年 02月 2016年 01月 2015年 12月 2015年 11月 2015年 10月 2015年 09月 2015年 08月 2015年 07月 2015年 05月 2015年 04月 2015年 03月 2015年 02月 2015年 01月 2014年 12月 2014年 11月 2014年 10月 2014年 09月 2014年 08月 2014年 06月 2014年 04月 2014年 03月 2014年 02月 2014年 01月 2013年 12月 2013年 10月 2013年 09月 2013年 08月 2013年 07月 2013年 06月 2013年 05月 2013年 04月 2013年 03月 2013年 02月 2013年 01月 2012年 12月 2012年 11月 2012年 10月 2012年 09月 2012年 08月 2012年 07月 2012年 06月 2012年 05月 2012年 04月 2012年 03月 2012年 02月 2012年 01月 2011年 12月 2011年 11月 2011年 10月 2011年 09月 2011年 08月 2011年 07月 2011年 06月 2011年 05月 2011年 04月 2011年 03月 2011年 02月 2011年 01月 2010年 12月 2010年 11月 2010年 10月 2010年 09月 2010年 08月 2010年 07月 2010年 06月 2010年 05月 2010年 04月 2010年 03月 2010年 02月 2010年 01月 2009年 12月 2009年 10月 2009年 09月 2009年 07月 2009年 06月 2009年 05月 2009年 04月 2009年 03月 2009年 02月 2009年 01月 2008年 12月 2008年 11月 2008年 10月 2008年 09月 2008年 08月 2008年 07月 2008年 06月 2008年 05月 2008年 04月 2008年 03月 2008年 02月 2008年 01月 2007年 12月 2007年 11月 2007年 04月 2007年 03月 2007年 02月 2007年 01月 2006年 11月 2006年 10月 2006年 09月 2006年 08月 2006年 07月 ファン
ブログジャンル
画像一覧
|
1 2017年 12月 22日
.NET プログラミングにおけるデータベースアクセスを担うライブラリ EntityFramework。
その EntityFramework の、.NET Core にも対応した新世代バージョンが EntityFramwork Core である。 EntityFrmework 及び EntityFrmework Core は、SQL Server 接続用や SQLite 接続用などの "データベースプロバイダ" を介して、実際のデータベースアクセスを行うプロバイダモデルとなっている。 そして EntityFramework Core には In-Memory データベースプロバイダが提供されている。 このデータベースプロバイダは、文字通り、メモリをデータ保存領域とした、すぐに揮発はしてしまうが高速に動作させることができるデータベースプロバイダである。 NuGet パッケージの説明書きにもあるように、もっぱらテスト用途を想定したデータベースプロバイダだ。 この In-Memory データベースプロバイダ、これまで実際に使ってみたことはなかったのだが、今回、機会を得たので、EntityFramework Core を用いたデータベースアクセスを伴うプログラムの単体テストに試用してみた。 実装の様子下記のようなデータベースコンテキストクラスを実装していたとして、using Microsoft.EntityFrameworkCore; データベースコンテキストを、In-Memory データベースプロバイダの使用を指定して構築するには、下記のコードとなる。var option = new DbContextOptionsBuilder<MyDbContext>() 上記コードの最終行、"EnsureCreated()" の呼び出しによって、データベースコンテキストクラスによって示されるモデルのとおりに、In-Memory データベースが構築される。あとはこのデータベースコンテキストオブジェクトを通して、普通に EntityFramework によるデータベースアクセスのコードが実行できる。 実際に 1対多のリレーションを持つようなモデルで、
寿命はプロセス単位、名前が同じなら同じデータベースインスタンスを指すIn-Memory データベースプロバイダを使用する指定のところで、"データベース名" なるものを指定している(下記)。 .UseInMemoryDatabase(databaseName: "MyMemDb") これは In-Memory データベースのインスタンスに付与する名前だそうだ。さすがに使える文字種など制約・規約はあると思うが、しかしそれは別にして、とにかくプログラマが好きな名前を指定してよい。 但し、同じデータベース名を引数に UseInMemoryDatabase() 呼び出しして得たデータベースコンテキストは、同じ "内容" のデータベースを読み書きすることになる。 また、この In-Memory のデータベースは、プロセスが生きている間は生存し続けているっぽい。 この仕組み・仕様をうまく活用すれば、テスト対象の In-Memory データベース常態の初期構築をいちどだけ実行する、といった、単体テスト処理速度のチューニングに使えるのかもしれない。 しかし、同じデータベースインスタンスをテスト実行期間の間、テスト間で共有し続けたら、各テストの前提条件と結果が入り乱れてテストにならないのではないだろうか。 そう考えると、テストごとに異なる In-Memory データベースインスタンスを割り当てたほうが良いのでは、と思った。 ちなみに、テストごとに異なる In-Memory データベースのインスタンスを割り当てる、すなわち別々の異なるデータベース名で初期化するには、自分がすぐに思いつくのは GUID 値を用いる方法である。 .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) こうすることで、とりあえずテストごとに別々の In-Memory データベースインスタンスを使用するようになり、テスト間の干渉なく安心してテストを実装・実行できる。ただ、いちど作られた In-Memory データベースインスタンスを明示的に破棄する方法があるのかないのか、現時点ではわかっていない。 そのため、テスト実行プロセスの期間中、実行されるテストによって次々と In-Memory データベースインスタンスが立てられ、テストによってレコードが追加され、しかし破棄されないとなると、メモリ消費量が大変なことになったりしないか、ちょっと心配である。 トランザクションは効かないあと、In-Memory データベースプロバイダは、データベーストランザクションが使えない。In-Memory データベースプロバイダを設定したデータベースコンテキストで、下記のとおり "BeginTransaction()" 呼び出しを実行すると、 var t = db.Database.BeginTransaction(); この呼び出しで「System.InvalidOperationException : Warning as error exception for warning 'Microsoft.EntityFrameworkCore.Database.Transaction.TransactionIgnoredWarning': Transactions are not supported by the in-memory store.」 という例外が発生してしまう。 上記「In-Memory データベースはトランザクションをサポートしていません」警告を、例外とせずに無視するよう設定することもできる。 下記のとおり、オプション設定するところで "ConfigureWarnings()" 呼び出しを付け加え、上記警告を示す Event ID 値を無視するよう Ignore メソッドで指定する。 var option = new DbContextOptionsBuilder<MyDbContext>() ただし、これで例外を発生することはなくなるが、In-Memory データベースがトランザクションをサポートしていないことには変わりないので注意が必要だ。すなわち、トランザクションのロールバックが実行されても、データベース状態は巻き戻らず、すべての追加/変更は即座にコミット・反映されてしまう。 SQLite の In-Memory データベースさてところで、EntityFramework Core で利用可能なデータベースプロバイダのひとつとして、SQLite 用データベースプロバイダがある。そして、SQLite にも In-Memory データベース機能がある。 https://www.sqlite.org/inmemorydb.html ということで、先述の EntityFramework Core ネイティブの In-Memory データベースプロバイダのときと同様に、単体テスト用途で、SQLite の In-Memory モードを使うこともできる。 データベースコンテキスト構築のコードは下記となる。 var option = new DbContextOptionsBuilder<MyDbContext>() SQLite の In-Memory データベースでも、インスタンスに名前を付けて使うことができるのだが、上記コードのとおり明示的に名前を付けない場合は、接続を開くごとに異なるインスタンスとなるらしい。先に書いたように、この振る舞いは単体テストにはうってつけである。 そのこともあり、EntityFramework Core ネイティブの In-Memory データベースプロバイダ使用のときとは違って、"OpenConnection()" 呼び出しにて明示的に接続を開いている。 また、ちゃんと確認できていないのだが、ドキュメントを読み取った感じでは、接続が閉じられる (本件で言うとデータベースコンテキストが破棄 = Dispose される) と、In-Memory データベースインスタンスも即座に破棄されるっぽい。 テスト実行プロセスの期間中ずっと、メモリ上に居座ることはなさそうな様子だ。 トランザクションもつかえるさすが SQLite というべきか、当然のごとく、In-Memory モードにおいても、データベーストランザクションが機能する。トランザクションを開始して、ロールバックすれば、データベース状態はトランザクション開始時点のままとなるし、コミットすれば永続化される。 まとめEntityFramework Core によるデータベースアクセスを伴うプログラムについて、その単体テスト実装時、揮発性の In-Memory データベースを用いて単体テストを実現する方法としては、以下が挙げられる。
前者は、
個人的には、SQLite の In-Memory モードを採用の方向で考えている。 ▲
by developer-adjust
| 2017-12-22 23:40
| .NET
|
Comments(0)
2013年 06月 27日
ASP.NET Web API と Entity Framework な話。
背景Entity Framework によるモデル実装で、例えばテーマとそれに対する賛同者の投票、みたいな 1対 n の関係のモデルなんかは普通にありると思う。テーマが下記で、 public class Theme {投票が下記みたいな感じ。 public class Vote {そしてさらに、たまにあるのが、投票から投票対象のテーマを逆引きしたいケース。 そのために以下のように投票クラスにも、テーマを返すプロパティを設けることがある。 public class Vote { これでアプリケーションの動作としては問題ないのだが、このテーマオブジェクトを ASP.NET Web API で公開しようとしたときにちょっとした問題が起きる。 public class ThemesController : ApiControllerこの実装で URL /api/themes に GET 要求を投げると、 The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json' といった例外メッセージの System.InvalidOperationException 例外が発生するのだ。 何が起きているのか?原因は循環参照。テーマオブジェクトを JSON または XML にシリアル化するとき、そのテーマオブジェクトの Votes プロパティに含まれる投票オブジェクトも順次シリアル化する。 ところがその投票オブジェクトには、その投票オブジェクト自身のシリアル化の発端となった親のテーマオブジェクトがプロパティとして返されるようになっている。 親をシリアル化開始して、子のシリアル化を始めたら、また親のシリアル化にたどり着き... と無限ループになってしまうわけだ。 Json.NET の仕組みで対抗するこの問題をどう解決したらよいか?まず、JSON 形式にシリアル化する場合だが、そのシリアル化を行うライブラリとして Json.NET が使われている。 そこで Json.NET の仕様に従って、「このプロパティはシリアル化しないでね」と属性で印をつけることで循環参照の輪を断ち切ることができる。 具体的には、子である投票クラスからの、親であるテーマオブジェクトへの参照である Theme プロパティに対して、JsonIgnore という属性をつけるとよい。 public class Vote {こうすれば content-type=application/json で要求された場合は、投票オブジェクトの Theme プロパティはシリアル化されずに正常に動作するようになる。 XML で返す場合しかしこれだけだと、XML 形式で返す場合には相変わらず循環参照による例外が発生することになる。XML 形式にシリアル化するときは Json.NET は使われないのだから、JsonIgnore 属性をつけたところで無意味なのだ。 そこで JsonIgnore 属性はいったん捨てて、WCF プログラミングでも登場した、DataContract 属性と DataMember 属性で、明示的にシリアル化するプロパティに印を付けていく必要がある。 [DataContract]上記はサンプルだからまだいいが、実際にはもっとたくさんのプロパティを備えているのが現実のアプリケーションのコードだと想像される。 そうなると、いちいちすべてのプロパティに DataMember 属性を書きまくらなければならず、骨が折れる上に、見た目もすっきりしない。 ちょっと残念。 content-type=application/json にだけ対応して、content-type=application/xml には例外で返すのでよいなら、前者のとおり、ちょこっと JsonIgnore 属性をつけるだけでもいいかもしれないが。 以上、ASP.NET Web API における循環参照の解決方法。 最後にサンプルのソースコード、プロジェクト一式を GitHub で公開してある。https://github.com/sample-by-jsakamoto/HowToResolveCircularReferenceAtWebAPI 最初のコミットが循環参照による例外を出力するコードで、次に JsonIgnore による解決、最後に DataContract/DataMember による解決、の2つのコミットが積み重なっている。 ご参考まで。 もっとすっきり解決できる方法はないのだろうか? もしご存じだったらご教示いただければありがたい。 2013/06/29追記 @takepara さんの tumblr で参考記事を教えていただいた。 http://takepara.tumblr.com/post/54016328867/asp-net-web-api-developer-adjust なるほど、JSON にシリアル化するときのシリアライザの設定をアプリケーション起動時などで済ませておくと良いようだ。 XML へのシリアル化にも対応するには不足と思われるが、今日び、JSON で事足りることのほうが多いと思うので、こちらの解決策で済ませておくのがよいかもしれない。 ▲
by developer-adjust
| 2013-06-27 22:30
| .NET
|
Comments(0)
1 |
ファン申請 |
||