Fluent NHibernate - отображение внешнего ключа как свойства

Я ищу способ добавить связь между двумя объектами и иметь настраиваемый идентификатор для внешнего ключа. Я просмотрел предыдущие сообщения, но самое близкое, что я могу найти, - это предложение. Загрузить ассоциацию - это не то, на что я надеюсь. Я знаю, что это можно сделать в Entity Framework с привязкой .HasForeignKey, но я не могу найти способ сделать это в Fluent NHibernate.

Возьмем два примера сущностей:

public class Ticket
{
    public virtual int Id { get; set; }
    public virtual string Title { get; set; }
    public virtual string ServiceId { get; set; }
    public virtual Service Service { get; set; }
}

public class Service
{
    public virtual string Id { get; set; }
}

Я хочу иметь возможность создать новый экземпляр Ticket и назначить ему Службу, используя следующие средства (предположим, что связанная Служба уже есть в таблице):

Ticket ticket = new Ticket() {
    Title = "Problem with MS Word",
    ServiceId = "Microsoft Word 2012"
};

Я не хочу делать следующее:

Ticket ticket = new Ticket() {
    Title = "Problem with MS Word",
    Service = Session.Load<Service>("Microsoft Word 2012")
};

У меня есть веские причины для этого, и, как я уже сказал, это можно сделать в Entity Framework, но я действительно озадачен тем, как добиться того же в Fluent NHibernate. Мои сопоставления в настоящее время выглядят так:

public class TicketMapping : ClassMap<Ticket>
{
    public TicketMapping()
    {
        Id(m => m.Id);
        Map(m => m.Title).Column("Title");
        Map(m => m.ServiceId).Column("ServiceId");
        HasOne(m => m.Service).ForeignKey("ServiceId");

        Schema("dbo");
        Table("Tickets");
    }
}

public class ServiceMapping : ClassMap<Service>
{
    public ServiceMapping()
    {
        Id(m => m.Id);

        Schema("dbo");
        Table("Services");
    }
}

Любая помощь всегда приветствуется!


Просто быстрое редактирование для Джея - причина, по которой я не хочу сеанс. Загрузить мой элемент, заключается в том, что я не хочу, чтобы мой уровень представления (MVC 3) знал что-либо о NHibernate, поэтому я используя шаблон репозитория и вводя единый репозиторий в контроллер. Так, например, у меня будет TicketRepository, который соответствует следующему контракту

public interface IRepository<T>
{
    T GetById(object id);
    void Create(T entity);
    void Update(T entity);
    void Delete(T entity);
}

Я не хочу вводить ServiceRepository только для того, чтобы получить ссылку на Службу для Билета.


person Paul Aldred-Bann    schedule 14.08.2012    source источник
comment
Не могли бы вы рассказать об уважительных причинах отказа от использования Session.Load?   -  person Jay    schedule 14.08.2012
comment
@Jay, не уверен в причине Террика ... но иногда может быть нелепо загружать объект только для того, чтобы иметь возможность ссылаться на ключ, который у вас уже есть перед сохранением, ИМХО. У нас было строгое соглашение об уровне обслуживания в предыдущем проекте, в котором нам пришлось проделать то же самое с Entity Framework, прежде чем он получил поддержку FK.   -  person Kevin Nelson    schedule 14.08.2012
comment
@Jay обновил мой пост с указанием причин. Кроме того, Кевин хорошо замечает, я не хочу дополнительных накладных расходов на загрузку другого объекта для этого связанного.   -  person Paul Aldred-Bann    schedule 14.08.2012
comment
@KevinNelson Session.Load не загружает объект; вот почему это метод ссылки на внешний ключ с известным идентификатором. База данных будет поражена только в том случае, если вы попытаетесь получить доступ к свойствам проксируемого объекта.   -  person Jay    schedule 14.08.2012
comment
@Terric Как я упоминал выше, Session.Load не загружает объект. Однако, помимо этого, я бы не советовал не допускать попадания NHibernate в ваши контроллеры. NHibernate - это абстракция, и вы отказываетесь от большей части того, что является мощным, если пытаетесь скрыть это. См. ayende.com/blog / 4567 /   -  person Jay    schedule 14.08.2012
comment
@Jay - ах, круто, не знал, что NHibernate использует прокси или что-то еще, чтобы избежать загрузки объекта ... вероятно, одна из многих причин, по которым многие считали, что это выходит за рамки EF, когда EF впервые появился. Однако, по мнению Террика, если они переключатся с NHibernate на Entity Framework, они определенно не захотят, чтобы их контроллеры были завалены NHibernate ... поэтому использование DI на контроллере с интерфейсом IRepository является хорошей практикой.   -  person Kevin Nelson    schedule 14.08.2012
comment
@KevinNelson Это предмет споров, можно ли сэкономить усилия при переключении ORM. Кажется, вы просто берете на себя боль заранее и на протяжении всей разработки, а не когда (ЕСЛИ!) Действительно вносите это изменение. Менее спорным, на мой взгляд, является то, является ли хорошей практикой наличие репозитория для каждой сущности - это не так. Репозиторий должен быть per-aggregate-root, поэтому вам не нужно вручную сшивать ассоциации, используя несколько репозиториев.   -  person Jay    schedule 14.08.2012
comment
@Jay - Это (ЕСЛИ!) То, за что я заплатил волынщику. Однако для некоторых приложений даже анти-шаблон smart-ui - неплохая идея. Я не говорил, что есть правильное или неправильное, но говорил, что паттерн Террика - хорошая / законная практика.   -  person Kevin Nelson    schedule 14.08.2012
comment
Спасибо за дискуссию, ребята, некоторые очень интересные моменты - особенно репозиторий per-aggregate-root, что я не рассматривал. Основная причина, по которой я пытаюсь уберечь NHibernate от уровня представления, заключается в том, что он может быть отправлен другим командам, которые подключат свою собственную сборку DAL (которая соответствует уже указанным мною контрактам и модели сущностей). Этот DAL может быть EF, ADO.Net, NHibernate или чем-то еще. Я действительно думаю, что мне нужно еще раз взглянуть на мою стратегию репозитория, как говорит Джей.   -  person Paul Aldred-Bann    schedule 15.08.2012


