Dec 25, 2009

Fluent NHibernate. Маппинг наследования

Гугл подсказал мне, что блог находят в основном при поиске того, как замапить различные виды наследования используя Fluent Nhibernate. Попробую осветить этот вопрос.

Nhibernate поддерживает 3 способа реализации наследования. Различаются они количеством необходимых для этого таблиц. Далее будут рассмотрены 2 из них подробнее.

Table per class hierarchy

В данной схеме для всей иерархии классов используется одна таблица. Рассмотрим пример со следующей моделью:

image

Для хранения такой структуры будет достаточно одной таблицы с полями Id, Name, UploadDate, WhoIsOnPicture, Lenght, Type. Стоит обратить внимание на то, что поле Type используется Nhibernate’ом для того, чтобы узнать какой конкретный тип находится в конкретной строке. Итак маппинг будет выглядеть следующим образом:

public class ContentMapping: ClassMap<Content>
{
    public ContentMapping()
    {
        Id(x => x.Id).GeneratedBy.Native();
        Map(x => x.Name).Length(200).Not.Nullable();
        Map(x => x.UploadDate).Not.Nullable();
        DiscriminateSubClassesOnColumn("ContentType");
    }
}

public class PhotoMapping : SubclassMap<Photo>
{
    public PhotoMapping()
    {
        Map(x => x.WhoIsOnPicture);
        DiscriminatorValue("Photo");
    }
}

public class VideoMapping: SubclassMap<Video>
{
    public VideoMapping()
    {
        Map(x => x.Length);
        DiscriminatorValue("Video");
    }
}

Генерируемый XML довольно громоздкий, и полностью приводить его я не буду. Важно лишь то, что сформирован маппинг, и в нем используются теги subclass для photo и video.

Недостатком данной схемы является то, что в таблице Content колонки WhoIsOnPicture и Length обязательно должны позволять сохранять NULL.

Table per subclass

Очень радует, что для того, чтобы использовать этот подход, достаточно просто убрать упоминания Discriminator из классов, маппинг будет абсолютно таким же:

public class ContentMapping: ClassMap<Content>
{
    public ContentMapping()
    {
        Id(x => x.Id).GeneratedBy.Native();
        Map(x => x.Name).Length(200).Not.Nullable();
        Map(x => x.UploadDate).Not.Nullable();
    }
}
public class PhotoMapping : SubclassMap<Photo>
{
    public PhotoMapping()
    {
        Map(x => x.WhoIsOnPicture);
    }
}
public class VideoMapping : SubclassMap<Video>
{
    public VideoMapping()
    {
        Map(x => x.Length);
    }
}

Этот маппинг приведет к использованию joined-subclass элементов.

9 comments:

  1. Как-то скудненько, где трейтий тип наследования? не дописал что-ли?

    ReplyDelete
  2. Не могли бы вы выложить пример кода? Т.к. при попытке реализовать стратегию "Table per subclass", аналогично приведенному примеру, sql-код при сохранении/изменении экземпляра класса-потока генерируется для родительской таблицы...

    ReplyDelete
  3. Конечно, сейчас займусь

    ReplyDelete
  4. дополнение к вышестоящему комментарию (предвидя вопрос), функция DiscriminateSubClassesOnColumn у меня отсутствует, как и полагается при "Table per subclass". Но тем не менее sql-код пытается писать в родительскую таблицу. Вот мой код маппинга:

    public class MaterialMap : ClassMap
    {
    public MaterialMap()
    {
    Table("MATERIAL_AAA");
    Not.LazyLoad();
    Id(x => x.ID).Column("MATERIAL_ID")
    .GeneratedBy.Sequence("MATERIAL_SQ");
    }
    }

    public class RealMaterialMap : SubclassMap
    {
    public RealMaterialMap()
    {
    Table("MATERIAL");
    Not.LazyLoad();
    }
    }

    при сохранении объекта типа RealMaterial генерируется следующий SQL:

    could not insert:
    INSERT INTO MATERIAL_AAA (MATERIAL_ID) VALUES (?)

    Заранее благодарен за помощь!!!

    ReplyDelete
  5. Извиняйте, жестко затупил, при "Table per subclass", только доп. поля кладутся в дочерние таблицы... Представлял себе по-другому.

    ReplyDelete
  6. Хоть бы структуру таблиц в базе выложил.

    ReplyDelete
  7. Согласен: не хватает структуры таблиц в БД для большей наглядности.

    ReplyDelete
  8. Структуру таблиц специально не выкладывал потому что пост только о FluentNhibernate. При желании по маппингам можно сгенерировать базу.

    ReplyDelete