NHibernate Tutorial 2: Implementing and mapping the remaining domain objects

in #utopian-io8 years ago (edited)

In the first part [1] of this tutorial series I have introduced NHibernate and its sister project Fluent NHibernate. I have discussed the perquisites needed to start developing a NHibernate based application. Finally I have introduced a simple domain model which I then took as a base to implement. The first entity of this domain was then implemented and mapped. The unit test for the mapping was discussed.

In this second part I'll continue to implement and map the remaining part of the domain.

What Will I Learn?

  • You will learn implementing and mapping the remaining domain objects

Requirements

  • NHibernate

Difficulty

  • Intermediate

Curriculum

NHibernate Tutorial 2: Implementing and mapping the remaining domain objects

First let’s implement and map the Product class. This process is similar to the one used when defining and mapping the customer object. Let’s first have a look at the implementation of the product class

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal UnitPrice { get; set; }
    public bool Discontinued { get; set; }
}

To keep our sample simple I have only implemented the most important properties. The mapping is straight forward and very similar to the mapping of the customer class in the first part of this article. So I don’t want to discuss the details again. Below you can see the implementation of the ProductMapping class

public class ProductMapping : ClassMap<Product>
{
    public ProductMapping()
    {
        Not.LazyLoad();
        Id(p => p.Id).GeneratedBy.HiLo("1000");
        Map(p => p.Name).WithLengthOf(50).Not.Nullable();
        Map(p => p.UnitPrice).Not.Nullable();
        Map(p => p.Discontinued).Not.Nullable();
    }
}

Again the unit test for the mapping is similar to the unit test of the customer mapping class. We can use the PersistenceSpecification helper class of the Fluent NHibernate framework to facilitate this task.

[TestFixture]
public class ProductMapping_Fixture : FixtureBase
{
    [Test]
    public void can_correctly_map_product()
    {
        new PersistenceSpecification<Product>(Session)
                .CheckProperty(p=>p.Id, 1001)
                .CheckProperty(p=>p.Name, "Apples")
                .CheckProperty(p=>p.UnitPrice, 10.45m)
                .CheckProperty(p=>p.Discontinued, true)
                .VerifyTheMappings();
    }
}

The PersistenceSpecification class offers a fluent interface to define the exact testing context. The mapping of an individual property can be tested with the aid of the CheckProperty method which expects as a first parameter a lambda expression of the property to map and as a second parameter a value which is assigned to the property during the test.

Certainly you might want to know what exactly is happing during the test. The easiest way to do this is to enable the logging of the SQL produced by NHibernate. This is very easy - we just have to change the configuration in the base fixture class as follows

[SetUp]
public void SetupContext()
{
    var cfg = Fluently.Configure()
        .Database(SQLiteConfiguration.Standard
                      .ShowSql()
                      .InMemory
        );
    SessionSource = new SessionSource(cfg.BuildConfiguration().Properties, 
                                      new TestModel());
    Session = SessionSource.CreateSession();
    SessionSource.BuildSchema(Session);
}

The only thing we have changed is the .ShowSql() method call we have added in the SetupContext method of our FixtureBase class. When running the above unit test the following output is generated by NHibernate

As we can see the unit test creates an insert statement with the values provided and subsequently reloads the data written to the database with a select statement.

Now we come to a more interesting piece of the domain, the order and its order items. Let’s start with the OrderItem class. Here is its definition

public class OrderItem
{
    public int Id { get; set; }
    public int Quantity { get; set; }
    public Product Product { get; set; }
}

Again for simplicity only the most important properties are implemented. Adding additional properties is straight forward and is left for the reader as an exercise. Below the mapping for this class is shown. Please note the new keyword References. With this keyword we define a many-to-one relation between an order item and a product. That is each order item has/references exactly one product whilst every product can be referenced by many order items (in different orders!). With the .Not.Nullable() statement we declare that the reference to a product is mandatory (otherwise an order item is in an invalid state).

