Before reading this article, please go through the following articles.
-
Object Relational Mapping (ORM) Using NHibernate - Part 1 of 8
-
Object Relational Mapping (ORM) Using NHibernate - Part 2 of 8
-
Object Relational Mapping (ORM) Using NHibernate - Part 3 of 8
Coding Optional One-To-Many Entity Associations
It was explained in the previous article (Part 3 of the article series) that an optional One-To-Many allows the value "0" (zero) in the multiplicity of the association. It was also shown with an example that optional One-To-Many associations results in nullable foreign key columns if not mapped correctly and hence need to be handled differently to avoid nulls which is preferred always for the db's stored data quality. The example shown was an optional One-To-Many association between PaymentApprovedOrder and Items in the inventory which was mapped simply as One-To-Many thereby resulting in null values in the ForeignKeyValue. Figure 1 shows the Item Table with null values for the foreignkey PAYMENTAPPROVEDORDERID caused by mapping the optional One-To-Many association between PaymentApprovedOrder and Item as simply One-To-Many.

Figure 1
Results of One-To-Many association for PaymentApprovedOrder and Item (items in inventory); see the null values in the foreign key column
Here in this article, the intent is to eliminate the nulls introduced in the database by correctly mapping the association between PaymentApprovedOrder and Item as optional One-To-Many.
Background
In NHibernate, an optional One-To-Many association is correctly mapped by introducing a join table between the "one" end table and the "many" end table. A row in the join table will have the primary key of both the "one" and the "many" end table set as foreign keys in it. It avoids the null values in the foreign key posted to the "many" end table found earlier by moving the foreign key to the join table. So will the null value for the ForeignKey Column now occur in the join table? Definitely not. In NHibernate you define the Join Table by saying that a row in the join table is optional (which simply means that the row is absent for any null value foreign key) i.e. if a row is present in the join table then both foreign keys are required and present. Abstractly reading this may not be enlightening. After reading the example, read this paragraph to enjoy the beauty of how NHibernate join tables can be used to solve the problem of null values in mapping an optional One-To-Many bidirectional association. The abstract concept is most important and hence reading this paragraph again after finishing the sample is better to truly enjoy NHibernate
Continuing the ECommerce Sample
The association between PaymentApprovedOrder and Item has to be mapped as an optional One-To-Many bidirectional association. We already mapped this as a simple One-To-Many association in the previous article. Now we will improve this. It is imperative to note that the best and recommended way to map collections with a bidirectional association is to use collections like <set> and <idbag>. Ordered collections like <list> are best avoided for collections in the bidirectional association. It so happens that for an optional One-To-Many association, the best collection to use is <idbag>. We will see why <idbag> is preferred over <set>. The <idbag> in C# code will be declared using "IList<>" but one should remember <idbag> does not store ordering information or index information.
First, let us consider the PaymentApprovedOrder end of the association. An instance of PaymentApprovedOrder will have one or many instances of Item. The C# code is changed in PaymentApprovedOrder.cs so that the declaration for the collection is IList<Item> to map it to <idbag> in the mapping file. But to know the changes in the mapping PaymentApprovedOrder class from a simple One-To-Many to an Optional One-To-Many association, refer to Figure 2.
In Figure 2 (lower side - optional One-To-Many mapping), the most notable change apart from the change of collection mapping to <idbag> from <set> is in the way the collection <idbag> is mapped. Here the collection table is named for an entity association. Remember from Part 2 and Part 3 of the article series, the collection table is named only for value types but for entity associations the collection table is implied by NHibernate from the association. Also here there is no <element> tag inside the <idbag> collection but it has an association specified as <many-to-many> signifying it's an entity association with a collection table name. The table named by <idbag table=".."> collection is called a join table and is explained in the next paragraph very clearly. The <many-to-many> association mapping is necessary instead of <many-to-one> mapping because the join table does not work as required with <many-to-one> mapping. The <many-to-many> mapped is constrained to work like a <many-to-one> association by using the attribute unique=true as in <many-to-one unique="true">. For an explanation of how this works, refer to Part 1 of this article series where a <many-to-one> was constrained to behave like a <one-to-one> using the unique=true attribute. The most important property worth noting is that <idbag> defines a separate surrogate key for the collection using the tag <collection-id>. This primary key column in <idbag> is what makes it ideal for use in an optional One-To-Many association collection and we will discuss this more while answering the question why <idbag> is preferred over a <set> collection in mapping bidirectional optional One-To-Many associations. One of the foreign keys for the collection table from this end of the association is named using the tag <key> and here it is "PAYMENTAPPROVEDORDERID". This is one end of the association where the collection is defined with <idbag> and a join table which we will be seen in detail next on the other side of the association.
Let us consider the other end of the association, i.e. Item class. Refer to Figure 3. A major change in mapping occurs in the mapping file of the Item class in Item. hbm. Earlier we directly mapped the association between Item and PaymentApprovedOrder as a simple Many-To-One association. We know that in the database to realise this association link, the primary key of "one" end of the table which is "PAYMENTAPPROVEDORDERID" (from the table "PAYMENTAPPROVEDORDER") is posted as a foreign key in the "many" end of the table i.e. the Item table (we already explained this in Part 3 of this article series - Background section). To confirm this, Refer to Figure 1 which shows the columns in the Item table and it is found that it has a column for paymentapprovedorderid, the key column posted from the paymentapprovedorder table. Now the problem is, our Item table represents items in the inventory that may not have been ordered at all. Hence this paymentapprovedorderid will have null keys. From this observation it's clear that what we need to do is to remove the nulls. We need to move this foreignkey column paymentapprovedorderid outside the table item and yet maintain the link between the paymentapprovedorder table and the item table. This is done by introducing a join table between the paymentapprovedorder table and the item table called PAYMENTAPPROVEDORDER_ITEMS table and the optional One-To-Many association between the paymentapprovedorder table and the item table is mapped to this table. Also note that the collection table named in the <idbag> collection mapping in the earlier paragraph is this same paymentapprovedorder_items join table only. The columns in the joinable will be the foreign key posted from the primarykey of the tables linked with the One-To-Many association. Hence in our case the join table will have the foreignkeys set to paymentapprovedorderid (primarykey of the paymentapprovedorder table) and the item (primary key of the item table. A row in this jointable will denote the item bought for a particular paymentapprovedorder. Since the foreignkey column that was causing the null has been moved from the item table to the join table, there won't be any nulls in the item table. The most interesting thing is that the join table itself will not have any null values and will have a row only when a item is bought in an order i.e when a paidorder has an item bought. Let us see how this is done in the Item.hbm mapping file.



Gurpreet AroraPosted Jul 27, 2023, 3:34 PM
Informative Thanks For Sharing !!!!\
Tina GargPosted Oct 17, 2012, 12:13 AM
Very nice article Sir, Sir In this you mention all the items code(InventorySerialCode) different for all the items, If their are many number of items, then is their any way to auto generate that code for all the items. Please explain me this also.