Entity Framework 4.1: Code First and WCF Data Services
Note: The information provided in this post is now maintained in the TechNet Wiki topic: Using Entity Framework 4.1 Code First with WCF Data Services. |
Now that Entity Framework 4.1 is released, I decided that it was past-time for me to try to implement a data service by using the Code First functionality in Entity Framework 4.1. Code First is designed to be super easy; as already mentioned by ScottGu, it will even autogenerate a database for you, including connection strings…all by convention. Code First in EF 4.1 is, in fact, so different from the previous incarnations of Entity Framework, that Scott Hanselman famously dubbed a preview of this release “magic unicorn,” a name which folks ran with. The unicorn, moves Entity Framework way more toward the object-end of the ORM world. However, by relying solely on 1:1 “by convention” mapping, Code First definitely does not take advantage of much of the great benefits of the Entity Framework, which is the powerful conceptual-to-store mapping functionality of the EDMX. IMHO, the real reason that so people like this new version is that with its new DbContext and DbSet(Of TEntity) objects, validation, and the extensive use of attributes for any not-by-convention mappings, Code First in EF 4.1 ends-up looking very much like LINQ to SQL (still the darling Microsoft ORM of many programmers). There are even new fluent APIs, which can be used to configure a data model at runtime.
Bottom line: If you like how LINQ to SQL works (or nHibernate with its fluent APIs), you will probably also like Code First in Entity Framework 4.1.
However, rather than letting the unicorn do its magical thing, I opted to instead get Code First working with an existing database, Northwind. (Not because I thought Code First was too easy, but I just didn’t want to have to invent a bunch of new data—and I am very familiar Northwind, especially my favorite customer 'ALFKI'.) Not the most interesting demonstration, but I did get Code First working with some LINQ to SQL-style mapping, of which the self-referencing association on Employees was the most challenging). If you want to see how the Code First data classes look, download the Code First Northwind Data Service project from the code gallery on MSDN.
Now on to the data service…
Rowan Miller posted some early information on how define a data service provider using a pre-release version EF 4.1. However, the EF folks made some minor improvements before the final release, so the required customizations for WCF Data Services (the .NET Framework 4 version) are a bit different now that EF 4.1 is officially released. This is what I’m going to demonstrate in the rest of this post. (Note that the upcoming release of WCF Data Services that supports OData v3 natively supports DbContext as an Entity Framework data service provider, so customization of the data service will not be required in the next version.)
Here is what I did to get my EF 4.1 RTM Code First Northwind data model working with WCF Data Services:
Create a new ASP.NET Web application (even an empty project works)
Install EF 4.1
You can get the final RTM of “magic unicorn” from the Microsoft Download Center.Add reference to EntityFramework.dll
This is EF 4.1 assembly.Create the set of “plain-old CLR object” (POCO) Northwind classes
I basically followed ScottGu’s example by creating a 1:1 mapping for the Employees, Orders, Order Details, Customers, and Products tables in Northwind. OK, not exactly 1:1 since I didn’t want pluralized my POCO type names, so I had to use mapping attributes. Again, you download the project from MSDN Code Gallery to see how I did this.Add a reference to System.ComponentModel.DataAnnotations
This assembly defines many of the mapping attributes that I needed to map my POCO classes to the Northwind database.Define a context class that inherits from DbContext (I called mine NorthwindContext), which exposes properties that return typed-DbSetproperties for each of my POCO classes (entity sets):
public class NorthwindContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Employee> Employees { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderDetail> OrderDetails { get; set; }
public DbSet<Product> Products { get; set; }
}Add the Northwind connection string (NorthwindContext) to the Web.config file.
This is a named connection string, which is basically just a SqlClient connection to the Northwind database. This should point to the data source that is the SQL Server instance running the Northwind database.Add a new item to the project, which is a new WCF Data Services item template. Detailed steps on how to do this are shown in the WCF Data Services quickstart. Here’s what my data service definition looks like:
public class Northwind : DataService<ObjectContext>
{
// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
// We need to set these explicitly to avoid
// returning an extra EdmMetadatas entity set.
config.SetEntitySetAccessRule("Customers", EntitySetRights.AllRead);
config.SetEntitySetAccessRule("Employees", EntitySetRights.AllRead);
config.SetEntitySetAccessRule("Orders", EntitySetRights.All);
config.SetEntitySetAccessRule("OrderDetails", EntitySetRights.All);
config.SetEntitySetAccessRule("Products", EntitySetRights.AllRead);config.DataServiceBehavior.MaxProtocolVersion =
DataServiceProtocolVersion.V2;
}
}Note that the type of the data service is ObjectContext and not NorthwindContext; there is a good reason for this. Basically, the current released version of WCF Data Services doesn’t recognize DbContext as an Entity Framework provider, so we need to get the base ObjectContext class to create the data source.
Override the CreateDataSource method to manually get the ObjectContextto provide to the data services runtime:
// We must override CreateDataSource to manually return an ObjectContext,
// otherwise the runtime tries to use the built-in reflection provider
// instead the Entity Framework provider.
protected override ObjectContext CreateDataSource()
{
NorthwindContext nw = new NorthwindContext();
// Get the underlying ObjectContext for the DbContext.
var context = ((IObjectContextAdapter)nw).ObjectContext;
context.ContextOptions.ProxyCreationEnabled = false;
// Return the underlying context.
return context;
}
From here, I just published the data service and I was able to access Northwind data using Code First as if the project had instead used an .edmx-based Entity Framework data model.
Cheers,
Glenn Gailey
WCF Data Services User Experience
Microsoft Corporation
Comments
Anonymous
June 17, 2011
Thanks, your post solved my problem.Anonymous
August 25, 2011
i am getting the following error 'System.Data.Objects.ObjectContext' does not contain a definition for 'Address' and no extension method 'Address' accepting a first argument of type 'System.Data.Objects.ObjectContext' could be found (are you missing a using directive or an assembly reference?) below is my code. using System; using System.Collections.Generic; using System.Data.Services; using System.Data.Services.Common; using System.Linq; using System.ServiceModel.Web; using System.Web; using System.Data.Services.Client; using System.Data.Objects; using System.Data.Entity.Infrastructure; using CodeFirstMVC.Models; namespace eGate.BackOffice.WebClient.WCF_Services { public class WcfDataService1 : DataService<ObjectContext> { // This method is called only once to initialize service-wide policies. public static void InitializeService(DataServiceConfiguration config) { config.SetEntitySetAccessRule("*", EntitySetRights.All); config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3; } protected override ObjectContext CreateDataSource() { AddressContext addrContext = new AddressContext(); // Get the underlying ObjectContext for the DbContext. var context = ((IObjectContextAdapter)addrContext).ObjectContext; context.ContextOptions.ProxyCreationEnabled = false; // Return the underlying context. return context; } [WebGet] public IQueryable<Address> GetAllAddress() { return CurrentDataSource.Address; } } }Anonymous
August 27, 2011
That is an excellent question. Basically, the short answer is that we are used to having CurrentDataSource return a strongly-typed ObjectContext (with all those nice code-gen'd IQueryable<T> properties), but because of the Code First workaround you only get a vanilla ObjectContext back. I've written another post that describes the issue in more detail, and that shows how to handle this new behavior created by implementing the Code First workaround: blogs.msdn.com/.../ef-4-1-code-first-and-wcf-data-services-part-2-service-operation-considerations.aspx. Please take time to check it out. -GlennAnonymous
October 02, 2011
Thanks a bunch! I had been battling with this for a few hours.Anonymous
October 24, 2011
Do me a favour plz. Stop writing blogs, build a team wiki. There is so much broken information floating around about this fix that finding it was a part-luck and part-google search. #justsayingAnonymous
October 25, 2011
@Scott. I generally publish this kind of content on my blog first, and then if there is enough interest, I will migrate it to a more "official" location. Thanks for the reminder that I need to migrate this info to the TechNet Wiki, where it will be more discoverable.Anonymous
October 26, 2011
I have migrated the information in this post to the TechNet Wiki topic: Using Entity Framework 4.1 Code First with WCF Data Services (social.technet.microsoft.com/.../5234.aspx), which will hopefull make this content more discoverable.Anonymous
May 02, 2012
I have a problem with the WCF Data Services using the wrong database schema. EF Code First has created my tables as 'forum.Users' and 'forum.Posts' but my dataservice tries to get the tables at 'dbo.Users' and 'dbo.Posts'. I think the place where it goes wrong is the CreateDataSource method in the WCF class but I'm not sure. I can't find a way where I can tell the dataservices which database schema it should use.Anonymous
May 05, 2012
Mats, Are you using the attribute [Table("Users", Schema = "forum")] in your Code First data model that you are providing as T in DataService<T>? What version of WCF Data Services are you using? Thanks, Glenn.