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

Сущности: Команда ‹-> Сотрудник команды ‹-> Сотрудник

Требования:

  • Команда и Сотрудник могут существовать без своего двойника.
  • В отношении Team-TeamEmployee ответственна команда (родительская) [используя позже TeamRepository].
  • В отношении Employee-TeamEmployee сотрудник является ответственным (родительским) [используя позже EmployeeRepository].
  • Дубликаты не допускаются.
  • Удаление Команды удаляет всех Сотрудников в Команде, если Сотрудник не входит в другую Команду.
  • При удалении Сотрудника удаляется только Команда, если Команда больше не содержит Сотрудников.

Отображение:

public class TeamMap : ClassMap<Team>
{
    public TeamMap()
    {
        // identity mapping
        Id(p => p.Id)
            .Column("TeamID")
            .GeneratedBy.Identity();

        // column mapping
        Map(p => p.Name);

        // associations
        HasMany(p => p.TeamEmployees)
            .KeyColumn("TeamID")
            .Inverse()
            .Cascade.SaveUpdate()
            .AsSet()
            .LazyLoad();
    }
}

public class EmployeeMap : ClassMap<Employee>
{
    public EmployeeMap()
    {
        // identifier mapping
        Id(p => p.Id)
            .Column("EmployeeID")
            .GeneratedBy.Identity();

        // column mapping
        Map(p => p.EMail);
        Map(p => p.LastName);
        Map(p => p.FirstName);

        // associations
        HasMany(p => p.TeamEmployees)
            .Inverse()
            .Cascade.SaveUpdate()
            .KeyColumn("EmployeeID")
            .AsSet()
            .LazyLoad();

        HasMany(p => p.LoanedItems)
            .Cascade.SaveUpdate()
            .LazyLoad()
            .KeyColumn("EmployeeID");
    }
}

public class TeamEmployeeMap : ClassMap<TeamEmployee>
{
    public TeamEmployeeMap()
    {
        Id(p => p.Id);

        References(p => p.Employee)
            .Column("EmployeeID")
            .LazyLoad();

        References(p => p.Team)
            .Column("TeamID")
            .LazyLoad();
    }
}

Создание сотрудников и команд:

    var employee1 = new Employee { EMail = "Mail", FirstName = "Firstname", LastName = "Lastname" };
    var team1 = new Team { Name = "Team1" };
    var team2 = new Team { Name = "Team2" };

    employee1.AddTeam(team1);
    employee1.AddTeam(team2);


    var employee2 = new Employee { EMail = "Mail2", FirstName = "Firstname2", LastName = "Lastname2" };
    var team3 = new Team { Name = "Team3" };

    employee2.AddTeam(team3);
    employee2.AddTeam(team1);

    team1.AddEmployee(employee1);
    team1.AddEmployee(employee2);
    team2.AddEmployee(employee1);
    team3.AddEmployee(employee2);

    session.SaveOrUpdate(team1);
    session.SaveOrUpdate(team2);
    session.SaveOrUpdate(team3);

    session.SaveOrUpdate(employee1);
    session.SaveOrUpdate(employee2);

После этого я фиксирую изменения с помощью transaction.Commit (). Первая странность заключается в том, что мне нужно сохранить команды и сотрудников вместо одного из них (почему ?!). Если я сохраню только все команды или (Xor) всех сотрудников, я получу TransientObjectException:

"объект ссылается на несохраненный временный экземпляр - сохраните временный экземпляр перед сбросом. Тип: Core.Domain.Model.Employee, Entity: Core.Domain.Model.Employee"

Когда я сохраняю все созданные команды и сотрудников, все сохраняется нормально, НО в таблице отношений TeamEmployee есть повторяющиеся ассоциации.

ID EID TID
1  1   1
2  2   1
3  1   2
4  2   3
5  1   1
6  1   2
7  2   3
8  2   1

