The Problem
I Like the Odata support that you get from the EF provider. It’s easy to set up and it supports updating write out of the box. To good to be true? Well, yes. Sort of any way. It’s easy to get WOWed by all the helloworld (and a bit more complex) examples you can find on the web. There are some disadvantes (in my opinion) to the procedure.
In particular the tight coupling to the data model / context is a problem to me.Why? Well what if you have constructed an Odata services and you need to change it? To change the structure of the service you need to change the database. If the service is uses you can not do that at once.
What do you do when you need to create a new version of this nice Odata service? I have searched the web for the answer to this question, but I did not find it. There is advice on when you schould create a new version of your service, how to call it but never how to actually do it. Since I had a business requirement to provide a new version of my web service I tried to crack this nut on my own…
The options
There are many ways to skin a goat. And there are many ways I tried to create a second version of my Odata service. Here’s a summary of my attempts:
- Create an extra abstraction using the reflection provider. I was thinking of a setup like this:
- DB -> EF datacontext -> Reflection datacontext version 1 -> WCF Odata service
- DB -> EF datacontext -> Reflection datacontext version 2 -> WCF Odata service
- I wanted to use the reflecion provider to only expose what was agreed on for each version of the service and the EF context to provide acces to my latest and greatest datamodel. Unfortunatly I could not get it to work. The problems:
- Multiple versions of the same class (altough in different namespaces of course) where confusing EF.
- Some linq operations to transform the schape of my service wheren’t support by linq to sql.
- Altough I liked the abstrion I did not like the fact that I lost all update facilities and would now have to provide this code myself (did not try that because i and ii made read-only already impossible).
- Create a new application with the new version.
- This would work but require a second database (not feasable for me) or at least a EF context that operated on a view (probably possible but I am not an experienced sql server administrator and decided to come back to this option if nesecary.
- Create two EF datacontext that targeted the same database (a bit a like a code version of a view).
- I Liked this idea because it migt mean I will not have to come up with code to handle updates, have just one database and will not have to learn new sql server “tricks”. The good news is I got this scenario to work. The bad news? lots of things whent wrong, hence this post 🙂
A Solution
For starters, lets make clear what my goal was: I needed to versions of an Odata service to give my users a time frame to transiton from the old version to the new:
- //myApplication.MyDomain.something/Service/v1/MyOdataService.svc : provides access to the current version of my service
- //myApplication.MyDomain.something/Service/v2/MyOdataService.svc : provide access to the next version of my service.
Each wcf service will get it’s own data context. This data context will be mapped to dto classes that represent the entities that will be used in the service. The mapping to these object will not by automatic. I will code them using the fluent api to ensure the right tables and fields are mapped to my objects.
One extra data context will be used to provide acces to the latest version of the database. This context will be used to generate database updates and provide access to the data from a mvc website.
There is a problem with this setup. Some classes are represented three times (v1, v2 and “current” and this confuses entity framework because namespaces are ignored by the db context. It will generate errors complaining that it doesn’t now how to choose between the class definitions. Microsoft claims this behaviour is intendend to make it more easy for developers to use EF to get started. To me a maintainable solutions seems more importand then an easy start but I might be missing the clue here…
After reading about a “loadFromAssembly” method on the MetaDataWorkspace property of System.Data.Entity I got hope that using different assemblies might help acommodating my scenario of three dbcontexts over the same set of db tables. And it did!!
The way I solved the problem is by creating three seperate projects (and assemblies):
- MyMvcWebSite
This projects containts the website and the current version of my object model. The datacontext is used to migrate the database and server data to my website only.
I also has a folder named “Services”. This folder contains the dataservice class for each version of my webservice. But ONLY the class itself and NOT the db context. It now has two classes:
- MyOdataServiceV1.svc.cs : this class references the db context of version 1
- MyOdataServiceV2.svc.cs : this class references the db context of version 2
Apart from these two classes theres only one (but vital) change to be made for each (new) version of the service:
In the global.asax, during the Application_Start() I have added the following line to prevent the service data context from medeling (updating) the database and form verifing the consistency between the database and the model (there not consitent of course):
Database.SetInitializer<ServiceV1.MyDbContext>(null);
Database.SetInitializer<ServiceV2.MyDbContext>(null);
Probably there is a Database.SetInitializer(MyRealDbContextInitializer) as well. If it’s in another place, that place will be suitable as well.
- ServiceV1
This class libary containts a reference to EF, the defintion of the dto’s of the version 1 service a datacontext (ServiceV1.MyDbContext) and the mapping from the db model to the service model. Because the same technique and conventions are used very little mapping is required. I needed to map a renamed column, but other then that the conventions worked nicely for me.
- ServiceV2
This class library is identical to the ServiceV1, but with slightly different dto’s and a sligtly diffrent mapping.
Does it work?
It certenly seems like it. We are still in test but we will probably go live with this version soon. If it proves to be stable on this one service we will apply it to others as well. The scary thing is that I have not found Microsoft documentation describing this behaviour (only searching voor classes with the “right” name in the assembly of the dbContext or a referenced one) so an new version of EF/WCF might spoil the fun…
A Question?
If you have any experience serving more then one version of a ODATA/EF/WCF feed and think my way of dealing with it is bad or you just know an even better one. Please let me know.