Introduction
This article explains and illustrates on a practical example, how the support of different component mapping is implemented in Entity Developer with Fluent NHibernate.
A component in NHibernate is a contained object that is persisted as a value type, rather than an entity. Components are mapped differently depending on their use in different situations:
One of the most common situations is when a component is used to map a class property, which is a class itself. Components may contain properties of any types including simple NHibernate types or other components. Both of these cases will be used in our example.
The database contains the Suppliers, Orders and Customers tables. The Suppliers and Customers tables have the same set of fields that describe the address. The Orders table also has a set of fields for address description, however, this set is somewhat narrower (it has no Phone and Fax fields).
We perform the following sequence of operations:
Create an NHibernate model, add the Suppliers, Orders and Customers tables to the model, create the ShortAddressType complex type to describe the shortened variant of the address and, in the Orders entity, change the set of properties of the ship address for the ShortShipAddress property of the ShortAddressType complex type, as well as set the property mapping to the columns of the Orders table.
Create the FullAddressType complex type to describe the full variant of the address that includes the ShortAddress property of the ShortAddressType complex type and two additional properties: Phone and Fax.
In the Suppliers and Customers entities, we use the Address property of the FullAddressType to replace the sets of properties that describe the full address, as well as set the property mapping to the table columns.
Add the additional Fluent NHibernate template to generate fluent mapping.
In the result, we have the following model (the presence of the association and its mapping in the model is conditioned by the structure of the tables in the Northwind database and is not immediately related to our example of how components are mapped in Fluent NHibernate):
Below is the generated fluent mapping for this model:
public class SuppliersMap : ClassMap<Suppliers>
{
public SuppliersMap()
{
Schema("dbo");
Table("Suppliers");
LazyLoad();
Id(x => x.SupplierID)
.Column("SupplierID")
.CustomType("Int32")
.Access.Property()
.CustomSqlType("int")
.Not.Nullable()
.Precision(10)
.GeneratedBy.Identity();
Map(x => x.CompanyName)
.Column("CompanyName")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Not.Nullable()
.Length(40);
Map(x => x.ContactName)
.Column("ContactName")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(30);
Map(x => x.ContactTitle)
.Column("ContactTitle")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(30);
Map(x => x.HomePage)
.Column("HomePage")
.CustomType("StringClob")
.Access.Property()
.Generated.Never()
.CustomSqlType("ntext")
.Length(1073741823);
Component(x => x.Address,
aAddress => {
aAddress.Access.Property();
aAddress.Component(x => x.ShortAddress,
aAddress_ShortAddress =>
{
aAddress_ShortAddress.Access.Property();
aAddress_ShortAddress.Map(x => x.Address)
.Column("Address")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(60);
aAddress_ShortAddress.Map(x => x.City)
.Column("City")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(15);
aAddress_ShortAddress.Map(x => x.Region)
.Column("Region")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(15);
aAddress_ShortAddress.Map(x => x.PostalCode)
.Column("PostalCode")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(10);
aAddress_ShortAddress.Map(x => x.Country)
.Column("Country")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(15);
}
);
aAddress.Map(x => x.Phone)
.Column("Phone")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(24);
aAddress.Map(x => x.Fax)
.Column("Fax")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(24);
}
);
}
}
public class OrdersMap : ClassMap<Orders>
{
public OrdersMap()
{
Schema("dbo");
Table("Orders");
LazyLoad();
Id(x => x.OrderID)
.Column("OrderID")
.CustomType("Int32")
.Access.Property()
.CustomSqlType("int")
.Not.Nullable()
.Precision(10)
.GeneratedBy.Identity();
Map(x => x.OrderDate)
.Column("OrderDate")
.CustomType("DateTime")
.Access.Property()
.Generated.Never()
.CustomSqlType("datetime");
Map(x => x.RequiredDate)
.Column("RequiredDate")
.CustomType("DateTime")
.Access.Property()
.Generated.Never()
.CustomSqlType("datetime");
Map(x => x.ShippedDate)
.Column("ShippedDate")
.CustomType("DateTime")
.Access.Property()
.Generated.Never()
.CustomSqlType("datetime");
Map(x => x.Freight)
.Column("Freight")
.CustomType("Decimal")
.Access.Property()
.Generated.Never()
.Default("0")
.CustomSqlType("money")
.Precision(19)
.Scale(4);
Map(x => x.ShipName)
.Column("ShipName")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(40);
Component(x => x.ShortShipAddress,
aShortShipAddress => {
aShortShipAddress.Access.Property();
aShortShipAddress.Map(x => x.Address)
.Column("ShipAddress")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(60);
aShortShipAddress.Map(x => x.City)
.Column("ShipCity")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(15);
aShortShipAddress.Map(x => x.Region)
.Column("ShipRegion")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(15);
aShortShipAddress.Map(x => x.PostalCode)
.Column("ShipPostalCode")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(10);
aShortShipAddress.Map(x => x.Country)
.Column("ShipCountry")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(15);
}
);
References(x => x.Customers)
.Class<Customers>()
.Access.Property()
.Cascade.None()
.LazyLoad()
.Columns("CustomerID");
}
}
public class CustomersMap : ClassMap<Customers>
{
public CustomersMap()
{
Schema("dbo");
Table("Customers");
LazyLoad();
Id(x => x.CustomerID)
.Column("CustomerID")
.CustomType("String")
.Access.Property()
.CustomSqlType("nchar")
.Not.Nullable()
.Length(5)
.GeneratedBy.Assigned();
Map(x => x.CompanyName)
.Column("CompanyName")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Not.Nullable()
.Length(40);
Map(x => x.ContactName)
.Column("ContactName")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(30);
Map(x => x.ContactTitle)
.Column("ContactTitle")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(30);
Map(x => x.Fax)
.Column("Fax")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(24);
Component(x => x.Address,
aAddress => {
aAddress.Access.Property();
aAddress.Component(x => x.ShortAddress,
aAddress_ShortAddress => {
aAddress_ShortAddress.Access.Property();
aAddress_ShortAddress.Map(x => x.Address)
.Column("Address")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(60);
aAddress_ShortAddress.Map(x => x.City)
.Column("City")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(15);
aAddress_ShortAddress.Map(x => x.Region)
.Column("Region")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(15);
aAddress_ShortAddress.Map(x => x.PostalCode)
.Column("PostalCode")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(10);
aAddress_ShortAddress.Map(x => x.Country)
.Column("Country")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(15);
}
);
aAddress.Map(x => x.Phone)
.Column("Phone")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(24);
aAddress.Map(x => x.Fax)
.Column("Fax")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(24);
}
);
HasMany<Orders>(x => x.Orders)
.Access.Property()
.AsSet()
.Cascade.None()
.LazyLoad()
.Inverse()
.KeyColumns.Add("CustomerID", mapping => mapping.Name("CustomerID")
.SqlType("nchar")
.Nullable()
.Length(5));
}
}
The code above represents the generated model mapping classes. The mapping of each class contains the descriptions of the identity key mapping and class properties, as well as the definitions of the name of the table in the database, the name of the schema, to which the table belongs, as well as the mapping of their navigation properties.
In the mapping of the Orders class, the Component function is used to map the ShortShipAddress component property of the type ShortAddressType: the function defines how the columns of the Orders table that describe the ship's address are mapped to the properties of the ShortAddressType complex type.
In the mapping of the Customers and Suppliers classes, the Component function is used to map the Address property of the FullAddressType composite component: the function defines how the columns of the Phone and Fax tables that extend the short description of the address are mapped to the properties of the FullAddressType complex type; the internal use of the Component function defines the mapping of the ShortAddress component property of the ShortAddressType type that is included into the FullAddressType type.
Entity keys in NHibernate must consist of one property. However, you may use component as a composite identifier. This component must be Serializable and it must re-implement Equals() and GetHashCode(), consistently with the database's notion of composite key equality. The application must assign its own identifiers for this class; generators cannot be used for a composite identifier.
The database contains the Products, Orders and OrderDetails tables, linked through foreign key constraints.

