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.
To demonstrate how to implement and use components along with many other kinds of NHibernate mapping, we release NHibernate Mapping Samples application, which demonstrates 50 different mapping cases, how they are mapped using both fluent and XML mapping, and their usage. Visit our NHibernate Mapping Samples blog article to read more about this application and download it.
Related Articles