awilinsk
awilinsk

Reputation: 2044

DDD aggregate root child relationships NHibernate mappings

I am trying to follow the Aggregate design principle and have come up with a situation that I need help with. My aggregate root is a Customer object. The Customer object has a child collection of Address objects and a child collection of Contact objects.

A Contact can have a reference to an Address under the Customer aggregate. The Customer object has a unique ID and the Address and Contact objects have a local id, so the primary key in the database would be CustomerId and AddressId.

Here are the simplified classes:

public class Customer : AggregateRoot {
    public virtual int CustomerId { get; protected set; }
    public virtual IList<Address> Addresses { get; protected set; }
    public virtual IList<Contact> Contacts { get; protected set; }
}
public class Address : Entity {
    public Address(Customer customer, int addressId) {
        this.Customer = customer;
        this.AddressId = addressId;
    }

    public virtual Customer Customer { get; protected set; }
    public virtual int AddressId { get; protected set; }
}
public class Contact : Entity {
    public Contact(Customer customer, int contactId) {
        this.Customer = customer;
        this.ContactId = contactId;
    }

    public virtual Customer Customer { get; protected set; }
    public virtual int ContactId { get; protected set; }
    public virtual Address Address { get; set; }
}

The database has tables like the following:

Customer

CustomerId int identity PK

Address

CustomerId int not null PK,FK
AddressId int not null PK

Contact

CustomerId int not null PK,FK
ContactId int not null PK
AddressId int null FK

My problem comes when I am trying to map my entities with Fluent NHibernate. Because the Address object has a composite key of CustomerId and AddressId, NHibernate won't reuse the column CustomerId in the contact table. When I try to save the aggregate, I get an exception saying that there is more values than there are parameters. This happens because the Address object has a composite ID and does not share the CustomerId column with the Contact object.

The only way I can see to get around this is to add an AddressCustomerId column in the Contact table, but now I have a duplicate column as CustomerId and AddressCustomerId are the same values. Is there anyway around this behavior?

Upvotes: 2

Views: 1444

Answers (2)

awilinsk
awilinsk

Reputation: 2044

As far as I can tell, there is no way in NHibernate to share columns. I ended up going with the solution I have been using for a few years now. I use a GUID as the ID for NHibernate and use int surrogate keys to query from. This solution has been working well for me, but I just wanted to reduce some of the added waste in the database.

Upvotes: 1

eulerfx
eulerfx

Reputation: 37739

If neither Address nor Contact have identity outside of the Customer aggregate then they should be mapped as component collections. Also, is there a need for the Customer-Address and the Customer-Contact relationship to be bi-directional? And is there a need for an addressId and a contactId? If model is simplified, this would work:

public class Customer
{
    public virtual int CustomerId { get; protected set; }
    public virtual IList<Address> Addresses { get; protected set; }
    public virtual IList<Contact> Contacts { get; protected set; }
}

public class Address
{
    public string Street1 { get; private set; }
    public string Street2 { get; private set; }
    public string City { get; private set; }
    public string Region { get; private set; }
}

public class Contact
{
    public string Name { get; private set; }
    public string Email { get; private set; }
    public virtual Address Address { get; set; }
}

public class CustomerMap : FluentNHibernate.Mapping.ClassMap<Customer>
{
    public CustomerMap()
    {
        Table("Customers");
        Id(x => x.CustomerId);
        HasMany(x => x.Addresses)
            .Table("CustomerAddresses")
            .KeyColumn("CustomerId")
            .Component(m =>
            {
                m.Map(x => x.Street1);
                m.Map(x => x.Street1);
                m.Map(x => x.City);
            });
        HasMany(x => x.Contacts)
            .Table("CustomerContacts")
            .KeyColumn("CustomerId")
            .Component(m =>
            {
                m.Map(x => x.Name);
                m.Map(x => x.Email);
                m.Component(x => x.Address, ma =>
                {
                    ma.Map(x => x.Street1);
                });
            });
    }
}

In the mapping, the address and contact collection are mapped as components. This means they don't need to have their own identity and therefore no individual mapping class. In this model however, the contact's address would be stored in the same row as the contact data itself, which I believe is a good model (as opposed to a more normalized one).

Upvotes: 1

Related Questions