Given the following scenario, I want map the type hierarchy to the database schema using Fluent NHibernate.
I am using NHibernate 2.0
Type Hierarchy
public abstract class Item
{
public virtual int ItemId { get; set; }
public virtual string ItemType { get; set; }
public virtual string FieldA { get; set; }
}
public abstract class SubItem : Item
{
public virtual string FieldB { get; set; }
}
public class ConcreteItemX : SubItem
{
public virtual string FieldC { get; set; }
}
public class ConcreteItemY : Item
{
public virtual string FieldD { get; set; }
}
See image
The Item
and SubItem
classes are abstract.
Database Schema
+----------+ +---------------+ +---------------+
| Item | | ConcreteItemX | | ConcreteItemY |
+==========+ +===============+ +===============+
| ItemId | | ItemId | | ItemId |
| ItemType | | FieldC | | FieldD |
| FieldA | +---------------+ +---------------+
| FieldB |
+----------+
See image
The ItemType
field determines the concrete type.
Each record in the ConcreteItemX
table has a single corresponding record in the Item
table; likewise for the ConcreteItemY
table.
FieldB
is always null if the item type is ConcreteItemY
.
The Mapping (so far)
public class ItemMap : ClassMap<Item>
{
public ItemMap()
{
WithTable("Item");
Id(x => x.ItemId, "ItemId");
Map(x => x.FieldA, "FieldA");
JoinedSubClass<ConcreteItemX>("ItemId", MapConcreteItemX);
JoinedSubClass<ConcreteItemY>("ItemId", MapConcreteItemY);
}
private static void MapConcreteItemX(JoinedSubClassPart<ConcreteItemX> part)
{
part.WithTableName("ConcreteItemX");
part.Map(x => x.FieldC, "FieldC");
}
private static void MapConcreteItemY(JoinedSubClassPart<ConcreteItemY> part)
{
part.WithTableName("ConcreteItemX");
part.Map(x => x.FieldD, "FieldD");
}
}
FieldB
is not mapped.
The Question
How do I map the FieldB
property of the SubItem
class using Fluent NHibernate?
Is there any way I can leverage DiscriminateSubClassesOnColumn
using the ItemType
field?
Addendum
I am able to achieve the desired result using an hbm.xml file:
<class name="Item" table="Item">
<id name="ItemId" type="Int32" column="ItemId">
<generator class="native"/>
</id>
<discriminator column="ItemType" type="string"/>
<property name="FieldA" column="FieldA"/>
<subclass name="ConcreteItemX" discriminator-value="ConcreteItemX">
<!-- Note the FieldB mapping here -->
<property name="FieldB" column="FieldB"/>
<join table="ConcreteItemX">
<key column="ItemId"/>
<property name="FieldC" column="FieldC"/>
</join>
</subclass>
<subclass name="ConcreteItemY" discriminator-value="ConcreteItemY">
<join table="ConcreteItemY">
<key column="ItemId"/>
<property name="FieldD" column="FieldD"/>
</join>
</subclass>
</class>
How do I accomplish the above mapping using Fluent NHibernate?
Is it possible to mix table-per-class-hierarchy with table-per-subclass using Fluent NHibernate?
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.
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();
}
}
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<>)