Given the following scenario, I want map the type hierarchy to the database schema using Fluent NHibernate.
I am using NHibernate 2.0
Type Hier
Well, I'm not sure that it's quite right, but it might work... If anyone can do this more cleanly, I'd love to see it (seriously, I would; this is an interesting problem).
Using the exact class definitions you gave, here are the mappings:
public class ItemMap : ClassMap<Item>
{
public ItemMap()
{
Id(x => x.ItemId);
Map(x => x.ItemType);
Map(x => x.FieldA);
AddPart(new ConcreteItemYMap());
}
}
public class SubItemMap : ClassMap<SubItem>
{
public SubItemMap()
{
WithTable("Item");
// Get the base map and "inherit" the mapping parts
ItemMap baseMap = new ItemMap();
foreach (IMappingPart part in baseMap.Parts)
{
// Skip any sub class parts... yes this is ugly
// Side note to anyone reading this that might know:
// Can you use GetType().IsSubClassOf($GenericClass$)
// without actually specifying the generic argument such
// that it will return true for all subclasses, regardless
// of the generic type?
if (part.GetType().BaseType.Name == "JoinedSubClassPart`1")
continue;
AddPart(part);
}
Map(x => x.FieldB);
AddPart(new ConcreteItemXMap());
}
}
public class ConcreteItemXMap : JoinedSubClassPart<ConcreteItemX>
{
public ConcreteItemXMap()
: base("ItemId")
{
WithTableName("ConcreteItemX");
Map(x => x.FieldC);
}
}
public class ConcreteItemYMap : JoinedSubClassPart<ConcreteItemY>
{
public ConcreteItemYMap()
: base("ItemId")
{
WithTableName("ConcreteItemY");
Map(x => x.FieldD);
}
}
Those mappings produce two hbm.xml files like so (some extraneous data removed for clarity):
<class name="Item" table="`Item`">
<id name="ItemId" column="ItemId" type="Int32">
<generator class="identity" />
</id>
<property name="FieldA" type="String">
<column name="FieldA" />
</property>
<property name="ItemType" type="String">
<column name="ItemType" />
</property>
<joined-subclass name="ConcreteItemY" table="ConcreteItemY">
<key column="ItemId" />
<property name="FieldD">
<column name="FieldD" />
</property>
</joined-subclass>
</class>
<class name="SubItem" table="Item">
<id name="ItemId" column="ItemId" type="Int32">
<generator class="identity" />
</id>
<property name="FieldB" type="String">
<column name="FieldB" />
</property>
<property name="ItemType" type="String">
<column name="ItemType" />
</property>
<property name="FieldA" type="String">
<column name="FieldA" />
</property>
<joined-subclass name="ConcreteItemX" table="ConcreteItemX">
<key column="ItemId" />
<property name="FieldC">
<column name="FieldC" />
</property>
</joined-subclass>
</class>
It's ugly, but it looks like it might generate a usable mapping file and it's Fluent! :/ You might be able to tweak the idea some more to get exactly what you want.
This is how I resolved my inheritance problem:
public static class DataObjectBaseExtension
{
public static void DefaultMap<T>(this ClassMap<T> DDL) where T : IUserAuditable
{
DDL.Map(p => p.AddedUser).Column("AddedUser");
DDL.Map(p => p.UpdatedUser).Column("UpdatedUser");
}
}
You can then add this to your superclass map constructor:
internal class PatientMap : ClassMap<Patient>
{
public PatientMap()
{
Id(p => p.GUID).Column("GUID");
Map(p => p.LocalIdentifier).Not.Nullable();
Map(p => p.DateOfBirth).Not.Nullable();
References(p => p.Sex).Column("RVSexGUID");
References(p => p.Ethnicity).Column("RVEthnicityGUID");
this.DefaultMap();
}
}
I know this is really old, but it is now pretty simple to set up fluent to generate the exact mapping you initially desired. Since I came across this post when searching for the answer, I thought I'd post it.
You just create your ClassMap for the base class without any reference to your subclasses:
public class ItemMap : ClassMap<Item>
{
public ItemMap()
{
this.Table("Item");
this.DiscriminateSubClassesOnColumn("ItemType");
this.Id(x => x.ItemId, "ItemId");
this.Map(x => x.FieldA, "FieldA");
}
}
Then map your abstract subclass like this:
public class SubItemMap: SubclassMap<SubItemMap>
{
public SubItemMap()
{
this.Map(x => x.FieldB);
}
}
Then map your concrete subclasses like so:
public class ConcreteItemXMap : SubclassMap<ConcreteItemX>
{
public ConcretItemXMap()
{
this.Join("ConcreteItemX", x =>
{
x.KeyColumn("ItemID");
x.Map("FieldC")
});
}
}
Hopefully this helps somebody else looking for this type of mapping with fluent.
The line of code: if (part.GetType().BaseType.Name == "JoinedSubClassPart1")
can be rewritten as follows:
part.GetType().BaseType.IsGenericType && part.GetType().BaseType.GetGenericTypeDefinition() == typeof(JoinedSubClassPart<>)