Click here to Skip to main content
15,434,532 members
Please Sign up or sign in to vote.
5.00/5 (2 votes)
I am new to Nhibernate and I don't know how to create a polymorphic relationship/mappings in my application. I have tried different solutions and tutorials but unfortunately I am still not been able to figure out how can I make this work. Following is my database structure in which ProcessActionLog is the main table (base class) and ProcessActionEmail, ProcessActionInterviewFeedback and ProcessActionNotesInfo is the child entities.

http://i.stack.imgur.com/EVNiO.png[^]

Entities
ProcessActionLog.cs

C#
/// <summary>
        /// Process action type enum field
        /// </summary>
        public enum ProcessActionType
        {
            ProcessActionEmail,
            ProcessActionInterviewFeedback,
            ProcessActionNotesInfo
        }
        public class ProcessActionLog
        {
            /// <summary>
            /// Id (primary key)
            /// </summary>
            public virtual int Id { get; set; }
            /// <summary>
            /// User on which this particular process and its corresponding task is performed
            /// </summary>
            public virtual User MasterUser { get; set; }
            /// <summary>
            /// User by whom this particular action is performed
            /// </summary>
            public virtual User SystemUser { get; set; }
            /// <summary>
            /// Process of this particular action
            /// </summary>
            public virtual Process Process { get; set; }
            /// <summary>
            /// Task of this particular action
            /// </summary>
            public virtual Task Task { get; set; }
            /// <summary>
            /// Name of action being performed
            /// </summary>
            public virtual string LogName { get; set; }
            /// <summary>
            /// Created at time stamp of this action log
            /// </summary>
            public virtual DateTime CreatedAt { get; set; }
            /// <summary>
            /// Process Action Type 
            /// </summary>
            public virtual ProcessActionType ProcessActionType { get; set; }
            /// <summary>
            /// Details of process action Log
            /// </summary>
            public virtual ProcessActionLog Details { get; set; }
        }



ProcessActionInterviewFeedback.cs

C#
public class ProcessActionInterviewFeedback
       {
           /// <summary>
           /// primary key of interview feedback record
           /// </summary>
           public virtual int Id { get; set; }
           /// <summary>
           /// Interviewer name
           /// </summary>
           public virtual string InterviewerName { get; set; }
           /// <summary>
           /// location
           /// </summary>
           public virtual string Location { get; set; }
           /// <summary>
           /// Date of interview
           /// </summary>
           public virtual DateTime Date { get; set; }
           /// <summary>
           /// Process Action Log
           /// </summary>
           public virtual ProcessActionLog ProcessActionLog { get; set; }
       }

ProcessNotesInfo.cs

C#
public class ProcessActionNotesInfo
       {
           /// <summary>
           /// Id of process action notes
           /// </summary>
           public virtual int Id { get; set; }
           /// <summary>
           /// comment given in notes
           /// </summary>
           public virtual string Comment {get; set;}
           /// <summary>
           /// Process Action log
           /// </summary>
           public virtual ProcessActionLog ProcessActionLog { get; set; }
       }


Mappings
ProcessActionLogMap.cs

C#
class ProcessActionLogMap : ClassMap<processactionlog>
       {
           public ProcessActionLogMap()
           {
               Id(x => x.Id);
               Map(x => x.LogName).Length(100).Not.Nullable();
               Map(x => x.ProcessActionType).CustomType<int32>().Not.Nullable();
               Map(x => x.CreatedAt).CustomType<datetime>().Not.Nullable();
               References(x => x.MasterUser).Column("UserId");
               References(x => x.Process).Column("ProcessId");
               References(x => x.SystemUser).Column("CreatedBy");
               References(x => x.Task).Column("TaskId").Nullable();

               //ReferencesAny(x => x.Details)
               //            .EntityTypeColumn("ProcessActionType")
               //            .EntityIdentifierColumn("Id")
               //            .IdentityType<int>()
               //            .AddMetaValue<processactionemail>("ProcessActionEmail").EntityIdentifierColumn("ProcessActionLogId")
               //            .AddMetaValue<processactioninterviewfeedback>("ProcessActionInterviewFeedback").EntityIdentifierColumn("ProcessActionLogId")
               //            .AddMetaValue<processactionnotesinfo>("ProcessActionNotesInfo").EntityIdentifierColumn("ProcessActionLogId");
           }
       }


ProcessActionEmailMap.cs


