FIX: WCF Data Service with Entity Framework Code-First DbContext doesn’t accept updates

Summary

The Entity Framework Code First DbContext doesn’t expose the interfaces to support updates when exposed via WCF Data Services. Attempting to save changes results in a fault with the message "The data source must implement IUpdatable or IDataServiceUpdateProvider to support updates." The fix is to alter your WCF Data Service to expose the DbContext's underlying ObjectContext and to disable proxy generation.

Background

Jesse Liberty and I have stumbled into some frontier country. Our work on a Windows Phone + WCF Data Services + EF Code First + MVC 3 solution for the The Full Stack has put us in that delightful developer place where the combination of two or more pre-release or newly-released bits can feel like you're in primordial-ooze-technical-preview stage. Honestly, it's our job to get there before you do, and we love it.

I've been pushing Entity Framework Code-First to anyone who will listen. My kids are sick of hearing about it... "yes, dad, POCO object, no config, we GET it!"

We'd figured out how to connect all the pieces together so that data in an ASP.NET MVC 3 using SQL CE and EF Code First could expose data to a Windows Phone client via a WCF Data Service. That sounds like a lot of moving parts, but it actually went pretty smoothly once we figured out the steps, as documented here.

The problem - the service was read-only

Our goal is to build out a contact manager application that allows you to quickly store and look up contacts by description, e.g. short, long hair, works for Microsoft, met at MIX 2010.

Our phone client did a fine job of reading the data from the service, but our attempts to save changes back to the service gave some errors that were hard to troubleshoot. Based on some help from Chris "Woody" Woodruff, we changed our update method to stop using batching, and we started seeing the specific error message: "The data source must implement IUpdatable or IDataServiceUpdateProvider to support updates."

That error message led me to a comment on a post by Rowan Miller on using WCF Data Services against a DbContext, which is exactly what we were doing. Rowan points out that if you're going by experience or documentation on using Entity Framework (before Code First), you'd create a simple WCF Data Service and point it at your Context class, and everything would work. However:

Now what if your BlogContext derives from DbContext instead of ObjectContext? In the current CTP4 you can’t just create a DataService of a derived DbContext, although you can expect this to work by the time there is an RTM release.

But there is some good news, DbContext uses ObjectContext under the covers and you can get to the underlying context via a protected member.

Aha, you say - that was for CTP4, and there's a newer release. Read on...

DbContext as a lightweight, convention-based wrapper over ObjectContext

DbContext is really easy to work with. It does the basic things you'd expect from ObjectContext, like tracking changes to your entities, batching changes, etc. The DbContext also adds some other convention-based goodness on top, though, which does things like infer what the database connection should be based on the the entity name, creating the database if it doesn't exist, etc.

However, the DbContext abstracts away / hides some capabilities in the ObjectContext. Usually, those capabilities aren't things you'll miss, but occasionally you will. In this case, we need IUpdatable support, as the MSDN documentation for DataService<T> explains:

The type of the DataService<T> must expose at least one property that returns an entity set that is an IQueryable<T> collection of entity types. This class must also implement the IUpdatable interface to enable updates to be made to entity resources.

The workaround - Expose the DbContext's base ObjectContext

Rowan Miller explains the workaround, which involves two short steps. Fortunately, since he posted that info (valid for CTP4), it's become even easier, since DbContext directly exposes the ObjectContext with your having to write a property to expose it. Prior to EF Code First CTP 5, if you wanted to call into the base ObjectContext, you had to expose the underlying context via a property, like this:

public class BlogContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    public ObjectContext UnderlyingContext
    {
        get { return this.ObjectContext; }
    }
}

In CTP5, the DbContext implements IObjectContextAdapter, which exposes the ObjectContext. So, putting that together, if you want to set up a WCF Data Service that exposes a DbContext named PersonContext, you’ll need to change from this:

public class PersonTestDataService : DataService<PersonContext>
{
    public static void InitializeService(DataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule("*", EntitySetRights.All);
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
    }
}

to this:

public class PersonTestDataService : DataService<ObjectContext>
{
    public static void InitializeService(DataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule("*", EntitySetRights.All);
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
    }

    protected override ObjectContext CreateDataSource()
    {
        var ctx = new PersonContext();
        var objectContext = ((IObjectContextAdapter)ctx).ObjectContext;
        objectContext.ContextOptions.ProxyCreationEnabled = false;
        return objectContext;
    }
}

Here’s the summary of what we did:

  • The DataService is typed as ObjectContext rather than the class which implemented DbContext (PersonContext).
  • We override CreateDataSource to get at the PersonContxt’s underlying ObjectContext, returning the ObjectContext as the result.
  • We disable Proxy Creation, which apparently does something magical. I just got it from Rowan Miller’s post and it works, so I’m not going to complain.

Updating the client proxy

If you’d previously created a client service proxy (via DataSvcUtil) for the service, you’ll need to regenerate it because the ObjectContext based WCF Service now implements IUpdatable and thus exposes some new additional methods. In our test case - a flat object model with one class holding simple strings - the changes to the client proxy class were to set up property change notifications:

  • public event global::System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
  • protected virtual void OnPropertyChanged(string property)

Fun with IObjectContextAdapter

I’ve run into other circumstances where I needed access to the DbContext’s ObjectContext – for instance working with the ObjectStateManager. It’s nice to be able to cast a DbContext to an IObjectContextAdapter to get at the ObjectContext, then go to town with the base ObjectContext.

6 Comments

  • I must be missing something, Jon. Following your example I wind up with a generic ObjectContext that my DataService doesn't recognize as having my entity queryables on it. I get errors trying to do basic GETs against the resources, and digging in it's b/c it doesn't find the types exposed by the DbContext. Are there other changes you had to make that aren't listed in the post?

  • Correction, on POST (to create a new resource) the response is that my type is not valid. Note that for dev I'm doing this w/ EntityRights.All set on "*" so the permissions aren't it, I don't think.

  • Hi Jon, I'm using EntityFramework 4.1 RTW and I dont' see ObjectContext as a member of DbContext. Did they take it out??

  • Hi Jon,

    There are some changes in RTW. ObjectContext is an explicitely implemented IObjectContextAdapter interface member on DbContext.

    So to access it, you need to do this:

    public ObjectContext UnderlyingContext
    {
    get { return ((IObjectContextAdapter)this).ObjectContext; }

    }

  • Hi,Jon. i am wondering Does EntityFramework 4.1 take out ObjectContext as a member of DbContext? i can't see it in DbContext Class.

  • Hi, Jon - I believe I may have the same problem as Paul. After changing my service to DataService and adding the property, the public DbSet properties in my DbContext are no longer available as properties on the DataService.

    What am I missing...?

Comments have been disabled for this content.