Reputation: 2044
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
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
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