C#
class ProcessActionEmailMap : ClassMap<processactionemail>
      {
          public ProcessActionEmailMap()
          {
              Id(x => x.Id);
              Map(x => x.CC).Length(100).Nullable();
              Map(x => x.EmailBodyContent).Length(4001).Not.Nullable();
              Map(x => x.EmailDateTime).CustomType<datetime>().Not.Nullable();
              Map(x => x.EmailRandomText).Length(100).Nullable();
              Map(x => x.EmailSubject).Length(100).Not.Nullable();
              Map(x => x.Recipients).Length(100).Not.Nullable();
              Map(x => x.RecipientType).CustomType<int32>().Not.Nullable();
              References(x => x.ProcessActionLog, "ProcessActionLogId");
          }
      }


ProcessActionInterviewFeedback.cs

C#
class ProcessActionInterviewFeedbackMap : ClassMap<processactioninterviewfeedback>
       {
           public ProcessActionInterviewFeedbackMap()
           {
               Id(x => x.Id);
               Map(x => x.Date).CustomType<datetime>().Nullable();
               Map(x => x.InterviewerName).Length(50).Not.Nullable();
               Map(x => x.Location).Length(100).Nullable();
               References(x => x.ProcessActionLog, "ProcessActionLogId");
           }
       }


ProcessActionNotesInfoMap.cs

C#
class ProcessActionNotesInfoMap : ClassMap<processactionnotesinfo>
        {
            public ProcessActionNotesInfoMap()
            {
                Id(x => x.Id);
                Map(x => x.Comment).Length(255).Not.Nullable();
                References(x => x.ProcessActionLog, "ProcessActionLogId");
            }
        }


Now what I am looking for is to create such mapping that on the basis of "ProcessActionType" column in ProcessActionLog Table I should be able to insert and retrieve data from any of the child tables. The reason why I have defined primary keys in my child tables is because for one column of my parent table the child table can have multiple records in child tables. Kindly help.

The commented part of ProcessActionLogMap.cs file gives error.

Kindly help me to accomplish this relationship. Thank you.
Posted
Updated 1-Jan-22 13:22pm
v2

1 solution

As stated in this wiki: Fluent NHibernate Any

Quote:
There are three things you need to provide to be able to map using an Any; a column that holds the type of the entitiy, at least one column holding the identifier value, and a type for the identifier itself. You can specify these using the EntityTypeColumn, EntityIdentifierColumn, and IdentityType methods respectively.


The problem is, that you have a foreign-key to your parent table ProcessActionLog inside your subclasses. The NHibernate mapping needs this column as a one-to-one relation inside the parent table instead.

You will need 2 columns in your ProcessActionLog Table:

1. The discriminator type column (EntityTypeColumn)
This will tell NHibernate what concrete type this reference is.
You already got this column, currently mapped as an Enum.
Unfortunately, it has to be a string, so you will have to change your existing Type column to a string. Furthermore, this column is managed by NHibernate, so you can not have a direct mapping on that property anymore.

2. The Reference-Id column (EntityIdentifierColumn) which points to the Primary-Key on the child-tables.
Note, that this is not a Database Foreign-Key, but a identifier column which is also managed by NHibernate. There will be no Property for this column in your class.
Instead, this will converge to the Details Property as a concreate instance of the corresponding child class.



Instructions for your migration


Create a new Interface. This will represent the base for your Details Property and your subclasses.
(You could also use a abstract class, but an interface is sufficient here)
C#
public interface IProcessActionLogDetail
{
    int Id { get; set; }
}


Inside your ProcessActionLog remove the ProcessActionType Property and replace your Details Type with this new Interface:

C#
public class ProcessActionLog
{
    public virtual int Id { get; set; }

    // Obsolete
    // public virtual ProcessActionType ProcessActionType { get; set; }

    // [...Other Properties...]

    public virtual IProcessActionLogDetail Details { get; set; }
}


Next, let your subclasses implement this interface. You can not have a reference to your parent class anymore, so you will have to get rid of that, too:
C#
public class ProcessActionLogEmail : IProcessActionLogDetail
{
    public virtual int Id { get; set; }

    // Obsolete
    // public virtual ProcessActionLog ProcessActionLog { get; set; }

    // [...Other Properties...]
}

public class ProcessActionInterviewFeedback : IProcessActionLogDetail
{
    public virtual int Id { get; set; }

    // Obsolete
    // public virtual ProcessActionLog ProcessActionLog { get; set; }

    // [...Other Properties...]
}

public class ProcessActionNotesInfo : IProcessActionLogDetail
{
    public virtual int Id { get; set; }

