2.3 Non-convention model builder
To build an Edm model using non-convention model builder is to create an IEdmModel
object by directly call fluent APIs of ODataModelBuilder
. The developer should take all responsibility to add all Edm types, operations, associations, etc into the data model one by one.
Let’s see how to build the Ccustomer-Order* business model by ODataModelBuilder
.
CLR Models
Non-convention model builder is based on CLR classes to build the Edm Model. The Customer-Order business CLR classes are present in abstract section.
Enum Type
The following codes are used to add an Enum type Color
:
var color = builder.EnumType<Color>();
color.Member(Color.Red);
color.Member(Color.Blue);
color.Member(Color.Green);
It will generate the below metadata document:
<EnumType Name="Color">
<Member Name="Red" Value="0" />
<Member Name="Blue" Value="1" />
<Member Name="Green" Value="2" />
</EnumType>
Complex Type
Basic Complex Type
The following codes are used to add a complex type Address
:
var address = builder.ComplexType<Address>();
address.Property(a => a.Country);
address.Property(a => a.City);
It will generate the below metadata document:
<ComplexType Name="Address">
<Property Name="Country" Type="Edm.String" />
<Property Name="City" Type="Edm.String" />
</ComplexType>
Derived Complex type
The following codes are used to add a derived complex type SubAddress
:
var subAddress = builder.ComplexType<SubAddress>().DerivesFrom<Address>();
subAddress.Property(s => s.Street);
It will generate the below metadata document:
<ComplexType Name="SubAddress" BaseType="WebApiDocNS.Address">
<Property Name="Street" Type="Edm.String" />
</ComplexType>
Abstract Complex type
The following codes are used to add an abstract complex type:
builder.ComplexType<Address>().Abstract();
......
It will generate the below metadata document:
<ComplexType Name="Address" Abstract="true">
......
</ComplexType>
Open Complex type
In order to build an open complex type, you should change the CLR class by adding an IDictionary<string, object>
property, the property name can be any name. For example:
public class Address
{
public string Country { get; set; }
public string City { get; set; }
public IDictionary<string, object> Dynamics { get; set; }
}
Then you can build the open complex type by call HasDynamicProperties()
:
var address = builder.ComplexType<Address>();
address.Property(a => a.Country);
address.Property(a => a.City);
address.HasDynamicProperties(a => a.Dynamics);
It will generate the below metadata document:
<ComplexType Name="Address" OpenType="true">
<Property Name="Country" Type="Edm.String" />
<Property Name="City" Type="Edm.String" />
</ComplexType>
You can find that the complex type Address
only has two properties, while it has OpenType="true"
attribute.
Entity Type
Basic Entity Type
The following codes are used to add two entity types Customer
& Order
:
var customer = builder.EntityType<Customer>();
customer.HasKey(c => c.CustomerId);
customer.ComplexProperty(c => c.Location);
customer.HasMany(c => c.Orders);
var order = builder.EntityType<Order>();
order.HasKey(o => o.OrderId);
order.Property(o => o.Token);
It will generate the below metadata document:
<EntityType Name="Customer">
<Key>
<PropertyRef Name="CustomerId" />
</Key>
<Property Name="CustomerId" Type="Edm.Int32" Nullable="false" />
<Property Name="Location" Type="WebApiDocNS.Address" Nullable="false" />
<NavigationProperty Name="Orders" Type="Collection(WebApiDocNS.Order)" />
</EntityType>
<EntityType Name="Order">
<Key>
<PropertyRef Name="OrderId" />
</Key>
<Property Name="OrderId" Type="Edm.Int32" Nullable="false" />
<Property Name="Token" Type="Edm.Guid" Nullable="false" />
</EntityType>
Abstract Open type
The following codes are used to add an abstract entity type:
builder.EntityType<Customer>().Abstract();
......
It will generate the below metadata document:
<EntityType Name="Customer" Abstract="true">
......
</EntityType>
Open Entity type
In order to build an open entity type, you should change the CLR class by adding an IDictionary<string, object>
property, while the property name can be any name. For example:
public class Customer
{
public int CustomerId { get; set; }
public Address Location { get; set; }
public IList<Order> Orders { get; set; }
public IDictionary<string, object> Dynamics { get; set; }
}
Then you can build the open entity type as:
var customer = builder.EntityType<Customer>();
customer.HasKey(c => c.CustomerId);
customer.ComplexProperty(c => c.Location);
customer.HasMany(c => c.Orders);
customer.HasDynamicProperties(c => c.Dynamics);
It will generate the below metadata document:
<EntityType Name="Customer" OpenType="true">
<Key>
<PropertyRef Name="CustomerId" />
</Key>
<Property Name="CustomerId" Type="Edm.Int32" Nullable="false" />
<Property Name="Location" Type="WebApiDocNS.Address" Nullable="false" />
<NavigationProperty Name="Orders" Type="Collection(WebApiDocNS.Order)" />
</EntityType>
You can find that the entity type Customer
only has three properties, while it has OpenType="true"
attribute.
Entity Container
Non-convention model builder will build the default entity container automatically. However, you should build your own entity sets as:
builder.EntitySet<Customer>("Customers");
builder.EntitySet<Order>("Orders");
It will generate the below metadata document:
<Schema Namespace="Default" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityContainer Name="Container">
<EntitySet Name="Customers" EntityType="WebApiDocNS.Customer">
<NavigationPropertyBinding Path="Orders" Target="Orders" />
</EntitySet>
<EntitySet Name="Orders" EntityType="WebApiDocNS.Order" />
</EntityContainer>
</Schema>
Besides, you can call Singleton<T>()
to add singleton into entity container.
Function
It’s very simple to build function (bound & unbound) in Web API OData. The following codes define two functions. The first is bind to Customer
, the second is unbound.
var function = customer.Function("BoundFunction").Returns<string>();
function.Parameter<int>("value");
function.Parameter<Address>("address");
function = builder.Function("UnBoundFunction").Returns<int>();
function.Parameter<Color>("color");
function.EntityParameter<Order>("order");
It will generate the below metadata document:
<Function Name="BoundFunction" IsBound="true">
<Parameter Name="bindingParameter" Type="WebApiDocNS.Customer" />
<Parameter Name="value" Type="Edm.Int32" Nullable="false" />
<Parameter Name="address" Type="WebApiDocNS.Address" />
<ReturnType Type="Edm.String" Unicode="false" />
</Function>
<Function Name="UnBoundFunction">
<Parameter Name="color" Type="WebApiDocNS.Color" Nullable="false" />
<Parameter Name="order" Type="WebApiDocNS.Order" />
<ReturnType Type="Edm.Int32" Nullable="false" />
</Function>
Besides, Web API OData will automatically add function imports for all unbound functions. So, the metadata document should has:
<FunctionImport Name="UnBoundFunction" Function="Default.UnBoundFunction" IncludeInServiceDocument="true" />
Action
Same as function, it’s also very simple to build action (bound & unbound) in Web API OData. The following codes define two actions. The first is bind to collection of Customer
, the second is unbound.
var action = customer.Collection.Action("BoundAction");
action.Parameter<int>("value");
action.CollectionParameter<Address>("addresses");
action = builder.Action("UnBoundAction").Returns<int>();
action.Parameter<Color>("color");
action.CollectionEntityParameter<Order>("orders");
It will generate the below metadata document:
<Action Name="BoundAction" IsBound="true">
<Parameter Name="bindingParameter" Type="Collection(WebApiDocNS.Customer)" />
<Parameter Name="value" Type="Edm.Int32" Nullable="false" />
<Parameter Name="addresses" Type="Collection(WebApiDocNS.Address)" />
</Action>
<Action Name="UnBoundAction">
<Parameter Name="color" Type="WebApiDocNS.Color" Nullable="false" />
<Parameter Name="orders" Type="Collection(WebApiDocNS.Order)" />
<ReturnType Type="Edm.Int32" Nullable="false" />
</Action>
Same as function, Web API OData will automatically add action imports for all unbound actions. So, the metadata document should has:
<ActionImport Name="UnBoundAction" Action="Default.UnBoundAction" />
Summary
Let’s put all codes together:
public static IEdmModel GetEdmModel()
{
var builder = new ODataModelBuilder();
// enum type
var color = builder.EnumType<Color>();
color.Member(Color.Red);
color.Member(Color.Blue);
color.Member(Color.Green);
// complex type
// var address = builder.ComplexType<Address>().Abstract();
var address = builder.ComplexType<Address>();
address.Property(a => a.Country);
address.Property(a => a.City);
// address.HasDynamicProperties(a => a.Dynamics);
var subAddress = builder.ComplexType<SubAddress>().DerivesFrom<Address>();
subAddress.Property(s => s.Street);
// entity type
// var customer = builder.EntityType<Customer>().Abstract();
var customer = builder.EntityType<Customer>();
customer.HasKey(c => c.CustomerId);
customer.ComplexProperty(c => c.Location);
customer.HasMany(c => c.Orders);
// customer.HasDynamicProperties(c => c.Dynamics);
var order = builder.EntityType<Order>();
order.HasKey(o => o.OrderId);
order.Property(o => o.Token);
// entity set
builder.EntitySet<Customer>("Customers");
builder.EntitySet<Order>("Orders");
// function
var function = customer.Function("BoundFunction").Returns<string>();
function.Parameter<int>("value");
function.Parameter<Address>("address");
function = builder.Function("UnBoundFunction").Returns<int>();
function.Parameter<Color>("color");
function.EntityParameter<Order>("order");
// action
var action = customer.Collection.Action("BoundAction");
action.Parameter<int>("value");
action.CollectionParameter<Address>("addresses");
action = builder.Action("UnBoundAction").Returns<int>();
action.Parameter<Color>("color");
action.CollectionEntityParameter<Order>("orders");
return builder.GetEdmModel();
}
And the final XML will be:
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
<edmx:DataServices>
<Schema Namespace="WebApiDocNS" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<ComplexType Name="Address">
<Property Name="Country" Type="Edm.String" />
<Property Name="City" Type="Edm.String" />
</ComplexType>
<ComplexType Name="SubAddress" BaseType="WebApiDocNS.Address">
<Property Name="Street" Type="Edm.String" />
</ComplexType>
<EntityType Name="Customer" OpenType="true">
<Key>
<PropertyRef Name="CustomerId" />
</Key>
<Property Name="CustomerId" Type="Edm.Int32" Nullable="false" />
<Property Name="Location" Type="WebApiDocNS.Address" Nullable="false" />
<NavigationProperty Name="Orders" Type="Collection(WebApiDocNS.Order)" />
</EntityType>
<EntityType Name="Order">
<Key>
<PropertyRef Name="OrderId" />
</Key>
<Property Name="OrderId" Type="Edm.Int32" Nullable="false" />
<Property Name="Token" Type="Edm.Guid" Nullable="false" />
</EntityType>
<EnumType Name="Color">
<Member Name="Red" Value="0" />
<Member Name="Blue" Value="1" />
<Member Name="Green" Value="2" />
</EnumType>
</Schema>
<Schema Namespace="Default" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<Function Name="BoundFunction" IsBound="true">
<Parameter Name="bindingParameter" Type="WebApiDocNS.Customer" />
<Parameter Name="value" Type="Edm.Int32" Nullable="false" />
<Parameter Name="address" Type="WebApiDocNS.Address" />
<ReturnType Type="Edm.String" Unicode="false" />
</Function>
<Function Name="UnBoundFunction">
<Parameter Name="color" Type="WebApiDocNS.Color" Nullable="false" />
<Parameter Name="order" Type="WebApiDocNS.Order" />
<ReturnType Type="Edm.Int32" Nullable="false" />
</Function>
<Action Name="BoundAction" IsBound="true">
<Parameter Name="bindingParameter" Type="Collection(WebApiDocNS.Customer)" />
<Parameter Name="value" Type="Edm.Int32" Nullable="false" />
<Parameter Name="addresses" Type="Collection(WebApiDocNS.Address)" />
</Action>
<Action Name="UnBoundAction">
<Parameter Name="color" Type="WebApiDocNS.Color" Nullable="false" />
<Parameter Name="orders" Type="Collection(WebApiDocNS.Order)" />
<ReturnType Type="Edm.Int32" Nullable="false" />
</Action>
<EntityContainer Name="Container">
<EntitySet Name="Customers" EntityType="WebApiDocNS.Customer">
<NavigationPropertyBinding Path="Orders" Target="Orders" />
</EntitySet>
<EntitySet Name="Orders" EntityType="WebApiDocNS.Order" />
<FunctionImport Name="UnBoundFunction" Function="Default.UnBoundFunction" IncludeInServiceDocument="true" />
<ActionImport Name="UnBoundAction" Action="Default.UnBoundAction" />
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>