views:

189

answers:

1

Hi!

How can I delete a child from a bidirectional many-to-many association?

Deleting the child does not work because I get an exception (FK Violation).

Just removing the child from the parent and call saveorupdate on the parent does not do anything.

Entities:

public class Employee : Entity
{
    public virtual string LastName { get; set; }
    public virtual string FirstName { get; set; }
    public virtual string EMail { get; set; }
    public virtual IList<LoanedItem> LoanedItems { get; set; }
    public virtual ISet<Team> Teams { get; set; }

    public Employee()
    {
        if (LoanedItems == null)
        {
            LoanedItems = new List<LoanedItem>();
        }

        if (Teams == null)
        {
            Teams = new HashedSet<Team>();
        }
    }

    public virtual void AddTeam(Team team)
    {
        Teams.Add(team);
        team.Employees.Add(this);
    }

    public virtual void RemoveTeamFromEmployee(Team team)
    {
        Teams.Remove(team);
    }
}

public class Team : Entity
{
    public virtual string Name { get; set; }
    public virtual ISet<Employee> Employees { get; set; }

    public Team()
    {
        if (Employees == null)
        {
            Employees = new HashedSet<Employee>();
        }
    }

    public virtual void RemoveEmployeeFromTeam (Employee employee)
    {
        var result = Employees.Remove(employee);
    }

    public virtual void AddEmployee(Employee employee)
    {
        Employees.Add(employee);
        employee.Teams.Add(this);
    }
}

Mappings:

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

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

        // Employee is responible for the relationship
        HasManyToMany(p => p.Employees)
            .Table("TeamEmployee")
            .AsSet()
            .LazyLoad()
            .Inverse()
            .Cascade.SaveUpdate()
            .ParentKeyColumn("TeamId")
            .ChildKeyColumn("EmployeeId")
            .NotFound.Ignore();

    }
}

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);

        // Employee is responible for the relationship
        HasManyToMany(p => p.Teams)
            .Table("TeamEmployee")
            .AsSet()
            .LazyLoad()
            .ParentKeyColumn("EmployeeId")
            .ChildKeyColumn("TeamId")
            .Cascade.SaveUpdate()
            .NotFound.Ignore();

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

EDIT

Code to delete a team:

//Test Case 2
//Delete Team 2
Team team = session.Get<Team>(2);

List<Employee> employees = team.Employees.ToList();

foreach (var employee in employees)
{
    team.RemoveEmployeeFromTeam(employee);
}

session.Delete(team);
+1  A: 

If it is a many-to-many, then one direction is the "master" direction. The other direction will have inverse="false" defined. You have to remove from the collection the right way round - to be sure (and to truly represent what your deletion is achieving), delete both. Then flush the session to have this persisted.

Example:

if A has a collection of objects of type B, and vice versa, then to remove the association between two particular instances, you have to delete the instance of A from the instance of B's collection and vice versa. Then when the session is flushed, NHibernate knows to delete the relevant row from the linking table.

Edit now you've posted code

Try changing the two methods like so:

public virtual void RemoveTeamFromEmployee(Team team)
{
    Teams.Remove(team);
    team.Employees.Remove(this);
}

public virtual void RemoveEmployeeFromTeam (Employee employee)
{
    Employees.Remove(employee);
    employee.Teams.Remove(this);
}

To tidy up your code to delete a team, which is not responsible for the relationship, you do have to iterate over the linked employees. However, if you have to delete teams more frequently than employees, then you may find it more convenient to reverse this, and then you can just use:

team.Employees.Clear();
session.Delete(team);
David M
what is the right way to remove from the collection? I used my calls in the NH transaction scope, so afaik I dont need to flush manually.
Rookian
Can you edit your question to include the relevant bits of class and mapping definitions?
David M
there it is. I wanna delete a team (child) from the employee (parent).
Rookian
ah cool yes this works! But how can I write the code better? Look at the edited question...
Rookian
team.Employees.Clear(); looks pretty ugly in my opinion, but it should work because NHibernate would only really delete the team. But when I reuse the Employees collection it is empty and I think this is wrong.
Rookian
What do you mean "reuse the Employees collection"? The Team object in question is no longer valid - it represents a Team that has been deleted.
David M
yes you are right, we talked about DELETING and NOT REMOVING.
Rookian