    // Obsolete
    // public virtual ProcessActionLog ProcessActionLog { get; set; }

    // [...Other Properties...]
}


Your subclass-mapping should mostly be the same, but you will have to remove the Reference to the parent:
C#
public class ProcessActionLogEmailMap : ClassMap<ProcessActionLogEmail>
{
    public ProcessActionLogEmailMap()
    {
        // Mappings

        // Removed
        // References(x => x.ProcessActionLog, "ProcessActionLogId");
    }
}

public class ProcessActionInterviewFeedbackMap : ClassMap<ProcessActionInterviewFeedback>
{
    public ProcessActionInterviewFeedbackMap()
    {
        // Mappings

        // Removed
        // References(x => x.ProcessActionLog, "ProcessActionLogId");
    }
}

public class ProcessActionNotesInfoMap : ClassMap<ProcessActionNotesInfo>
{
    public ProcessActionNotesInfoMap()
    {
        // Mappings

        // Removed
        // References(x => x.ProcessActionLog, "ProcessActionLogId");
    }
}


Now to the parent class-mapping part:
As i mentioned before, you need two columns in order for NHibernate to know how to join the one-to-one part. The entity-type and the referenced id of the child table.
Both of these columns exist on the database, but cannot be mapped as class Properties, since NHibernate will manage them implicitly.
C#
public class ProcessActionLogMap : ClassMap<ProcessActionLog>
{
    public ProcessActionLogMap()
    {
        // Other mappings

        ReferencesAny(x => x.Details)

            // You already have the Type column,
            // but this needs to be changed to a string on the database.
            // Unfortunately, NHibernate wont let you specify the type of the
            // TypeColumn. Its a string by default.
            .EntityTypeColumn("Type")

            // This specifies the column name in your parent table,
            // to store the reference id of the child-table.
            .EntityIdentifierColumn("ProcessActionLog_DetailId")

            // This specifies the type of column above.
            .IdentityType<int>()

            // Now for the tricky part: You currently have an enum as your
            // descriminator and the Type column is an integer.
            // After you changed the column to a string, you will have to use
            // the int value of every Enum value as a string, too.
            // Add every subclass and use the string value of your former Enum:
            .AddMetaValue<ProcessActionLogEmail>("0")
            .AddMetaValue<ProcessActionInterviewFeedback>("1")
            .AddMetaValue<ProcessActionNotesInfo>("2")

            // If you plan to only insert values, but never update or delete,
            // you can use Cascade.Persist to let NHibernate do the insert.
            // Otherwise, if you want to delete Logs as well, use
            // Cascade.AllDeleteOrphan to auto-delete the child row.
            .Cascade.Persist();
    }
}



Usage in the code

Since NHibernate will manage the type of detail-class for you now, the Enum ProcessActionType is practically obsolete.
From now on, you just have to insert the concrete class instance for your Details Property and NHibernate will fill in the correct values into both Type and ProcessActionLog_DetailId columns.

A rudimentary example:
C#
using (var session = sessionFactory.OpenSession())
using (var transaction = session.BeginTransaction(IsolationLevel.ReadCommitted))
{
    ProcessActionLog log;

    // Email
    log = new ProcessActionLog
    {
        Details = new ProcessActionLogEmail()
    };

    session.Save(log);

    // InterviewFeedback
    log = new ProcessActionLog
    {
        Details = new ProcessActionInterviewFeedback()
    };

    session.Save(log);

    // NotesInfo
    log = new ProcessActionLog
    {
        Details = new ProcessActionNotesInfo()
    };

    session.Save(log);

    transaction.Commit();
}



Manual database migration

You will also have to migrate every foreign-key from your child tables to the parent.
Currently, you are using ProcessActionLogId in every child table to reference the parent. You need to turn this around, so that every row in your parent table will have the Primary-Key of the corresponding child table inside the ProcessActionLog_DetailId column. You can remove the ProcessActionLogId columns from your child tables afterwards.

I also suggest to manually create a Index on both columns Type and ProcessActionLog_DetailId in your parent table for performance on selects.


I hope this will help you further in your quest, traveller ;-)
 
Share this answer
 
v2
Comments
Dave Kreskowiak 2-Jan-22 1:55am    
Keep an eye on the dates of the questions you answer. This one is six years old, so I doubt you're going to get any answer from the OP.
Adam Flow 2-Jan-22 8:28am     CRLF
Yes.... I realized that after I wrote the answer. -.- I guess I was too enthusiastic. Oh, well... From now on, I will check the date first ;-)

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900