Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Components Mapping in Fluent NHibernate

0.00/5 (No votes)
7 Aug 2012 1  
This article explains and illustrates on a practical example, how the support of different component mapping is implemented in Entity Developer with Fluent NHibernate.

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:

Components as a Class Property

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). 

components-mapping-fluent/1.png

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): 

components-mapping-fluent/2.png

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);
          // mapping of the composite complex type FullAddressType 
          Component(x => x.Address, 
                    aAddress => {
                    aAddress.Access.Property();
                    // mapping of the complex type ShortAddressType
                    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);
                                       }
                                      );
                    // mapping of the Phone property of the FullAddressType type
                    aAddress.Map(x => x.Phone)
                      .Column("Phone")
                      .CustomType("String")
                      .Access.Property()
                      .Generated.Never()
                      .CustomSqlType("nvarchar")
                      .Length(24);
                    // mapping of the Fax property of the FullAddressType type
                    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);
          // mapping of the complex type ShortAddressType 
          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);
          // mapping of the composite complex FullAddressType 
          Component(x => x.Address, 
                    aAddress => {
                    aAddress.Access.Property();
                    // mapping of the complex type ShortAddressType 
                    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);
                                       }
                                      );
                    // mapping of the Phone property of the FullAddressType type
                    aAddress.Map(x => x.Phone)
                      .Column("Phone")
                      .CustomType("String")
                      .Access.Property()
                      .Generated.Never()
                      .CustomSqlType("nvarchar")
                      .Length(24);
                    // mapping of the Fax property of the FullAddressType type
                    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. 

Components as Composite Identifiers 

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. 

components-mapping-fluent/3.png

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:     

components-mapping-fluent/4.png

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();
          // mapping of the OrderDetailsKeyType complex type as a composite entity key
          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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here