RIA services and foreign key relations in your entity model
In december Roy Cornelissen, Marcel de Vries and I did a presentation about mobile development using C#. As part of the presentation we did demo’s that showed off parts of the Waar Is I app. It was a fun thing to show and the app simply begged to be finished. So we’re now hard at work rebuilding some parts of the app to improve it. The end result will hopefully be a useful app that a lot of people will download and use in their everyday life.
While rebuilding the domain model of the app I discovered some specific behavior in the combination WCF RIA services and Entity framework. As it turns out RIA services needs a different approach to relationships when creating a model than you would like to. In this post I will show you what’s going on with relations in entity models and RIA services and how you can fix a problematic model so that it works with WCF RIA services.
How it all got started
Instead of going head-first into the database we decided that it might be good idea to create a model first. So what we did was, create a new entity model in VS2010 and add a couple of entities to it. The image below gives you an idea how this looks in VS2010 (No, it’s not the actual model).
The important bit of this model are the navigation properties. In the database that is generated from this model these are translated to a foreign key relation between Person and Address. You don’t see that in the model, which we thought is a good thing.
All looked good with the model so my colleague checked it in and one evening later I created a new domain service on top of the model to make it available in the Silverlight frontend. At this point it all still works and you get a class generated that looks a bit like this:
namespace SilverlightApplication2.Web.Services { using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Data; using System.Linq; using System.ServiceModel.DomainServices.EntityFramework; using System.ServiceModel.DomainServices.Hosting; using System.ServiceModel.DomainServices.Server; using SilverlightApplication2.Web.Models; [EnableClientAccess()] public class AddressBookDomainService : LinqToEntitiesDomainService<SampleModelContainer> { public IQueryable<Address> GetAddresses() { return this.ObjectContext.Addresses; } public void InsertAddress(Address address) { if ((address.EntityState != EntityState.Detached)) { this.ObjectContext.ObjectStateManager.ChangeObjectState(address, EntityState.Added); } else { this.ObjectContext.Addresses.AddObject(address); } } public void UpdateAddress(Address currentAddress) { this.ObjectContext.Addresses.AttachAsModified(currentAddress, this.ChangeSet.GetOriginal(currentAddress)); } public void DeleteAddress(Address address) { if ((address.EntityState != EntityState.Detached)) { this.ObjectContext.ObjectStateManager.ChangeObjectState(address, EntityState.Deleted); } else { this.ObjectContext.Addresses.Attach(address); this.ObjectContext.Addresses.DeleteObject(address); } } public IQueryable<Person> GetPeople() { return this.ObjectContext.People; } public void InsertPerson(Person person) { if ((person.EntityState != EntityState.Detached)) { this.ObjectContext.ObjectStateManager.ChangeObjectState(person, EntityState.Added); } else { this.ObjectContext.People.AddObject(person); } } public void UpdatePerson(Person currentPerson) { this.ObjectContext.People.AttachAsModified(currentPerson, this.ChangeSet.GetOriginal(currentPerson)); } public void DeletePerson(Person person) { if ((person.EntityState != EntityState.Detached)) { this.ObjectContext.ObjectStateManager.ChangeObjectState(person, EntityState.Deleted); } else { this.ObjectContext.People.Attach(person); this.ObjectContext.People.DeleteObject(person); } } } }
With that done I pressed Ctrl+Shift+B and assumed (Yes, you know where this goes) that it compiled and I could go ahead and build the frontend around the service. I was wrong. Instead of compiling Visual Studio decided that this was a good moment to give me the following error:
Unable to retrieve association information for association ‘SampleModel.PersonAddress’. Only models that include foreign key information are supported. See Entity Framework documentation for details on creating models that include foreign key information.
Yep, this is exactly how it felt. You know you’re going to be fast with this kind of technology, but somehow Murphy always finds a way to stop you. (A bit of drama is good, right?)
Fixing it
Appearantly RIA services doesn’t work with relations that don’t have their foreign key columns exposed. To understand what that means I need to explain a little more about the entity model and what you can do with relationships.
There are two ways in which you can define relationships inside an entity model. The first technique is to create relations using navigation properties only. This is the technique that you will use by default when you start out with a model and generate a database from that. The other way to create relationships in your model is by creating them using exposed foreign keys. This means that the field in the database that represents the foreign key is mapped in the model as a property.
So how do you get from the first type of relationshipmapping to the second type? The first thing you need to do is to map the foreign key properties. This is done by opening up the mapping window. Select View >Other windows > Entity Model Mapping Details.
Using this window you can modify how the entity that you selected in the model is mapped to the storage model (The database). To map the foreign key column, you first need to add a new Scalar property to the entity. Once you have created the new property you can select it in the mapping window and map it to the foreign key column in the database. Do this for every relation that is part of the model.
The next step in converting the relations is to create foreign key constraints in the model. This is done by double clicking on a relation. Visual Studio 2010 will display the following dialog.
In this dialog you need to select the entity that is the principal in the constraint. The term principal is a bit unclear, but it means that the entity you select for this role is the one that contains the primary key for the relation. Once you have selected an entity the dialog will automatically select the entity that represents the other end of the relation. After that you can select the property that is the foreign key and click OK to accept the settings.
Be aware that at this point the model is very very broken. You can’t compile it because the entity model tooling doesn’t know which way you’re going. It will tell you so when you do try to compile.
The final step in the conversion is to select the entity and remove the mapping for the relation. This is done through the mapping details window. It will show a light gray text telling you that it’s broken. Simply click the link to fix the problem.
Now that the relations in the model are changed to relations with exposed foreign keys you should be able to compile the application and run it.
Conclusion
The way RIA services handles some situations still surprises me from time to time. Things that should work don’t always work. I hope the information helps you prevent the problems we had while building the Waar Is I domain service.