Using Entity Framework 4.1 Code First with WCF Data Services

The Entity Framework 4.1 release introduced a feature named Code First, which enables you to define an Entity Framework data model using only code. For more information, see Getting Started with the Entity Framework 4.1. This article shows you how to create a Code First implementation of Northwind, which is then used as the data source by WCF Data Services to create a Northwind data service that supports the Open Data Protocol (OData). The code in this topic has been verified using Entity Framework 4.1 and .NET Framework 4 version of WCF Data Services.

Code First as an Entity Framework Data Service Provider

Code First introduces a new class to manage entities, the DbContext class. This new context class uses an underlying ObjectContext instance. However, because DbContext does not inherit from ObjectContext, it can not be used directly by WCF Data Services as an Entity Framework provider. Instead, you must manually obtain the underlying ObjectContext from the IObjectContextAdapter.ObjectContext implementation and provide this context to the WCF Data Service runtime by overriding the CreateDataSource method.

Note: WCF Data Services 5.0 now supports the direct use of the DbContext class as an Entity Framework provider.  You can download this release, which also supports OData v3, from the WCF Data Services 5.0 download page

This example assumes that you have the following installed:

  • Visual Studio 2010 – includes .NET Framework 4
  • Entity Framework 4.1 or 4.2 – you can get Entity Framework 4.1 from the Microsoft Download Center or as a NuGet package)
  • (Optional) Northwind database –if you do not already have Northwind installed on your default SQL Server Express instance, then Code First will create a new Northwind Instance for you, but this new Northwind instance will not contain any of the Northwind data.

To see the complete source code for this example, download the project from MSDN Code Gallery.

To create the Code First Northwind Data Model:
  1. In Visual Studio, create a new ASP.NET Web application project.

  2. Add references to the following assemblies:

    • EntityFramework.dll –the Entity Framework 4.1 assembly.
    • System.ComponentModel.DataAnnotations.dll –defines some of the mapping attributes
  3. Create a new code page in the project and add the following set of Code First Northwind classes:

    [Table("Customers")]
    public class Customer
    {
    public string CustomerID { get; set; }
    public string CompanyName { get; set; }
    public string ContactName { get; set; }
    public string ContactTitle { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string Region { get; set; }
    public string PostalCode { get; set; }
    public string Country { get; set; }
    public string Phone { get; set; }
    public string Fax { get; set; }
    public virtual ICollection<Order> Orders { get; set; }
    }
    [Table("Employees")]
    public class Employee
    {
    public int EmployeeID { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public string Title { get; set; }
    public string TitleOfCourtesy { get; set; }
    public DateTime? BirthDate { get; set; }
    public DateTime? HireDate { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string Region { get; set; }
    public string PostalCode { get; set; }
    public string Country { get; set; }
    public string HomePhone { get; set; }
    public string Extension { get; set; }
    public byte[] Photo { get; set; }
    public string Notes { get; set; }
    public int? ReportsTo { get; set; }
    public string PhotoPath { get; set; }
    [ForeignKey("ReportsTo")]
    public virtual Employee Manager{ get; set; }
    public virtual ICollection<Order> Orders { get; set; }
    }
    [Table("Order Details")]
    public class OrderDetail : IValidatableObject
    {
    [Key, Column(Order = 1)]
    public int OrderID { get; set; }
    [Key, Column(Order = 2)]
    public int ProductID { get; set; }
    public decimal UnitPrice { get; set; }
    public short Quantity { get; set; }
    public float Discount { get; set; }
    public Order Orders { get; set; }
    [ForeignKey("ProductID")]
    public virtual Product Product { get; set; }

    // Validate for the Discount property.
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {

    if (Discount < 0 || Discount > 1)
    {
    yield return new ValidationResult
    ("Discount must be a value between zero and one", new[] { "Discount" });
    }
    }
    }
    [Table("Orders")]
    public class Order
    {
    public int OrderID { get; set; }
    public string CustomerID { get; set; }
    public int? EmployeeID { get; set; }
    public DateTime? OrderDate { get; set; }
    public DateTime? RequiredDate { get; set; }
    public DateTime? ShippedDate { get; set; }
    public decimal? Freight { get; set; }
    public string ShipName { get; set; }
    public string ShipAddress { get; set; }
    public string ShipCity { get; set; }
    public string ShipRegion { get; set; }
    public string ShipPostalCode { get; set; }
    public string ShipCountry { get; set; }
    [ForeignKey("CustomerID")]
    public virtual Customer Customer { get; set; }
    [ForeignKey("EmployeeID")]
    public virtual Employee Employee { get; set; }
    public virtual ICollection<OrderDetail> OrderDetails { get; set; }
    //public Shipper Shipper { get; set; }
    }
    [Table("Products")]
    public class Product
    {
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public string QuantityPerUnit { get; set; }
    public decimal? UnitPrice { get; set; }
    public short? UnitsInStock { get; set; }
    public short? UnitsOnOrder { get; set; }
    public short? ReorderLevel { get; set; }
    public bool Discontinued { get; set; }
    // public Category Category { get ; set;}
    public ICollection<OrderDetail> Order_Detail { get; set; }
    // public Supplier Suppliers { get ; set;}
    }

  4. Define the context class, named NorthwindContext, that inherits from DbContext, as follows:

    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; }
    }

    This context class exposes properties that return typed-DbSet properties for each entity set, which are exposed as OData feeds by the data service.

  5. Add the following new connection string named NorthwindEntities to the project’s Web.config file:

    <add name="NorthwindEntities" connectionString="Data Source=.\SQLExpress;Initial Catalog=Northwind;Integrated
    Security=True;MultipleActiveResultSets=True" providerName="System.Data.SqlClient"/>

    This is a named SqlClient connection string that targets an existing Northwind database, in this case on the local SQL Express instance.
    Skip this step when you have Code First create the database instance for you.

  6. Add the following constructor to the NorthwindContext class:

    // Use the constructor to target a specific named connection string
    public NorthwindContext()
    : base("name=NorthwindEntities")
    {
    // Disable proxy creation, which doesn’t work well with data services.
    this.Configuration.ProxyCreationEnabled = false;

    // Create Northwind if it doesn't already exist.
    this.Database.CreateIfNotExists();
    }

    This constructor is needed to target a named connection string.

  7. (Optional) add the following OnModelCreating method override to the NorthwindContext class:

    // Disable creation of the EdmMetadata table.
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
    modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
    }

    This prevents the creation of the EdmMetadata table in the database. You only need to do this when you use “*” in the SetEntitySetAccessRule method, which is not recommended a recommended practice.

