使用 Fluent NHibernate 進行繼承映射 (Inheritance Mapping with Fluent NHibernate)


問題描述

使用 Fluent NHibernate 進行繼承映射 (Inheritance Mapping with Fluent NHibernate)

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?


參考解法

方法 1:

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.

方法 2:

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.

方法 3:

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<>)

方法 4:

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();
    }


}

(by JimyonkzStuart ChildsVirtualStaticVoidGary)

參考文件

  1. Inheritance Mapping with Fluent NHibernate (CC BY-SA 3.0/4.0)

#mapping #polymorphism #types #fluent #nhibernate






相關問題

Hibernate 單向父/子關係 - delete() 對子表執行更新而不是刪除 (Hibernate Unidirectional Parent/Child relationship - delete() performs update on child table instead of delete)

TIGER shapefile - 使用和解釋 (TIGER shapefiles - using and interpreting)

查找位置的城市和郵政編碼 (Finding City and Zip Code for a Location)

如何使 Vim 插件 NERDTree 的 <CR> 表現得更像 `go`? (How to make Vim plugin NERDTree's <CR> behave more like `go`?)

在 NHibernate 上的多對多關係上添加自定義列 (Add custom columns on many to many relationship on NHibernate)

在大型 .NET 項目中管理 DTO 和映射 (Managing DTOs and mapping in large .NET project)

使用 Fluent NHibernate 進行繼承映射 (Inheritance Mapping with Fluent NHibernate)

Biztalk映射問題,請想法 (Biztalk Mapping Problem, Ideas please)

BizTalk 數據庫查找 functoid 固定條件 (BizTalk Database Lookup functoid fixed condition)

EntityFramework FluentAPI 映射問題 (EntityFramework FluentAPI mapping issue)

將外鍵添加到現有數據庫 (Adding foreign keys to an already existing database)

如何重命名對像數組中對象的所有鍵? (How does one rename all of an object's keys within an array of objects?)







留言討論