Mapping associations

Association between two classes is a very simple concept to understand. If you want to have association between ClassA and ClassB, then you can add a property of type ClassB on ClassA. This is the most basic form of association.

Associations come in four different forms as described next:

  • One-to-many: One end of the association has single instance of the entity while the other end has multiple instances. In code, this is represented by a property of the Collection type. For example, IList<T>, [], ICollection<T> ,IEnumerable<T>. and so on
  • Many-to-one: Opposite of one-to-many.
  • One-to-one: Both ends of associations have one single instance of entities.
  • Many-to-many: Both ends of associations have multiple instances of entities.

We have example of all associations except many-to-many in our domain. Let me refer to part of the Employee class and the Benefit class as follows:

public class Employee : EntityBase
{	
  public virtual ICollection<Benefit> Benefits { get; set; }
  public virtual Address ResidentialAddress { get; set; }
}

public class Benefit : EntityBase
{
  public virtual Employee Employee { get; set; }
}

public class Address : EntityBase
{
  public virtual Employee Employee { get; set; }
}

The Benefits property on the Employee class is an example of one-to-many association. On one side of this association we have got one instance of the Employee class and on the other side we have got a list of instances of the Benefit class.

On the Benefit class, we have an association from benefit to employee. This is the other end of one-to-many association we just discussed. This clearly is many-to-one association. We can have multiple instances of the Benefit class belonging to the same Employee instance. The ResidentialAddress property on the Employee class, on the other hand, is an example of one-to-one association. Every employee has only one address. Similarly, association from Address back to Employee is also one-to-one for the same reason.

We do not have any example of many-to-many in our domain so I am going to extend the scope of our problem and add the following new business requirement to it:

Organization operates different communities for employees. Employees are encouraged to be members of different communities. These communities not only help in building a social circle but also give employees an opportunity to meet like-minded people and build on their skills by sharing experience. Employees can become members of more than one community at a time.

To satisfy the preceding requirement, we would need to add a new class to our domain to represent an employee community. There can be multiple members of a community and an employee can be a member of multiple communities. Following is the relevant part of the code:

namespace Domain
{
  public class Community : EntityBase
  {
    public virtual string Name { get; set; }
    public virtual string Description { get; set; }
    public virtual ICollection<Employee> Members { get; set; }
  }
}

namespace Domain
{
  public class Employee : EntityBase
  {
    public virtual ICollection<Community> Communities { get; set; }
  }
}

So, we have a collection of the Community class on Employee representing the communities that the employee is member of. At the same time, we have a collection of the Employee class on Community holding all employees who are member of that community.

Associations and database tables

Before we actually look into mapping different types of associations, it would be worthwhile to spend some time understanding how a relational database supports associations. We know that every entity in the domain model gets mapped to a database table. So in order to support association between two classes, the database must be able to relate two database tables somehow. Relational databases achieve this via a foreign key relationship between two tables. If I could use association between the Employee and Benefit class as an example, then following database diagram explains how these two entities are related to each other in the database:

Associations and database tables

As you can see, column Employee_Id on the Benefit table is designated as a foreign key to the Employee table. The value held in this column is primary key/identifier of a record in the Employee table. You can repeat the same primary key in as many Benefit records as you want. This implies that for one record in the Employee table, you can store many records in the Benefit table. This is basis of a one-to-many relationship.

Fundamentally, databases only support one-to-many relationships and one-to-one relationship using shared primary keys. All other relationships we discussed previously are merely a different arrangement of one-to-many relationship to achieve different results. For example, by putting a logical constraint that "you would never insert more than one record in the Benefit table for a record in the Employee table" you achieve a one-to-one relationship. Similarly, you can combine two one-to-many relationships through an intermediate table to achieve a many-to-many relationship.

Next, we will look at how the associations between the classes are mapped to database relations. As with previous mapping exercises, we will use unit tests to confirm that our mappings achieve what we want it to achieve.

One-to-many association

The Benefits collection on the Employee class is the one-to-many association that we are going to map here. We will use the following unit test to drive the implementation of mapping. In this test, we add an instance of the Leave, SeasonTicketLoan, and SkillsEnhancementAllowance classes to the Benefits collection on the Employee class and store the employee instance in database. We then retrieve the instance and confirm that all the benefit instances are present:

[Test]
public void MapsBenefits()
{
  object id = 0;
  using (var transaction = session.BeginTransaction())
  {
    id = session.Save(new Employee
    {
      EmployeeNumber = "123456789",
      Benefits = new HashSet<Benefit>
      {
        new SkillsEnhancementAllowance
        {
          Entitlement = 1000,
          RemainingEntitlement = 250
        },
        new SeasonTicketLoan
        {
          Amount = 1416,
          MonthlyInstalment = 118,
          StartDate = new DateTime(2014, 4, 25),
          EndDate = new DateTime(2015, 3, 25)
        },
        new Leave
        {
          AvailableEntitlement = 30,
          RemainingEntitlement = 15,
          Type = LeaveType.Paid
        }
      }
    });
    transaction.Commit();
  }

  session.Clear();

  using (var transaction = session.BeginTransaction())
  {
    var employee = session.Get<Employee>(id);

    Assert.That(employee.Benefits.Count, Is.EqualTo(3));
    transaction.Commit();
  }
}

This test is not very different from the previous test we saw. After retrieving the saved employee instance from the database, we are confirming that the Benefits collection on it has three items, which is what we had added. Let's add mappings to make this test pass. Mappings for this association would be added to the existing mapping file for the Employee class, which is Employee.hbm.xml. Following is how one-to-many association mapping is declared:

<set name="Benefits" cascade="all-delete-orphan">
  <key column="Employee_Id" />
  <one-to-many class="Benefit"/>
</set>

One-to-many mapping is also called collection mapping in NHibernate parlance. This is because property being mapped usually represents a collection of items. The only mandatory attribute that needs to be declared on set element is name of the property which is being mapped. This happens to be Benefits in this case.

There are two nested elements inside the set node. First one is key, which is used to declare the name of the foreign key column on the other end of the association. If you recall from the preceding diagram, foreign key from Benefit to the Employee class is named Employee_Id.

Second node inside the set is one-to-many. This is where we tell more about the many sides of this association. The only mandatory detail that is needed is name of the class on the other end.

You may have noticed that I have also added a cascade attribute on the mapping above. This is more of a personal preference and you can easily do away with this, but then remember that the default value that NHibernate assumes would be none.

I believe "pictures speak a thousand words". In the following image, I have tried to explain how mapping relates to database tables and domain classes:

One-to-many association

Types of collections

We have used set element to declare collection mapping. Set tells NHibernate that a property being mapped is a collection. Additionally, set also puts restrictions that the collection is unordered, unindexed, and does not allow duplicates. You can declare the collection to be ordered, indexed, and so on, using the following XML elements:

  • Bag
  • List
  • Map
  • Array

While they all map the collection properties to database, there are subtle differences in their runtime behavior and .NET types that they support. Following table gives key comparison of these mappings:

 

Set

Bag

List

Map

Array

Duplicates

Duplicates not allowed in the collection

Duplicates allowed in the collection

Duplicates allowed in the collection

Duplicates not allowed in collection

Duplicates allowed in collection

Ordering

Not ordered

Not ordered

Ordered

Ordered

Ordered

Access

Collection items cannot be accessed by index

Collection items can be accessed by index

Collection items can be accessed by index

Collection items can be accessed by index

Collection items can be accessed by index

Supported .NET types

IEnumerable<T>,

ICollection<T>,

Iesi.Collection.Generic.ISet<T>

IEnumerable<T>, ICollection<T>, IList<T>

IEnumerable<T>, ICollection<T>, IList<T>

IDictionary<TKey, TValue>

IEnumerable<T>, ICollection<T>, T[]

Knowledge of the above key differences, coupled with knowledge of some other features offered by each of the collection mappings is useful in determining which collection mapping does the job best. I do not intend to cover those details here, as purpose is to only introduce you to collection mappings. As we go into more details in the coming chapters, we will revisit collection mappings and look into some of these features.

Note

All association mappings have quite a detailed level configuration available. I have preferred to show you only the minimum required to make the associations work. Some of the optional configurations will be covered at appropriate times throughout the book. Rest are used in rare and specific situations and are left for readers to explore on their own.

Many-to-one association

The other end of one-to-many association is many-to-one association. When both one-to-many and many-to-one associations are present between two entities, it is called bidirectional association. This is because you can navigate from one end to the other end in both directions. Benefit to the Employee association in our domain model is an example of many-to-one association. This is also a bidirectional association if you notice. Reason to highlight bidirectional nature of association is that NHibernate handles them in a different way. We are going to cover this aspect in Chapter 5, Let's Store Some Data into the Database, but it is worth highlighting now what bidirectional mapping is.