To create a Northwind-based OData service that uses the Code First Northwind Model:
  1. In Solution Explorer, right-click the ASP.NET project, and then click Add New Item.

  2. In the Add New Item dialog box, select WCF Data Service.

  3. For the name of the service, type Northwind.

  4. In the Northwind.svc.cs file, which is the code-behind page for the data service, replace the generated Northwind data service class with the following code:

    public class Northwind : DataService<ObjectContext>
    {
    // This method is called only once to initialize service-wide policies.
    public static void InitializeService(DataServiceConfiguration config)
    {
    // Explicitly define permissions on the exposed entity sets.
    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; the current released version of WCF Data Services does not recognize DbContext as an Entity Framework provider, so you need to manually provide the base ObjectContext class to the data service.

  5. Add the following CreateDataSource method override to the Northwind data service class:

    // 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;
    }

At this point, you should be able to run your Northwind data service.

Service Operation Considerations

When you create a service operation, it is common to use the underlying Entity Framework provider to access the database directly by using the Entity Framework with the existing connection. You do this by getting the strongly-typed ObjectContext from the DataService<T>.CurrentDataSource property. However, as a side-effect of manually providing the WCF Data Services runtime with the ObjectContext from a Code First provider, the DataService<T>.CurrentDataSource property returns an un-typed ObjectContext instead of a strongly-typed DbContext, which include the DbSet<TEntity> properties that implement the nice IQueryable<T> functionality. Since the data service does not have access to this strongly-typed DbContext, we must instead call the ObjectContext.CreateObjectSet<TEntity> method to manually get an ObjectSet(TEntity), which also implements IQueryable<T>.

The following example creates a service operation that returns a filtered set of Product entities:

// Service operation that returns non-discontinued products.
[WebGet]
public IQueryable<Product> GetCurrentProducts()
{
var context = this.CurrentDataSource;

return context.CreateObjectSet<Product>()
.Where(p=>p.Discontinued == false)
.AsQueryable<Product>();
}

Note that the AsQueryable() method lets us return the entities as an IQueryable<T>, which enables clients to further compose the results of this service operation.