Friday, 23 March 2012

Bad times with Entity Framework and WCF


I recently wanted to make some data from a .NET MVC website I built some time ago available externally via a web service. After a little research I decided WCF was the way to go and set about creating a test service class that would return some data which was accessed using the Entity Framework (unfortunately my ORM of choice at the time). It was more difficult than I expected. 

Setup

My project uses .NET MVC 3, Entity Framework and Ninject amongst other things. The first job was to make sure I could inject the required service classes into my WCF service. Fortunately this was a piece of cake thanks to this excellent blog post. I'm not going to go into the WCF configuration because, to be honest, my knowledge of WCF is rubbish.

Problems sighted over the starboard bow

Setting aside my total lack of experience with WCF the first issue I encountered was that some of my queries worked perfectly while others failed with the mysterious error message "The underlying connection was closed: The connection was closed unexpectedly". After much Googling and many dead ends I eventually stumbled upon a Stack Overflow post which suggested checking the event viewer. Low and behold I find a considerably more specific error message:

System.ServiceModel.CommunicationException: There was an error while trying to serialize parameter http://www.yoursite.co.uk:MethodCall. The InnerException message was 'Type 'System.Data.Entity.DynamicProxies.SomeType_795A7B31EA6BCD0D7B9CFE73379DD55F309C0BEE00A295CAF276B939BB5BF4E6' with data contract name 'SomeType_795A7B31EA6BCD0D7B9CFE73379DD55F309C0BEE00A295CAF276B939BB5BF4E6:http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies' is not expected. 

Further Googling of this error revealed the problem. Lazy loading. Entity Framework supports lazy loading by default, provided your navigation properties are virtual. Unfortunately WCF does not support Entity Frameworks lazy loading. What a pain in the ass. It turns out that to disable lazy loading the ProxyCreationEnabled property of DbContext must be set to false, which leads to further difficulties. With lazy loading disabled any required navigation properties must be eagerly loaded, which unfortunately isn't possible by default. The only way that I am aware of to do this using Entity Framework is by calling the Include method explicitly for any navigation properties you want to return.

A solution

I found a solution, albeit one that was very specific to my needs. Firstly I created two separate database context classes that extended DbContext. One to be used for the web service, which set ProxyCreationEnabled to false and one for the WCF web service with ProxyCreationEnabled set to true. Fortunately using Ninject this was pretty straight forward. Secondly was the eager loading problem. I decided it would be almost impossible to specify exactly which properties needed to be eagerly loaded for each query, so the only other option I could think of was to eagerly load all the navigation properties. There are problems with this solution. It will likely return more data than is required and I'm only aware of how to load one level of navigation properties, which could also cause problems. By this I mean if entity A has navigation properties B and C, when returning a list of As, I can eagerly load properties B and C. If B and C have their own navigation properties they will not be loaded. To achieve this kind of eager loading I created a method which used reflection to get all the public properties from an entity and return a list of those which were navigation properties (in my case navigation properties could be identified by a common base class). Each of the navigation properties in the list could then be passed into the Include method to tell Entity Framework to eagerly load each property. After making these changes all my queries worked perfectly. Huzzah! 
 
As pleased as I was to get this working, to be honest I'm still not one hundred percent convinced that this solution will hold up. Unfortunately I can't think of a better one. Oh well, time will tell I suppose.



No comments:

Post a comment