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)
public interface IProcessActionLogDetail
{
int Id { get; set; }
}
Inside your
ProcessActionLog
remove the
ProcessActionType
Property and replace your
Details
Type with this new Interface:
public class ProcessActionLog
{
public virtual int Id { get; set; }
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:
public class ProcessActionLogEmail : IProcessActionLogDetail
{
public virtual int Id { get; set; }
}
public class ProcessActionInterviewFeedback : IProcessActionLogDetail
{
public virtual int Id { get; set; }
}
public class ProcessActionNotesInfo : IProcessActionLogDetail
{
public virtual int Id { get; set; }
}
Your subclass-mapping should mostly be the same, but you will have to remove the
Reference
to the parent:
public class ProcessActionLogEmailMap : ClassMap<ProcessActionLogEmail>
{
public ProcessActionLogEmailMap()
{
}
}
public class ProcessActionInterviewFeedbackMap : ClassMap<ProcessActionInterviewFeedback>
{
public ProcessActionInterviewFeedbackMap()
{
}
}
public class ProcessActionNotesInfoMap : ClassMap<ProcessActionNotesInfo>
{
public ProcessActionNotesInfoMap()
{
}
}
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.
public class ProcessActionLogMap : ClassMap<ProcessActionLog>
{
public ProcessActionLogMap()
{
ReferencesAny(x => x.Details)
.EntityTypeColumn("Type")
.EntityIdentifierColumn("ProcessActionLog_DetailId")
.IdentityType<int>()
.AddMetaValue<ProcessActionLogEmail>("0")
.AddMetaValue<ProcessActionInterviewFeedback>("1")
.AddMetaValue<ProcessActionNotesInfo>("2")
.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:
using (var session = sessionFactory.OpenSession())
using (var transaction = session.BeginTransaction(IsolationLevel.ReadCommitted))
{
ProcessActionLog log;
log = new ProcessActionLog
{
Details = new ProcessActionLogEmail()
};
session.Save(log);
log = new ProcessActionLog
{
Details = new ProcessActionInterviewFeedback()
};
session.Save(log);
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 ;-)