To test this mapping, we will extend the unit test from the earlier collection mapping example and add the following lines after the line where we asserted that benefit count is 3:

var seasonTicketLoan = employee.Benefits.OfType<SeasonTicketLoan>(). FirstOrDefault();
Assert.That(seasonTicketLoan, Is.Not.Null);
if (seasonTicketLoan != null) {
  Assert.That(seasonTicketLoan.Employee.EmployeeNumber, Is.EqualTo("123456789"));
}

var skillsEnhancementAllowance = employee.Benefits
                                 .OfType<SkillsEnhancementAllowance>().FirstOrDefault();
Assert.That(skillsEnhancementAllowance, Is.Not.Null);
if (skillsEnhancementAllowance != null)
{
  Assert.That(skillsEnhancementAllowance.Employee.EmployeeNumber, Is.EqualTo("123456789"));
}

var leave = employee.Benefits.OfType<Leave>().FirstOrDefault();
Assert.That(leave, Is.Not.Null);
if (leave != null)
{
  Assert.That(leave.Employee.EmployeeNumber, Is.EqualTo("123456789"));
}

At first look, this code may look complex but it is not actually. We have loaded employee instance from the database. On this instance, we confirmed that list of benefit has 3 benefit instances present. We query this list using LINQ and retrieve benefit instance of each type. On each of the benefit instance, we confirm that the Employee instance is present and it is the same employee instance that we had saved to database.

The mapping for this association is relatively easy as compared to collection mapping. Following is how you would declare this mapping inside of the Benefit class's mapping file, benefit.hbm.xml:

<many-to-one name="Employee"class="Employee"column="Employee_Id"/>

The node many-to-one signifies that this is a many end of a one-to-many association. There are only two mandatory attributes that this mapping takes. First one, name identifies the property on the mapped class that is at the singular end of the association. Second, class identifies the type of the singular end.

If the preceding text was any difficult to understand, then see if the following picture does any justice:

Many-to-one association

One-to-one association

In one-to-one association, both ends of association hold a single item. There are two variations of one-to-one association in relational databases. First is a variation of one-to-many association in that it uses a foreign key from one table to another. Difference from one-to-many association is that this foreign key has unique constraint applied to it so that only one record can be present on many side. Second variation uses a shared primary key approach in which associated tables share the primary key value. We are only going to look at unique foreign key approach here. Shared primary key approach is left as an exercise for the readers.

We will use the Employee to Address association to guide us. To elaborate the database relationship that we just discussed, let me show you database diagram for the Employee and Address table and how they are associated using the foreign key Employee_Id from the Address table to the Employee table.Refer to the following image:

One-to-one association

We will use the following unit test in order to verify mappings for the preceding association:

[Test]
public void MapsResidentialAddress()
{
  object id = 0;
  using (var transaction = Session.BeginTransaction())
  {
    var residentialAddress = new Address
    {
      AddressLine1 = "Address line 1",
      AddressLine2 = "Address line 2",
      Postcode = "postcode",
      City = "city",
      Country = "country"
    };

    var employee = new Employee
    {
      EmployeeNumber = "123456789",
      ResidentialAddress = residentialAddress
    };
    residentialAddress.Employee = employee;

    id = Session.Save(employee);
    transaction.Commit();
  }

  Session.Clear();

  using (var transaction = Session.BeginTransaction())
  {
    var employee = Session.Get<Employee>(id);
    Assert.That(employee.ResidentialAddress.AddressLine1, Is.EqualTo("Address line 1"));
    Assert.That(employee.ResidentialAddress.AddressLine2, Is.EqualTo("Address line 2"));
    Assert.That(employee.ResidentialAddress.Postcode, Is.EqualTo("postcode"));
    Assert.That(employee.ResidentialAddress.City, Is.EqualTo("city"));
    Assert.That(employee.ResidentialAddress.Country, Is.EqualTo("country"));
Assert.That(employee.ResidentialAddress.Employee.EmployeeNumber,Is .EqualTo("123456789"));
    transaction.Commit();
  }
}

Note

You might have noticed that I have explicitly set the ResidentialAddress property on the Employee class and the Employee property on the Address class. This is a bidirectional association and NHibernate usually makes sure that both ends are persisted correctly even if one end is set in code. But this is not always true in case of one-to-one association. We would cover details like these in Chapter 5, Let's Store Some Data into the Database, when we talk about persisting entities. I just wanted to highlight that the unit test for this scenario is written slightly differently.

