Part 2: Creating the Domain Models
by Rick Anderson
Add Models
There are three ways to approach Entity Framework:
- Database-first: You start with a database, and Entity Framework generates the code.
- Model-first: You start with a visual model, and Entity Framework generates both the database and code.
- Code-first: You start with code, and Entity Framework generates the database.
We are using the code-first approach, so we start by defining our domain objects as POCOs (plain-old CLR objects). With the code-first approach, domain objects don't need any extra code to support the database layer, such as transactions or persistence. (Specifically, they do not need to inherit from the EntityObject class.) You can still use data annotations to control how Entity Framework creates the database schema.
Because POCOs do not carry any extra properties that describe database state, they can easily be serialized to JSON or XML. However, that does not mean you should always expose your Entity Framework models directly to clients, as we'll see later in the tutorial.
We will create the following POCOs:
- Product
- Order
- OrderDetail
To create each class, right-click the Models folder in Solution Explorer. From the context menu, select Add and then select Class.
Add a Product
class with the following implementation:
namespace ProductStore.Models
{
using System.ComponentModel.DataAnnotations;
public class Product
{
[ScaffoldColumn(false)]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public decimal Price { get; set; }
public decimal ActualCost { get; set; }
}
}
By convention, Entity Framework uses the Id
property as the primary key and maps it to an identity column in the database table. When you create a new Product
instance, you won't set a value for Id
, because the database generates the value.
The ScaffoldColumn attribute tells ASP.NET MVC to skip the Id
property when generating an editor form. The Required attribute is used to validate the model. It specifies that the Name
property must be a non-empty string.
Add the Order
class:
namespace ProductStore.Models
{
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
public class Order
{
public int Id { get; set; }
[Required]
public string Customer { get; set; }
// Navigation property
public ICollection<OrderDetail> OrderDetails { get; set; }
}
}
Add the OrderDetail
class:
namespace ProductStore.Models
{
public class OrderDetail
{
public int Id { get; set; }
public int Quantity { get; set; }
public int OrderId { get; set; }
public int ProductId { get; set; }
// Navigation properties
public Product Product { get; set; }
public Order Order { get; set; }
}
}
Foreign Key Relations
An order contains many order details, and each order detail refers to a single product. To represent these relations, the OrderDetail
class defines properties named OrderId
and ProductId
. Entity Framework will infer that these properties represent foreign keys, and will add foreign-key constraints to the database.
The Order
and OrderDetail
classes also include "navigation" properties, which contain references to the related objects. Given an order, you can navigate to the products in the order by following the navigation properties.
Compile the project now. Entity Framework uses reflection to discover the properties of the models, so it requires a compiled assembly to create the database schema.
Configure the Media-Type Formatters
A media-type formatter is an object that serializes your data when Web API writes the HTTP response body. The built-in formatters support JSON and XML output. By default, both of these formatters serialize all objects by value.
Serialization by value creates a problem if an object graph contains circular references. That's exactly the case with the Order
and OrderDetail
classes, because each holds a reference to the other. The formatter will follow the references, writing each object by value, and go in circles. Therefore, we need to change the default behavior.
In Solution Explorer, expand the App_Start folder and open the file named WebApiConfig.cs. Add the following code to the WebApiConfig
class:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// New code:
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling =
Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);
}
}
This code sets the JSON formatter to preserve object references, and removes the XML formatter from the pipeline entirely. (You can configure the XML formatter to preserve object references, but it's a little more work, and we only need JSON for this application. For more information, see Handling Circular Object References.)