NHibernate объединяет ассоциации "многие ко многим", выполняющие обе функции в качестве родительских, используя объект отношения в модели предметной области

StackOverflow https://stackoverflow.com/questions/1928591

Вопрос

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

Требования:

  • Команда и Сотрудник могут существовать без своего двойника.
  • В отношениях Команда-сотрудник команда несет ответственность (родительская) [используя позже TeamRepository].
  • В отношениях Сотрудник-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:

"объект ссылается на несохраненный временный экземпляр - сохраните временный экземпляр перед удалением.Тип:Ядро.Домен.Модель.Сотрудник, Юридическое лицо:Ядро.Домен.Модель.Сотрудник"

Когда я сохраняю все созданные команды и сотрудников, все сохраняется нормально, НО таблица отношений 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.удалить(..))?

Это было полезно?

Решение

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

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

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

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

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

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

Проблема родителя на обоих концах

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

Должно быть выполнено вашей бизнес-логикой:

  • сохранение новых команд и новых сотрудников
  • управление отношениями и поддержание их в синхронизации
  • удаление команд и сотрудников, когда они больше не используются.(Здесь явно нет постоянная сборка мусора реализация в NHibernate, по нескольким причинам.)

Другие советы

Похоже, вам нужен 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 из базы данных для вас.

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top