2012年12月4日

ASP.NETのWCFサービスでDbContextから取得したオブジェクトを返そうとするとエラーになる場合の対策

先日、ASP.NETのWCFサービスでEntityFrameworkのDbContext経由で取得したモデルクラスのオブジェクトを返そうとして、壁にぶつかった。

発生したエラーの内容はこんな感じ。
Test method ServiceTest.CustLoadData threw exception:
System.ServiceModel.CommunicationException:
An error occurred while receiving the HTTP response to http://localhost:60741/MyService.svc.
This could be due to the service endpoint binding not using the HTTP protocol.
This could also be due to an HTTP request context being aborted
by the server (possibly due to the service shutting down).
See server logs for more details.
---> System.Net.WebException: The underlying connection was closed:
An unexpected error occurred on a receive.
---> System.IO.IOException:
Unable to read data from the transport connection:
An existing connection was forcibly closed by the remote host.
上記のエラーはWCFサービスの呼び出し側で表示されるエラーなので実際に起こっているエラーの原因はちょっと違う部分にある。


いろいろデバッグに苦労したけれど、とりあえずどうやらWCFサービス内でオブジェクトを返すためにシリアライズしようとしている所でエラーが起きているらしい、という事が分かった。

原因は、DbContextの動作原理としてPOCOに対する変更追跡と遅延ローディングを実現するために内部的に「動的プロキシクラス」というものを生成している事にあるらしい。

動的に生成されたプロキシクラスはシリアライズ対象に出来る「既知のクラス」として認識されないのでエラーになってしまう様だ。

POCO エンティティの使用 (Entity Framework)
『Windows Communication Foundation (WCF) では、プロキシを直接、シリアル化または逆シリアル化できません。その理由は、DataContractSerializer では既知の型のみのシリアル化または逆シリアル化ができますが、プロキシ型は既知の型ではないためです。』

結局対策としては

this.Configuration.ProxyCreationEnabled = false;

の1行をDbContextクラスのコンストラクタ内に入れる事で解決した。
これでデータ取得時に動的プロキシが作成されずに純粋なPOCOが返って来るので、シリアライズ時に問題が発生しなくなる。


ちなみにMSDN内の下のページには「シリアル化中にProxyDataContractResolver クラスを使用してプロキシ型を POCO 型にマップ」する方法が載っている。でもちょっと試してみたところ上手く行かなかったので今回はパスした。
チュートリアル: WCF による POCO プロキシのシリアル化 (Entity Framework)


他の解決策として、動的プロキシの作成を抑止せずにWCFサービスでオブジェクトを返す前に手動でPOCOにデータを詰め替えるという方法もある。

データを詰め替えるのはAutomapperなどのライブラリを使えば簡単に出来るので、ProxyCreationEnabled を falseにするという解決策を取りたくない場合はそちらでも良いかも知れない。



その他参考になりそうなURL:

Entity Framework 4, WCF & Lazy Loading Tip | .NET Zone


Entity Framework v4 POCO templates: repository returns object of incorrect type - Stack Overflow