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 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.
This example assumes that you have the following installed:
To see the complete source code for this example, download the project from MSDN Code Gallery.
[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;} }
// 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;} }
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 name="NorthwindEntities" connectionString="Data Source=.\SQLExpress;Initial Catalog=Northwind;Integrated Security=True;MultipleActiveResultSets=True" providerName="System.Data.SqlClient"/>
// 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(); }
// 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(); }
// Disable creation of the EdmMetadata table. protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove<IncludeMetadataConvention>(); }
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; } }
// 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.
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>(); }
// 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.