Search code examples
fluent-nhibernatemany-to-manyfluent-nhibernate-mappingdiscriminator

Fluent NHibernate: How to map many-to-many relations with discriminator


I have the following tables and entities which need to be mapped in Fluent NHibernate.

Tables:

CREATE TABLE workarea
(
  id uuid NOT NULL,
  name character varying(255) NOT NULL,
  CONSTRAINT pk_workarea PRIMARY KEY (id),
)

CREATE TABLE element
(
  id uuid NOT NULL,
  name character varying(255) NOT NULL,
  CONSTRAINT pk_element PRIMARY KEY (id),
)

CREATE TABLE attachment
(
  id uuid NOT NULL,
  filename character varying(255) NOT NULL,
  CONSTRAINT pk_attachment PRIMARY KEY (id),
)  

CREATE TABLE objectattachment
(
  id uuid NOT NULL,
  attachmentid uuid NOT NULL,
  attachmenttype string NOT NULL,
  objectid uuid NOT NULL,
  CONSTRAINT pk_objectattachment PRIMARY KEY (id),
  CONSTRAINT fk_oa_a FOREIGN KEY (attachmentid)
      REFERENCES attachment (id) MATCH SIMPLE
      ON UPDATE RESTRICT ON DELETE RESTRICT,
  CONSTRAINT fk_oa_at FOREIGN KEY (attachmenttypeid)
      REFERENCES attachmenttype (id) MATCH SIMPLE
      ON UPDATE RESTRICT ON DELETE RESTRICT
)

The idea under this database design is as follows:

  • A "workarea" or an "element" could have several "attachment" files and an "attachment" file could be referred to by several "workarea"s or "element"s.

  • A "workarea" or an "element" could refer to the same "attachment" file.

So the relations between "attachment"s and "workarea"s or "element"s are stored in "objectattachment " table, in which:

  • "attachmentid" field refers to the identifier of a specific "attachment"s.

  • "attachmenttype" field (discriminator) defines whether this relation is between "attachment"s and "workarea"s or between "attachment"s and "element"s.

  • "objectid" field refers to the identifier of a specific "workarea"s or "element"s, depending on the value of the above "attachmenttype" field.

Based on the database design, I then define domain model classes as follows:

public class WorkArea    
{
     private Guid _id = Guid.Empty;
     private string _name;
     public virtual Guid Id     
     {
          get { return _id ; }
          set { _id = value; }
     }
     public virtual string Name     
     {
          get { return _name ; }
          set { _name = value; }
     }
}

public class Element    
{
     private Guid _id = Guid.Empty;
     private string _name;
     public virtual Guid Id     
     {
          get { return _id ; }
          set { _id = value; }
     }
     public virtual string Name     
     {
          get { return _name ; }
          set { _name = value; }
     }
}

public class Attachment
{
     private Guid _id = Guid.Empty;
     private string _fileName;
     public virtual Guid Id     
     {
          get { return _id ; }
          set { _id = value; }
     }
     public virtual string FileName     
     {
          get { return _fileName; }
          set { _fileName= value; }
     }
}

public class WorkAreaAttachment : Attachment
{
     private WorkArea _workArea;
     public virtual WorkArea WorkArea 
     {
          get { return _workArea; }
          set { _workArea = value; }
     }     
}

public class ElementAttachment : Attachment
{
     private Element _element;
     public virtual Element Element
     {
          get { return _element; }
          set { _element = value; }
     }     
}

Now my question is whether I could mapping these domain model classes with the above database design. If yes, then how could I do that? If no, then how do I change the domain model classes to support Fluent NHibernate mapping against the designed database as I don't want to change the current database design (i.e. create separate "attachment" tables for "workarea" and "element").

Regards, Quan


Solution

  • public class AttachmentLink
    {
         private Attachment _attachment;
         public virtual Attachment Parent
         {
              get { return _attachment; }
              set { _attachment = value; }
         }
    
         private IHasAttachments _linkedTo;
         public virtual IHasAttachments LinkedTo
         {
              get { return _linkedTo; }
              set { _linkedTo = value; }
         }
    }
    
    // in AttachmentMap
    HasMany(x => x.Links)
        .Table("objectattachment");
    
    // map the component
    sealed class AttachmentLinkMap : ComponentMap<AttachmentLink>
    {
        public AttachmentLinkMap()
        {
            References(x => x.Attachment, "attachmentid");
            ReferencesAny(x => x.LinkedTo)
                .IdentityType<Guid>()
                .EntityIdentifierColumn("objectid")
                .EntityTypeColumn("attachmenttype")
                .AddMetaValue<WorkArea>(typeof(WorkArea).Name.ToLower())
                .AddMetaValue<Element>(typeof(Element).Name.ToLower())
                .Not.LazyLoad();  // to prevent false proxies
        }
    }
    
    
    // in ElementMap, and almost the same in WorkAreaMap
    HasManyToMany(x => x.Attachments)
        .Where("attachmenttype='element'")
    

    Note: you don't need an Id column in the link table