public class OrderItemMapping : ClassMap<OrderItem>
{
    public OrderItemMapping()
    {
        Not.LazyLoad();
        Map(oi => oi.Id).Not.Nullable();
        Map(oi => oi.Quantity).Not.Nullable();
        References(oi => oi.Product).Not.Nullable();
    }
}

Note as well that the Id of the order item class is not mapped as a key. An order item is not an entity but rather a value object. It cannot exist on its own. An instance of an order item can only be defined in the context of an order. As such the Id of the order item is only an internally used index to provide a sort index for order items of an order. The Id of an order item has to be defined by the order object when a new order item is added to it.

The unit test for the mapping is shown below

[Test]
public void can_correctly_map_order_item()
{
    var product = new Product{Name = "Apples", 
                              UnitPrice = 4.5m, 
                              Discontinued = true};
    new PersistenceSpecification<OrderItem>(Session)
                .CheckProperty(p=>p.Id, 1)
                .CheckProperty(p=>p.Quantity, 5)
                .CheckReference(p => p.Product, product)
                .VerifyTheMappings();
}

Note how the reference to the product is checked with the CheckReference method. There is one problem though with this test. It fails. This is due to the fact that we have not defined when two instances of type product are the same. We thus have to override the Equals and the GetHashCode methods inherited from the Object base class. This can be done as shown below

private int? oldHashCode;
public override int GetHashCode()
{
    // Once we have a hash code we'll never change it
    if (oldHashCode.HasValue)
        return oldHashCode.Value;
    bool thisIsTransient = Equals(Id, Guid.Empty);
    // When this instance is transient, we use the base GetHashCode()
    // and remember it, so an instance can NEVER change its hash code.
    if (thisIsTransient)
    {
        oldHashCode = base.GetHashCode();
        return oldHashCode.Value;
    }
    return Id.GetHashCode();
 }
public override bool Equals(object obj)
{
    Product other = obj as Product;
    if (other == null)
        return false;
    // handle the case of comparing two NEW objects
    bool otherIsTransient = Equals(other.Id, Guid.Empty);
    bool thisIsTransient = Equals(Id, Guid.Empty);
    if (otherIsTransient && thisIsTransient)
        return ReferenceEquals(other, this);
    return other.Id.Equals(Id);
}

An even better way would be to define an entity base class which implements these two methods. All domain entities would then inherit from this base class.

Let’s define such a (generic) base class now from which all entities will then inherit

public class Entity<TEntity> where TEntity : Entity<TEntity> 
{
    private int? oldHashCode;
    public Guid Id { get; set; }
    public bool Equals(Entity<TEntity> other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Equals(other.Id, Id);
    }
    public override int GetHashCode()
    {
        // Once we have a hash code we'll never change it
        if (oldHashCode.HasValue)
            return oldHashCode.Value;
        var thisIsTransient = Equals(Id, Guid.Empty);
        // When this instance is transient, we use the base GetHashCode()
        // and remember it, so an instance can NEVER change its hash code.
        if (thisIsTransient)
        {
            oldHashCode = base.GetHashCode();
            return oldHashCode.Value;
        }
        return Id.GetHashCode();
    }
    public override bool Equals(object obj)
    {
        var other = obj as TEntity;
        if (other == null)
            return false;
        // handle the case of comparing two NEW objects
        var otherIsTransient = Equals(other.Id, Guid.Empty);
        var thisIsTransient = Equals(Id, Guid.Empty);
        if (otherIsTransient && thisIsTransient)
            return ReferenceEquals(other, this);
        return other.Id.Equals(Id);
    }
}

Now the product (as well as the customer, employee and order) class has to inherit from this base class as follows

public class Product : Entity<Product>
{ ... }

Once we have defined equality for the product we can now re-execute the unit test and it will pass this time. When running the unit test the following output is produced

Note how Fluent NHibernate automatically inserts the product for us which is then referenced by the order item object.

The final piece is the implementation and mapping of the order class. The definition of the order class is given below

public class Order : Entity<Order>
{
    public DateTime OrderDate { get; set; }
    public Employee Employee { get; set; }
    public Customer Customer { get; set; }
    public IList<OrderItem> OrderItems { get; set; }
}