Ответы (2)


На мой взгляд, вы не можете избежать использования Session.Load (id) при использовании NHibernate. Как упоминалось в комментариях, это НЕ попадет в базу данных, просто создайте прокси-объект с идентификатором.

Некоторые возможные варианты:

  1. Вставьте второй общий репозиторий (ServiceRepository) в контроллер. Я не вижу в этом проблемы, но по какой-то причине вы хотите этого избежать. Вы можете добавить метод LoadById в общий интерфейс и реализовать его по-разному в каждой реализации для NH и EF (или других). В EF impl этот метод может работать так же, как GetById, а в NH impl он вызывает Session.Load
  2. Реализуйте неуниверсальный репозиторий для AR (совокупный корень), которым в данном случае будет Ticket. У него могут быть определенные методы для загрузки службы, а также билета.
  3. Реализуйте другую абстракцию поверх двух репозиториев и вставьте ее в контроллер вместо двух репозиториев в варианте 1. Это может f.ex быть упорным игнорирующим UnitOfWork, как описано здесь: Persistance ignorant UoW или какой-то сервис приложений, который организует создание билетов или TicketFactory.

Из 3 вариантов вариант 1, вероятно, самый простой, а 3 могут обеспечить лучшую абстракцию и большую ремонтопригодность в будущем.

person Trygve    schedule 31.08.2012
comment
Спасибо, лучшее понимание Session.Load означало, что я больше не беспокоюсь о его использовании. Вместо этого я использовал entity framework и отказался от NHibernate (в основном потому, что мне удобнее работать с EF, но я хотел посмотреть, что NHibernate может предложить). - person Paul Aldred-Bann; 07.09.2012
comment
Я обдумывал идею преобразования моих внешних ключей в целые числа вместо объектов, потому что я не хотел, чтобы мне приходилось обращаться к БД для каждого связанного объекта при каждой вставке или обновлении ... пока я не наткнулся на этот вопрос и не узнал о сеансе .Нагрузка(). После сравнения результатов журнала nhibernate бок о бок для простых операций - вау! ОГРОМНАЯ экономия! Спасибо! - person Brett; 01.03.2013

Вы можете использовать хитрость. Чтобы ваш код был чистым.

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

перед добавлением или обновлением

if (!string.IsNullOrEmpty(ServiceId) && Service == null)
{
   Service = new Service{ Id = ServiceId };
}

нормальная работа репозитория ...

Тестирую и работаю. Ваша архитектура все еще свободна от решений ORM

person RJardines    schedule 28.10.2012
comment
предположим, что если класс обслуживания имеет несколько полей, в этом случае нам нужно назначить здесь все поля? поскольку я столкнулся с той же проблемой .. - person jkyadav; 28.08.2015