So we have got a one-to-one association from Employee to Address and another one from Address back to Employee. Association from Employee to Address is mapped as follows:

<one-to-one name = "ResidentialAddress" class = "Address" property-ref = "Employee" cascade = "all" />

By now, you may have guessed what node one-to-one and attributes name, class, and cascade are for. The only additional attribute property-ref is used to declare the name of the property on the other end of the association which refers back to this entity. In our example, that would be property named Employee on Address class.

Association from Address to Employee is actually many-to-one association constrained to single item. It is mapped using many-to-one XML node we have seen earlier with an additional attribute specifying the unique constraint.

<many-to-one name="Employee" class="Employee" column="Employee_Id" unique="true" />

Following diagram should help you relate how this mapping associates domain model and database tables:

One-to-one association

Many-to-many association

In database, many-to-many associations are just an arrangement of two one-to-many associations connected via an intermediate table. Employee communities' example from our domain could translate to a table schema which looks as follows:

Many-to-many association

As you can see, we have got a connecting table named Employee_Community. There are two one-to-many relationships at play here, one going from Employee to Employee_Community and the other going from Community to Employee_Community. The end result is that if we navigate from Employee through to Community, we may end up with multiple communities and same if we navigate the other way round. Thus we get many-to-many relationship.

Following unit test verifies the behavior we want when employee to community association is mapped:

[Test]
public void MapsCommunities()
{
  object id = 0;
  using (var transaction = session.BeginTransaction())
  {
    id = session.Save(new Employee
    {
      EmployeeNumber = "123456789",
      Communities = new HashSet<Community>
      {
        new Community
        {
          Name = "Community 1"
        },
        new Community
        {
          Name = "Community 2"
        }
      }
    });
    transaction.Commit();
  }

  session.Clear();

  using (var transaction = session.BeginTransaction())
  {
    var employee = session.Get<Employee>(id);
    Assert.That(employee.Communities.Count, Is.EqualTo(2));
Assert.That(employee.Communities.First().Members.First().EmployeeNumber, Is.EqualTo("123456789"));
  transaction.Commit();
  }
}

Here we are storing an instance of the Employee class with two instances of the Community class. We then retrieve the saved instance of Employee and verify that it has two instances of Community present on it. We also verify that community has employee instance in its Members collection. There are probably more things we can verify here but for the purpose of testing associations, I think this is enough.

Mapping for this association needs to be added on both Employee and Community end. This is because the association is bidirectional. Following is the mapping added to employee.hbm.xml:

<set name="Communities" table="Employee_Community" cascade="all-delete-orphan">
  <key>
    <column name="Employee_Id" />
  </key>
  <many-to-many class="Community">
    <column name="Community_Id" />
  </many-to-many>
</set>

Next is the other part of the mapping added to community.hbm.xml:

<set name="Members" table="Employee_Community" cascade="all-delete-orphan" inverse="true">
  <key>
    <column name="Community_Id"/>
  </key>
  <many-to-many class="Employee">
    <column name="Employee_Id" />
  </many-to-many>
</set>

The preceding mappings are quite similar to the collection mappings we saw earlier. There is no surprise there as we are really dealing with two collection mappings. Let's just note the key differences in this mapping from the collection mapping.

First difference is the additional table attribute on the set node. This attribute tells NHibernate what the name of the connecting table is.

Second difference is the node many-to-many inside the set node. In collection mapping, we had one-to-many here. Two mandatory pieces of information on the many-to-many node are class and column. class tells the name of the class on the other end of association. In employee mappings this is Community and in community this is Employee. column is used to declare the name of the foreign key column on the connecting table that relates to the other end of the association. For employee side, this is column Community_Id on the connecting table. For community side, this is column Employee_Id on the connecting table.

Again, the subsequent images might help. The following first image shows Employee to Community association mapping:

Many-to-many association

Next, the second image shows Community to Employee association mapping:

Many-to-many association

Similar to collection mapping, set is used as an example here and you can use map, bag, list, array, and so on.

Note

We have the inverse attribute added to the mappings on Community side. This attribute controls who owns the association. I intend to cover this in detail in Chapter 5, Let's Store Some Data into the Database, so do not be bothered by inverse at this stage.

We have reached the end of association mapping for this chapter. Whatever we learned here should form a good foundation of knowledge. Associations are a vast and important area. We will keep revisiting association mappings throughout the book and learn more about them in the process. For the time being, let's move on to next topic.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.239.44