Note that it is very important not to expose the OrderItems collection as List but rather as IList (or even as IEnumerable). Otherwise you will get a type cast exception.

The mapping of the order class can be seen here

public class OrderMapping : ClassMap<Order>
{
    public OrderMapping()
    {
        Not.LazyLoad();
        Id(o => o.Id).GeneratedBy.GuidComb();
        Map(o => o.OrderDate).Not.Nullable();
        References(o => o.Employee).Not.Nullable();
        References(o => o.Customer).Not.Nullable();
        HasMany(o => o.OrderItems)
            .AsList(index => index.WithColumn("ListIndex")
                                  .WithType<int>());
    }
}

Again we have two References elements in the mapping. An order has a many-to-one relation to a customer as well as to an employee (who records the order).

The new and interesting part here is the HasMany element. With it we can define a one-to-many relation. The order object contains a list of order items. The collection of order items belonging to an order are returned as a list. The index of the list is mapped to a column called ListIndex of the corresponding table in the database. The type of this column in the table is int. We could as well map the order items as a set or other (interesting) collection type.

The unit test which verifies the mapping is presented below

[Test]
public void can_correctly_map_an_order()
{
    {
        var product1 = new Product{Name = "Apples", UnitPrice = 4.5m, 
                                   Discontinued = true};
        var product2 = new Product{Name = "Pears", UnitPrice = 3.5m, 
                                   Discontinued = false};
        Session.Save(product1);
        Session.Save(product2);
        var orderItems = new List<OrderItem>
         {
            new OrderItem {Id = 1, Quantity = 100, Product = product1},
            new OrderItem {Id = 2, Quantity = 200, Product = product2},
         };
        var customer = new Customer
                           {
                               FirstName = "John",
                               LastName = "Doe",
                               AddressLine1 = "1st Street",
                               PostalCode = "78279",
                               City = "Austin",
                               CountryCode = "US"
                           };
        var employee = new Employee {FirstName = "Sue", LastName = "Wong"};
        new PersistenceSpecification<Order>(Session)
            .CheckProperty(o => o.OrderDate, DateTime.Today)
            .CheckReference(o => o.Customer, customer)
            .CheckReference(o => o.Employee, employee)
            .CheckList(o => o.OrderItems, orderItems)
            .VerifyTheMappings();
    }
}

The above test is a bit lengthy since we have a lot of setup. We need products to be able to define meaningful order items. These products we have to explicitly save to the database. This is done with the aid of the session object. Now we can define order items which reference these products. We also need a customer and an employee object. Having defined all these elements we can now verify or test the mapping of the order entity.

Please note especially the .CheckList(…) method that is used to verify the mapping of the order items list.

Summary

In part 2 of the tutorial series on NHibernate and Fluent NHibernate I have continued to implement the remaining part of the domain. I have discussed the mapping of various forms of relations between different entities. For all mappings I have presented the code needed to verify the mappings.

In my next tutorial I'll show you how further refactor and improve the mapping of our domain model. I'll then discuss the usage of conventions and finally show how we can use the auto-mapping capabilities of Fluent NHibernate to completely avoid the explicit mapping of entities.



Posted on Utopian.io - Rewarding Open Source Contributors

Sort:  

Thank you for the contribution. It has been approved.

You can contact us on Discord.
[utopian-moderator]

Hey @haig I am @utopian-io. I have just upvoted you!

Achievements

  • You have less than 500 followers. Just gave you a gift to help you succeed!
  • Seems like you contribute quite often. AMAZING!

Suggestions

  • Contribute more often to get higher and higher rewards. I wish to see you often!
  • Work on your followers to increase the votes/rewards. I follow what humans do and my vote is mainly based on that. Good luck!

Get Noticed!

  • Did you know project owners can manually vote with their own voting power or by voting power delegated to their projects? Ask the project owner to review your contributions!

Community-Driven Witness!

I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!

mooncryption-utopian-witness-gif

Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x

Coin Marketplace

STEEM 0.04
TRX 0.33
JST 0.083
BTC 62069.28
ETH 1614.34
USDT 1.00
SBD 0.45