views:

239

answers:

1

I have the following domain graph, representing an insurance claim which can be against a vehicle or property:

public enum InvolvedPartyContext
{
    Vehicle = 1,
    Property = 2
}

public class Claim
{
    public virtual Guid Id { get; set; }
    public virtual InvolvedPartyContext InvolvedPartyContext { get; set; }
    public virtual Vehicle Vehicle { get; set; }    // set if Context = Vehicle
    public virtual Property Property { get; set; }  // set if Context = Property
}

public class Vehicle {  //... }

public class Property {  //... }

The SQL looks like this (notice the single foreign key InvolvedPartyId):

CREATE TABLE Claims ( 
 Id uniqueidentifier NOT NULL,
 InvolvedPartyContext int NOT NULL,
 InvolvedPartyId uniqueidentifier NOT NULL
)

CREATE TABLE Vehicles (
    Id uniqueidentifier NOT NULL,
    Registration varchar(20) NOT NULL
)

CREATE TABLE Properties (
    Id uniqueidentifier NOT NULL,
    PostCode varchar(20) NOT NULL
)

The Fluent NHibernate mapping file for Claim:

    public ClaimMap()
    {
        Id(x => x.Id);
        Map(x => x.InvolvedPartyContext).CustomTypeIs(typeof(InvolvedPartyContext));
        References(x => x.Vehicle, "InvolvedPartyId");
        References(x => x.Property, "InvolvedPartyId");
    }

This throws an "Invalid index {n} for this SqlParameterCollection with Count {m}" exception, since the same field (InvolvedPartyId) is mapped twice. A simple fix would be to create VehicleId and PropertyId fields, but in the real world there are many more contexts, so this isn't very flexible.

My question: is there a way to map a single foreign key to a number of mutually exclusive tables, based on a context?

+2  A: 

Personally, I wouldn't go with the design you have. Instead I'd create subclasses of your Claim class, VehicleClaim and PropertyClaim respectively.

public class VehicleClaim : Claim
{
  public virtual Vehicle Vehicle { get; set; }
}

Then change your mappings to use your InvolvedPartyContext column as a discriminator (the column which NHibernate uses to determine which class the row represents), and create subclass mappings for each subclass.

public class ClaimMap : ClassMap<Claim>
{
  public ClaimMap()
  {
    Id(x => x.Id);
    DiscriminateSubClassesOnColumn("InvolvedPartyContext");
  }
}

public class VehicleClaimMap : SubclassMap<VehicleClaim>
{
  public VehicleClaimMap()
  {
    DiscriminatorValue(1);
    References(x => x.Vehicle);
  }
}

If you really do want to run with what you've got, you should look into the any mappings; there isn't a lot of documentation on them, but you use the ReferencesAny method.

James Gregory
Your suggestion of using discriminators is a good one, but unfortunately our design is fixed. Found some good ReferencesAny documentation on Ayende's blog, so I'll go down this route. Thanks!
RaceFace