Программирование интерфейсов при отображении с помощью Fluent NHibernate - PullRequest
25 голосов
/ 10 мая 2009

Меня отправили в подчинение, и я начал изучать Свободный NHibernate (без предыдущего опыта NHibernate). В моем проекте я программирую на интерфейсы для уменьшения связи и т. Д. Это означает, что почти все относится к интерфейсу, а не к конкретному типу (IMessage вместо Message). Идея заключается в том, чтобы помочь сделать его более тестируемым, позволяя имитировать зависимости.

Однако (свободно) NHibernate не любит, когда я пытаюсь сопоставить интерфейсы вместо конкретных классов. Проблема проста - согласно Fluent Wiki, было бы разумно определить поле идентификатора моего класса, например,

int Id { get; private set; }

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

int Id { get; set; }

и я предполагаю, что это сводит на нет приватность сеттера в конкретном классе (идея заключается в том, что только NHibernate должен когда-либо устанавливать идентификатор, назначенный БД).

Пока, я полагаю, я просто сделаю сеттер публичным и постараюсь избежать соблазна написать ему ... Но есть ли у кого-нибудь представление о том, что было бы "правильным", наилучшим способом создания правильного поле первичного ключа, в которое может записывать только NHibernate, в то же время программируя только интерфейсы?

ОБНОВЛЕНО

Из того, что я понимаю после двух приведенных ниже ответов от mookid и Джеймса Грегори, я вполне могу оказаться на неправильном пути - у меня не должно быть причин иметь интерфейс для каждой сущности, как сейчас. Это все хорошо. Я думаю, что мой вопрос становится: нет ли причины программировать на 100% против интерфейса для каких-либо объектов? И если есть хотя бы одна ситуация, когда это может быть оправдано, возможно ли это сделать с помощью (свободно) NHibernate?

Я спрашиваю, потому что я не знаю, чтобы не критиковать. Спасибо за ответы. :)

Ответы [ 5 ]

12 голосов
/ 10 мая 2009

Я понимаю, что это диверсия, а не ответ на ваш вопрос (хотя я думаю, что Mookid это покрыл).

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

Например: как полагаться на IMessage меньше, чем полагаться на Message, когда они оба (почти), несомненно, имеют одинаковые подписи? Вам не нужно издеваться над сущностью, потому что редко она обладает достаточным поведением, чтобы требовать насмешки.

10 голосов
/ 15 июля 2009

У меня точно такая же проблема. К сожалению, у меня есть веская причина для использования интерфейсов сущностей; Модель сущностей будет реализована различными способами и с разными сопоставлениями для каждого клиента.

Вся модель должна быть доступна только для чтения, поэтому интерфейсы имеют стиль:

public interface IAccount
{
    long AccountId { get; }
    IHouse House { get; }
}

public interface IHouse
{
    long HouseId { get; }
    HouseStatus Status { get; }
    IList<IAccount> Accounts { get; }
}

Конкретные реализации затем реализуют их с помощью внутренних сеттеров:

public class Account: IAccount
{
    public virtual long AccountId { get; internal set; }
    public virtual IHouse House { get; internal set; }
}

public class House: IHouse
{
    public virtual long HouseId { get; internal set; }
    public virtual HouseStatus Status { get; internal set; }
    public virtual IList<IAccount> Accounts { get; internal set; }
}

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

HasMany(x => x.Accounts)

может стать

HasMany<Account>(x => x.Accounts)

Но нет эквивалентного «броска» для

References(x => x.House)

Отображение на интерфейсы (более точное решение) поднимает проблему, упомянутую выше, в том смысле, что идентификатор должен существовать в самом верхнем классе для настройки и требует установки на интерфейсе.

public sealed class AccountMap : ClassMap<IAccount>
{
    public PokerPlayerMap()
    {
        Id(x => x.AccountId, "account_id");

        DiscriminateSubClassesOnColumn("Type").SubClass<Account>(s =>  
        {
            References(x => x.House);       
        });
    }
}

Пока что мое единственное решение - добавить сеттеры во все поля идентификатора интерфейса. Жаль, что Id не может существовать внутри подкласса или его тип приведен из интерфейса.

10 голосов
/ 10 мая 2009

Вы можете настроить свой интерфейс так, чтобы он содержал только геттер:

public interface ISomeEntity
{
    int Id { get; }
}

Ваш конкретный класс также может реализовывать сеттер, и, поскольку вы программируете свои интерфейсы, вы никогда не будете вызывать сеттер "случайно".

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

Id(e => e.Id).Access.AsCamelCaseField();

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

8 голосов
/ 12 мая 2009

ОБНОВЛЕНИЕ: использование union-подкласса не поддерживается через интерфейс fluent, который обеспечивает fluent-nhibernate. Вам нужно будет использовать обычный файл отображения hbm и добавить его.

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

По сути, вы создаете определение отображения для базового класса (в данном случае вашего интерфейса) и указываете, как NHibernate должен обращаться с реализаторами, используя union-subclass.

Так, например, это должно позволить вам создать полиморфные ассоциации:

<class name="IAccountManager"
                abstract="true"
                table="IAccountManager">

        <id name="Id">
                <generator class="hilo"/>
        </id>

        <union-subclass
                table="DefaultAccountManager"
                name="DefaultAccountManager">
                <property name="FirstName"/>
        </union-subclass>

        <union-subclass
                table="AnotherAccountManagerImplementation"
                name="AnotherAccountManagerImplementation">
                <property name="FirstName"/>
        </union-subclass>
        ...
</class> 

Обратите внимание, что идентификатор одинаков для всех конкретных исполнителей. NHibernate требует этого. Кроме того, таблица IAccountManager фактически не существует.

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

4 голосов
/ 20 июля 2010

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

Ссылки теперь имеют общую перегрузку, чтобы разрешить приведение, которое Гекко искал в своем ответе.

...