views:

242

answers:

2

Let's say your have the following table structure:

                  ==============================  
                  | Case                       |
                  ==============================
                  | Id           | int         |
                  | ReferralType | varchar(10) |
        +---------| ReferralId   | int         |---------+
        |         ==============================         |
        |                      |                         |
        |                      |                         |
======================  ======================  ======================        
| SourceA            |  | SourceB            |  | SourceC            |
======================  ======================  ======================
| Id   | int         |  | Id   | int         |  | Id   | int         |
| Name | varchar(50) |  | Name | varchar(50) |  | Name | varchar(50) |
======================  ======================  ======================

Based on the ReferralType the ReferralId contains id to the SourceA, SourceB, or SourceC

I'm trying to figure out how to map this using Fluent NHibernate or just plain NHibernate into an object model. I've tried a bunch of different things but I haven't been succesful. Any ideas?

The object model might be something like:

public class Case
{ 
  public int Id { get; set; }
  public Referral { get; set; }
}

public class Referral
{
  public string Type { get; set; }
  public int Id { get; set; }
  public string Name { get; set; }
}
A: 

many-to-any
If the table structure is fixed, and you want to use the identity id generator, I would map the Source tables as 3 separate classes and map to the common interface as an any reference.

class Case
{
    public virtual IReferral Referral { get; set; }
}

class SourceA : IReferral
{
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
    public virtual string Type { get { return "SourceA"; } }
}

interface IReferral
{
    int Id { get; set; }
    string Name { get; set; }
    string Type { get; }
}

public class CaseMap : ClassMap<Case>
{
    public CaseMap()
    {
        ReferencesAny(m => m.Referral)
            .EntityTypeColumn("ReferralType")
            .EntityIdentifierColumn("ReferralId")
            .AddMetaValue<SourceA>("SourceA")
            .AddMetaValue<SourceB>("SourceB")
            .AddMetaValue<SourceC>("SourceC")
            .IdentityType<int>();
    }
}

union-subclass
Another option is union-subclass with an abstract base class mapping. This allows eager fetching, but you cannot use the identity generator on the subclass tables.

<class name="IReferral" abstract="true" table="Referral">
    <id name="Id">
        <generator class="hilo"/>
    </id>
    <property name="Name"/>
    <union-subclass name="SourceA" table="SourceA">
        <!-- class specific properties -->
    </union-subclass>
    <union-subclass name="SourceB" table="SourceB">
        <!-- class specific properties -->
    </union-subclass>
    <union-subclass name="SourceC" table="SourceC">
        <!-- class specific properties -->
    </union-subclass>
</class>

subclass
If you can change the tables, you can map all 3 Referral classes to the same table using subclass.

<class name="IReferral" abstract="true" table="Referral" discriminator-value="null">
    <id name="Id">
        <generator class="identity"/>
    </id>
    <discriminator column="Discriminator" not-null="true" type="System.String"/>
    <property name="Name"/>
    <subclass name="SourceA" discriminator-value="SourceA">
        <!-- class specific properties -->
    </subclass>
    <subclass name="SourceB" discriminator-value="SourceB">
        <!-- class specific properties -->
    </subclass>
    <subclass name="SourceC" discriminator-value="SourceC">
        <!-- class specific properties -->
    </subclass>
</class>
Lachlan Roche
I can't seem to get your sample working. I'm getting a "Not a member accessParameter name: expression" error
Mark Boltuc
I managed to get it working (see my answer). Thanks for the help Lachlan.
Mark Boltuc
A: 

I managed to get it working by doing the following:

public class Case
{
  public virtual int? Id { get; set; }
  public virtual CaseReferral Referral { get; set; }
}
public class CaseReferral
{
  public virtual string Type { get; protected set; }
  public virtual int? ReferralId { get; protected set; }
  public virtual string Name { get { return null; }

  //NOTE: We need this for mapping reasons
  protected virtual int CaseId { get; set; }
  protected CaseReferral() { }
}

public class CaseSourceAReferral : CaseReferral
{
  private SourceA _sourceA;
  public virtual SourceA Source
  {
    get { return _sourceA; }
    protected set
    {
      _sourceA = value;

      Type = "SourceA";
      ReferralId = ( _sourceA != null ? _sourceA.Id : null );
    }
  }

  public override string Name { get { return Source.Name; } }

  //NOTE: Default constructor for mapping reasons
  protected CaseSourceAReferral() { }
  public CaseSourceAReferral( int caseId, SourceA source )
  {
    CaseId = caseId;
    Source = source;
  }
}

public class CaseMap : ClassMap<Case>
{
  public CaseMap()
  {
    Id( c => c.Id );
    References( c => c.Referral ).Column( "Id" );
  }
}

public class CaseReferralMap : ClassMap<CaseReferral>
{
  public CaseReferralMap()
  {
    Id( Reveal.Property<CaseReferral>( "CaseId") ).Column( "Id" );
    Map( r => r.Type ).Column( "ReferralType" );
    Map( r => r.ReferralId ).Column( "ReferralId" );
    DiscriminateSubClassesOnColumn( "ReferralType" );
  }
}

public class CaseSourceAReferralMap : SubclassMap<CaseSourceAReferral>
{
  public CaseSourceAReferralMap()
  {
    DiscriminatorValue( "SourceA" );
    References( r => r.Source ).Column( "ReferralId" );
  }
}
Mark Boltuc

related questions