Таким образом, вместо 4 отношений получается 8 отношений. 4 отношения для левой стороны и 4 отношения для правой стороны. : [

Что я не так?

Дополнительные вопросы: когда я удаляю команду или сотрудника, мне нужно удалить команду или сотрудника из списка TeamEmployee в объектной модели, или NHibernate выполняет эту работу за меня (используя session.delete (..))?


person Rookian    schedule 18.12.2009    source источник


Ответы (4)


Вы говорите о бизнес-логике. Целью NHibernate не является реализация бизнес-логики.

Что делает ваш код:

Вы сопоставили две разные коллекции TeamEmployee, одну в Team, одну в Employee. В коде вы добавляете элементы в обе коллекции, каждый раз создавая новые экземпляры TeamEmployee. Так почему же вы ожидаете, что NHibernate не должен хранить все эти отдельные экземпляры?

Что можно сделать, чтобы это исправить:

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

Если вам не нужен класс, гораздо проще отобразить его как отношение «многие ко многим» (как уже было предложено Крис Конвей). Поскольку в памяти есть две коллекции, которые, как ожидается, будут содержать одни и те же данные, вы указываете NHibernate игнорировать одну из них при сохранении, используя Inverse.

Проблема с обоими родителями

На обоих концах нет родителя. Я думаю, ясно, что ни Команда, ни Сотрудник не являются родителями друг друга, они независимы. Вы, вероятно, имеете в виду, что они оба являются родителями промежуточного звена TeamEmployee. Они не могут быть родителями (и, следовательно, владельцами) одного и того же экземпляра. Либо один из них является родительским, либо это другой независимый экземпляр, что значительно усложняет управление им (вот как вы его реализовали сейчас). Если вы сопоставите его как отношение «многие ко многим», им будет управлять NHibernate.

Выполняется в соответствии с вашей бизнес-логикой:

  • хранение новых команд и новых сотрудников
  • управление отношениями и их синхронизация
  • удаление команд и сотрудников, когда они больше не используются. (В NHibernate явно отсутствует реализация постоянной сборки мусора по нескольким причинам.)
person Stefan Steinegger    schedule 01.01.2010
comment
почему на стороне команды нет опции каскадирования? - person Rookian; 02.01.2010
comment
... и как мне удалить команду с настройкой Криса Конвея? - person Rookian; 02.01.2010
comment
Я бы здесь ничего не каскадировал, но это дело вкуса. ИМХО, когда есть два независимых объекта, ссылающихся друг на друга, должна быть бизнес-логика для их создания, а не каскадирование. Даже если вам нужно позаботиться об этом в коде, это яснее. --- Когда вы удаляете команду, вам нужно перебрать сотрудников и удалить их всех. Когда вы удаляете сотрудника, вы можете использовать запрос, который возвращает все упомянутые команды, у которых нет других сотрудников, а также удаляет их. Вы можете использовать HQL для прямого удаления сущностей, не загружая их. - person Stefan Steinegger; 03.01.2010

Похоже, вам нужна HasManyToMany вместо двух карт HasMany. Кроме того, нет необходимости в TeamEmployeeMap, если у вас нет другого свойства в этой таблице, которое необходимо сопоставить. Другое дело, только одна сторона должна иметь набор Inverse (), и, поскольку вы добавляете команды к сотрудникам, я думаю, вам нужно сделать TeamMap обратным. Наличие инверсии только с одной стороны избавит от повторяющихся записей в базе данных.

Может быть, примерно так:

public class TeamMap : ClassMap<Team>
{
    public TeamMap()
    {
        // identity mapping
        Id(p => p.Id)
           .Column("TeamID")
           .GeneratedBy.Identity();

        // column mapping
        Map(p => p.Name);

        // associations
        HasManyToMany(x => x.TeamEmployees)
            .Table("TeamEmployees")
            .ParentKeyColumn("TeamID")
            .ChildKeyColumn("EmployeeID")
            .LazyLoad()
            .Inverse()
            .AsSet();
    }
}

public class EmployeeMap : ClassMap<Employee>
{
    public EmployeeMap()
    {
        // identifier mapping
        Id(p => p.Id)
            .Column("EmployeeID")
            .GeneratedBy.Identity();

        // column mapping
        Map(p => p.EMail);
        Map(p => p.LastName);
        Map(p => p.FirstName);

        // associations
        HasManyToMany(x => x.TeamEmployees)
            .Table("TeamEmployees")
            .ParentKeyColumn("EmployeeID")
            .ChildKeyColumn("TeamID")
            .Cascade.SaveUpdate()
            .LazyLoad()
            .AsSet();

        HasMany(p => p.LoanedItems)
            .Cascade.SaveUpdate()
            .LazyLoad()
            .KeyColumn("EmployeeID");
    }
}

Используя это, удаление удалит за вас TeamEmployee из базы данных.

person Chris Conway    schedule 18.12.2009
comment
Сопоставление связи "многие ко многим" с NH не позволяет обоим концам связи быть родителями, поэтому я использую третий объект отношения. - person Rookian; 18.12.2009

Ознакомьтесь с этим руководством и, в частности, как сопоставление между Product и Store настроен.

person Tomas Aschan    schedule 18.12.2009
comment
Сопоставление связи "многие ко многим" с NH не позволяет обоим концам связи быть родителями, поэтому я использую третий объект отношения. В этом отображении существует только один родитель и один ребенок. - person Rookian; 18.12.2009
comment
@dav_i: Спасибо, что указали на это. Я очень давно не работал с Fluent Nhibernate, но надеюсь, что их новое руководство по началу работы содержит примерно то же самое, что и старое. - person Tomas Aschan; 16.01.2014

NHibernate не допускает ассоциации "многие ко многим" с родителями на обоих концах.

person user224564    schedule 30.12.2009
comment
Вы уверены, что NH обычно не допускает общения «многие-ко-многим» с родителями с обеих сторон? Я ДУМАЮ, что это работает не с ключевым словом NH многие-ко-многим, а с ассоциациями «один ко многим», не так ли? - person Rookian; 30.12.2009