views:

31

answers:

2

Suppose I have the following tables:

Company           Person                 Address
----------------------------------------------------------------------------
Id (PK)           Id (PK)                Id (PK)
Name              CompanyId (FK)         CompanyId (FK)
                  AccessType             AddressType

Corresponding to the following C#.NET classes:

class Company
{
    int Id;
    List<Person> Employees;
    List<Address> Addresses;
}

class Person
{
    int Id;
    List<Address> CompanyAddresses;
}

class Address
{
    int Id;
    // ...
}

It's perfectly easy to map the Person + Address collections on the Company class using NHibernate:

<class name="Company" table="Company">
    <id name="Id" column="Id" />
    <set name="Employees" table="Person">
        <key column="CompanyId" />
        <one-to-many class="Person" />
    </set>
    <set name="Addresses" table="Address">
        <key column="CompanyId" />
        <one-to-many class="Address" />
    </set>
</class>

<class name="Person" table="Person">
    <id name="Id" column="Id" />
</class>

<class name="Address" table="Address">
    <id name="Id" column="Id" />
</class>

My problem is, how do I map the Addresses into the Person class as well? The idea is that, Companies have a list of Persons (employees) and Addresses (sites), but the Company addresses can also be looked up from the company's employees. (A bit weird and unorthodox, I know, but just play along with me on this).

Normally, I could do this by defining something like below on the Person class map:

<set name="CompanyAddresses" table="Address">
    <key column="CompanyId" property-ref="CompanyId" />
</set>

... but the Person (.NET) object does NOT implement a CompanyId property for the property-ref declaration to hold water (and we prefer for it not to).

How would I persist a collection of Addresses in the Person object via NHibernate, where Person::CompanyId = Address::CompanyId?

Thanks guys. :)

EDIT

It's actually top-of-head to just do a Person.Company.Addresses mapping like Diego suggested below, but sadly, that won't exactly work because while a Company would have a list of Addresses, each Person linked to a Company will only have a subset of those Addresses.

I've updated the table schema above. Think of it as like if a Person has AccessType = ENGINEER, then only AddressType = ENGINEERING Addresses will be persisted in it (in addition to CompanyId = CompanyId).

+1  A: 

If you're going to store an address for a person I think you'd need something in the Address table to link back to the Person table.

So, you could either add a PersonId column, and use that as the FK for addresses which relate to a person

Address
----------
Id (PK)
PersonID (FK - nullable)
CompanyID (FK - nullable)

Or you could have a discriminator column, in which case your Address table would look something like:

Address
----------
Id (PK)
Owner (Discriminator, eg either "Person" or "Company")
OwnerID (FK)

The most natural way (that I'm aware of) of mapping that is then as a Table per Class Hierarchy, though that involves having PersonAddress and CompanyAddress subclasses.

David
I think that the discriminator column may have some potential. :) I'll look into this and let you know how that pans out.
Richard Neil Ilagan
+1  A: 

You shouldn't try to map it directly.

Since a Person has a Company (it's on the table, I didn't see it in your mapping, but it should be there), the easiest way to get the addresses is person.Company.Addresses.

You can wrap that in a CompanyAddresses property that just delegates to Company.Addresses if you want.

Update: You can easily filter those addresses, although the list will be readonly:

public IEnumerable<Address> CompanyAddresses
{
    get { return Company.Addresses.Where(a => a.AddressType == AccessType); }
}
Diego Mijelshon
Yeah, that's how I'd think of it too --- except that what's really happening here is that while the Company has a collection of Person, the linked Person(s) on the other hand, only have a subset of the Addresses that their parent Company has (think of it as a Company has many sites, but an employee is only accessible from some of them). Because of this, Person.Company.Addresses doesn't exactly work. :( I'll edit the question above.
Richard Neil Ilagan
Adding fix according to your edit
Diego Mijelshon