Свободное отношение таблиц NHibernate 3 без первичных и внешних ключей

Справочная информация

У меня есть следующий класс, который я хочу сопоставить с NHibernate:

public class Player
{
    public virtual int Id { get; set; }
    public virtual Type Type { get; set; }
    public virtual string ScreenName { get; set; }
    public virtual bool Unsubscribed { get; set; }
}

На стороне базы данных у меня есть следующие таблицы:

-- New table
Player (
    int Id
    int TypeId (not null) -- foreign-key to Type table
    string ScreenName (not null) -- can be an EmailAddress, but not necessarily
)
Type (
    int Id
    string Name -- "Email", "Facebook", etc
)

Экранным именем игрока может быть адрес электронной почты («[email protected]»), экранное имя Twitter («@FooBar»), экранное имя Skype («foo.bar») или что-то еще в этом роде. Сопоставить первые три свойства Player с помощью Fluent NHibernate достаточно просто:

public class PlayerMap : ClassMap<Player>
{
    public PlayerMap()
    {
        Id(x => x.Id);
        Map(x => x.ScreenName)
            .Not.Nullable();
        References(x => x.Type)
            .Column("TypeId")
    }
}

public class TypeMap : ClassMap<Type>
{
    public TypeMap()
    {
        Id(x => x.Id);
        Map(x => x.Name);
    }
}

Но свойство Unsubscribed сложнее, потому что мне нужно получить эту информацию из двух устаревших таблиц, которые я не могу изменить и к которым я должен получить доступ в режиме только для чтения (вставки, обновления или удаления не разрешены):

-- Legacy tables, can't change
EmailAddress (
    int Id
    string EmailAddress (not null) -- "[email protected]"
)
Unsubscribed (
    int Id
    int EmailAddressId (not null) -- foreign key to EmailAddress table
)

Отписаться можно только от игроков по электронной почте, поэтому у игроков других типов никогда не будет строки ни в EmailAddress, ни в таблице Unsubscribed.

Это классы устаревших таблиц:

public class EmailAddress
{
    public virtual int Id { get; set; }
    public virtual string Value { get; set; }
    public virtual IList<Unsubscription> Unsubscriptions{ get; set; }
}

public class Unsubscription
{
    public virtual int Id { get; set; }
    public virtual EmailAddress EmailAddress { get; set; }
}

А вот их сопоставления Fluent:

public class EmailAddressMap : ClassMap<EmailAddress>
{
    public EmailAddressMap()
    {
        ReadOnly();
        Id(x => x.Id);
        Map(x => x.Value)
            .Column("EmailAddress")
            .Not.Nullable();
        HasMany(x => x.Unsubscriptions)
            .KeyColumn("EmailAddressId");
    }
}

public class EmailOptOutMap : ClassMap<EmailOptOut>
{
    public EmailOptOutMap()
    {
        ReadOnly();
        Id(x => x.Id);
        References(x => x.EmailAddress)
            .Column("EmailAddressId");
    }
}

Проблема

Моя проблема заключается в попытке получить информацию об отказе от подписки для игроков по электронной почте.

Единственный способ связать таблицу Unsubscribed с таблицей Player - это использовать промежуточную таблицу EmailAddress, соответствующую EmailAddress.EmailAddress и Player.AddressIdentifier, но мне сложно понять, как это сделать с помощью Fluent NHibernate.

Я посмотрел на Join для нескольких таблиц, но все примеры, которые я нашел, относятся только к двум таблицам, а не к трем:

  1. Объединение таблиц с помощью Fluent NHibernate
  2. Свободное левое соединение Nhibernate

person Community    schedule 10.02.2012    source источник


Ответы (2)


Одним из возможных решений может быть использование формулы для свойства Unsubscribed, доступного только для чтения, для динамического заполнения его значения на основе данных, хранящихся в устаревших таблицах. Вы можете сопоставить это следующим образом:

Map(x => x.Unsubscribed).Formula("(CASE WHEN EXISTS (SELECT EA.Id FROM EmailAddress EA INNER JOIN Unsubscribed ON EA.Id = Unsubscribed.EmailAddressId WHERE EA.EmailAddress = ScreenName) THEN 1 ELSE 0 END)").ReadOnly();

Конечно, вы можете дополнительно улучшить запрос выбора, добавив условие для TypeId, чтобы отфильтровать игроков, не получающих электронную почту. Кроме того, он позволяет вам избавиться от устаревших классов и сопоставлений, если они не используются где-то еще в приложении.

person Denis Ivin    schedule 10.02.2012
comment
Ваш ответ отлично работает! Просто хотел добавить дополнительную документацию для формул свойств из документации Hibernate. - person ; 13.02.2012

Чтобы дополнить ответ Дениса, я просто хотел добавить дополнительную документацию о том, как работают формулы свойств из Документы по Hibernate:

формула (необязательно): выражение SQL, определяющее значение для вычисляемого свойства. Вычисляемые свойства не имеют собственного сопоставления столбцов.

Мощная функция - производные свойства. Эти свойства по определению доступны только для чтения. Значение свойства вычисляется во время загрузки. Вы объявляете вычисление как выражение SQL. Затем это преобразуется в подзапрос предложения SELECT в запросе SQL, который загружает экземпляр:

<property name="totalPrice" formula="( SELECT SUM (li.quantity*p.price) FROM LineItem li, Product p WHERE li.productId = p.productId AND li.customerId = customerId AND li.orderNumber = orderNumber )"/>

Вы можете ссылаться на таблицу сущностей, не объявляя псевдоним для определенного столбца. В данном примере это будет customerId. Вы также можете использовать вложенный элемент сопоставления, если не хотите использовать атрибут.

person Community    schedule 28.07.2014