We perform the following sequence of operations: create an NHibernate model, add the Products, Orders and OrderDetails tables to the model, create the OrderDetailsKeyType complex type for the key of the OrderDetails entity and set the entity key of this entity as a property of the OrderDetailsKeyType type; add the additional Fluent NHibernate template to generate fluent mapping. In the result, we have the following model:

Below is the generated fluent mapping for this model:
public class ProductsMap : ClassMap<Products>
{
public ProductsMap()
{
Schema("dbo");
Table("Products");
LazyLoad();
Id(x => x.ProductID)
.Column("ProductID")
.CustomType("Int32")
.Access.Property()
.CustomSqlType("int")
.Not.Nullable()
.Precision(10)
.GeneratedBy.Identity();
Map(x => x.ProductName)
.Column("ProductName")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Not.Nullable()
.Length(40);
Map(x => x.SupplierID)
.Column("SupplierID")
.CustomType("Int32")
.Access.Property()
.Generated.Never()
.CustomSqlType("int")
.Precision(10);
Map(x => x.CategoryID)
.Column("CategoryID")
.CustomType("Int32")
.Access.Property()
.Generated.Never()
.CustomSqlType("int")
.Precision(10);
Map(x => x.QuantityPerUnit)
.Column("QuantityPerUnit")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(20);
Map(x => x.UnitPrice)
.Column("UnitPrice")
.CustomType("Decimal")
.Access.Property()
.Generated.Never()
.Default("0")
.CustomSqlType("money")
.Precision(19)
.Scale(4);
Map(x => x.UnitsInStock)
.Column("UnitsInStock")
.CustomType("Int16")
.Access.Property()
.Generated.Never()
.Default("0")
.CustomSqlType("smallint")
.Precision(5);
Map(x => x.UnitsOnOrder)
.Column("UnitsOnOrder")
.CustomType("Int16")
.Access.Property()
.Generated.Never()
.Default("0")
.CustomSqlType("smallint")
.Precision(5);
Map(x => x.ReorderLevel)
.Column("ReorderLevel")
.CustomType("Int16")
.Access.Property()
.Generated.Never()
.Default("0")
.CustomSqlType("smallint")
.Precision(5);
Map(x => x.Discontinued)
.Column("Discontinued")
.CustomType("Boolean")
.Access.Property()
.Generated.Never()
.Default("0")
.CustomSqlType("bit")
.Not.Nullable();
HasMany<OrderDetails>(x => x.OrderDetails)
.Access.Property()
.AsSet()
.Cascade.None()
.LazyLoad()
.Inverse()
.KeyColumns.Add("ProductID", mapping => mapping.Name("ProductID")
.SqlType("int")
.Not.Nullable());
}
}
public class OrdersMap : ClassMap<Orders>
{
public OrdersMap()
{
Schema("dbo");
Table("Orders");
LazyLoad();
Id(x => x.OrderID)
.Column("OrderID")
.CustomType("Int32")
.Access.Property()
.CustomSqlType("int")
.Not.Nullable()
.Precision(10)
.GeneratedBy.Identity();
Map(x => x.CustomerID)
.Column("CustomerID")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nchar")
.Length(5);
Map(x => x.EmployeeID)
.Column("EmployeeID")
.CustomType("Int32")
.Access.Property()
.Generated.Never()
.CustomSqlType("int")
.Precision(10);
Map(x => x.OrderDate)
.Column("OrderDate")
.CustomType("DateTime")
.Access.Property()
.Generated.Never()
.CustomSqlType("datetime");
Map(x => x.RequiredDate)
.Column("RequiredDate")
.CustomType("DateTime")
.Access.Property()
.Generated.Never()
.CustomSqlType("datetime");
Map(x => x.ShippedDate)
.Column("ShippedDate")
.CustomType("DateTime")
.Access.Property()
.Generated.Never()
.CustomSqlType("datetime");
Map(x => x.ShipVia)
.Column("ShipVia")
.CustomType("Int32")
.Access.Property()
.Generated.Never()
.CustomSqlType("int")
.Precision(10);
Map(x => x.Freight)
.Column("Freight")
.CustomType("Decimal")
.Access.Property()
.Generated.Never()
.Default("0")
.CustomSqlType("money")
.Precision(19)
.Scale(4);
Map(x => x.ShipName)
.Column("ShipName")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(40);
Map(x => x.ShipAddress)
.Column("ShipAddress")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(60);
Map(x => x.ShipCity)
.Column("ShipCity")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(15);
Map(x => x.ShipRegion)
.Column("ShipRegion")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(15);
Map(x => x.ShipPostalCode)
.Column("ShipPostalCode")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(10);
Map(x => x.ShipCountry)
.Column("ShipCountry")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(15);
HasMany<OrderDetails>(x => x.OrderDetails)
.Access.Property()
.AsSet()
.Cascade.None()
.LazyLoad()
.Inverse()
.KeyColumns.Add("OrderID", mapping => mapping.Name("OrderID")
.SqlType("int")
.Not.Nullable());
}
}
public class OrderDetailsMap : ClassMap<OrderDetails>
{
public OrderDetailsMap()
{
Schema("dbo");
Table("`Order Details`");
LazyLoad();
CompositeId<OrderDetailsKeyType>(x => x.ID)
.KeyProperty(x => x.OrderID, set => {
set.Type("Int32");
set.ColumnName("OrderID");
set.Access.Property(); } )
.KeyProperty(x => x.ProductID, set => {
set.Type("Int32");
set.ColumnName("ProductID");
set.Access.Property(); } );
Map(x => x.UnitPrice)
.Column("UnitPrice")
.CustomType("Decimal")
.Access.Property()
.Generated.Never()
.Default("0")
.CustomSqlType("money")
.Not.Nullable()
.Precision(19)
.Scale(4);
Map(x => x.Quantity)
.Column("Quantity")
.CustomType("Int16")
.Access.Property()
.Generated.Never()
.Default("1")
.CustomSqlType("smallint")
.Not.Nullable()
.Precision(5);
Map(x => x.Discount)
.Column("Discount")
.CustomType("Single")
.Access.Property()
.Generated.Never()
.Default("0")
.CustomSqlType("real")
.Not.Nullable()
.Precision(24);
References(x => x.Orders)
.Class<Orders>()
.Access.Property()
.Cascade.None()
.LazyLoad()
.Columns("OrderID");
References(x => x.Products)
.Class<Products>()
.Access.Property()
.Cascade.None()
.LazyLoad()
.Columns("ProductID");
}
}
The code above represents the generated model mapping classes. The mapping of each class contains the descriptions of the identity key mapping and class properties, as well as the definitions of the name of the table in the database, the name of the schema, to which the table belongs, as well as the mapping of their navigation properties. In the mapping of the OrderDetails class, the CompositeId function is used to map the composite entity key: it specifies the complex type, the complex type property representing the composite entity key of the entity, defines the mapping of the columns that form the key. By the way, if the entity key properties weren't put into a separate complex type, residing instead in the OrderDetail entity, the mapping of the classes would be identical, except that in the mapping of the OrderDetail entity, to map the composite entity key, there would be no need to specify the name of the complex type and the entity key property of the entity, whose type was equal to that of the complex type.
Related Articles