1

We're using NHibernate for our membership system. A User can be a member of many Roles and a Role can have many users.

When a Role or a User is deleted, it should only cascade the delete of the association record ("RoleUsers" table).

Deleting a Role works as expected. However, deleting a User does not delete the association record and as such fails due to a foreign key constraint.

My mapping on the Role side:

        HasManyToMany(r => r.Users)
            .Access.CamelCaseField()
            .Table("RoleUsers")
            .ParentKeyColumn("RoleId")
            .ChildKeyColumn("UserId")
            .AsSet();

Mapping on the User side:

        HasManyToMany(u => u.Roles)
            .Access.CamelCaseField()
            .Table("RoleUsers")
            .ParentKeyColumn("UserId")
            .ChildKeyColumn("RoleId")
            .Inverse(); // we'll add user to role, not role to user

And the failing test:

    [Test]
    public void Deleting_user_should_not_delete_roles()
    {
        var user = new User("john@doe.com", "John", "Doe", "Secr3t");
        var role = new Role("Admin");
        role.AddUser(user);

        object id;
        using (var txn = Session.BeginTransaction())
        {
            id = Session.Save(user);
            Session.Save(role);
            txn.Commit();
        }

        Session.Clear();

        var fromDb = Session.Get<User>(id);

        using (var txn = Session.BeginTransaction())
        {
            Session.Delete(fromDb);
            txn.Commit();
        }

        Session.Query<Role>().Count().ShouldEqual(1);
    }

I've tried every combination of Cascade on the user mapping and it either fails or deletes the association record AND the role (not what I want).

4

1 回答 1

2

逆向级联是两个不同的概念。当然,两者支持<many-to-many>关系。请参阅文档 6.8(几乎向下滚动到 6.9)

http://nhibernate.info/doc/nh/en/index.html#collections-bidirectional

1)首先,我们可以去掉集合的设置。User.Roles这将理顺用户与角色的关系行为用户本身被删除之前强制将其删除。

2)其次。如果User的Roles集合被标记为,inverse

HasManyToMany(u => u.Roles)
  ...
  .Inverse(); // we'll add user to role, not role to user

删除任何User,将永远不会触发该对的删除。那是因为我们明确地说:唯一关心这种关系的人是Role.

因此,如果我们想继续您的场景:

。逆(); // 我们将用户添加到角色,而不是角色添加到用户

我们应该保持一致。“我们将从角色中删除用户,而不是从用户中删除角色”:

[Test]
public void Deleting_user_should_not_delete_roles()
{
  var user = new User("john@doe.com", "John", "Doe", "Secr3t");
  var role = new Role("Admin");
  role.AddUser(user);

  object roleId;
  object id;
  using (var txn = Session.BeginTransaction())
  {
    id = Session.Save(user);
    roleId = Session.Save(role);
    txn.Commit();
  }

  Session.Clear();

  // take both from DB
  var userFromDb = Session.Get<User>(id);
  var roleFromDb = Session.Get<Role>(roleId);

  using (var txn = Session.BeginTransaction())
  {
     // HERE just remove the user from collection
     roleFromDb.Users.Remove(userFromDb);

     // all relations will be deleted
     Session.Save(roleFromDb);
     txn.Commit();
  }
  ... 
  // assertions
  // "John's" relation to Role "admin" is deleted
}

注意: 3)在那里没有使用级联,但可以帮助减少 Session.Save(user)...

编辑:扩展点 3)

正如 Ben Foster 在评论中注意到的那样,删除了用户。

3)我们甚至应该允许完全Role管理它的用户集合。让我们介绍一下casdace="all" (casdace="all-delete-orhpan" if User without any Role 应该被删除)。现在我们只能通过 Role 对象添加/更新用户。

Role 的 Users 集合的映射应如下所示:

HasManyToMany(r => r.Users)
  .Access.CamelCaseField()
  .Table("RoleUsers")
  .ParentKeyColumn("RoleId")
  .ChildKeyColumn("UserId")
  //.Cascade.All(); // just save or update instance of users
  .Cascade.AllDeleteOrphan(); // will even delete User without any Role
  .AsSet();

有了逆和级联,我们可以调整测试:

[Test]
public void Deleting_user_should_not_delete_roles()
{
  var user = new User("john@doe.com", "John", "Doe", "Secr3t");
  var role = new Role("Admin");
  role.AddUser(user);

  object roleId;
  using (var txn = Session.BeginTransaction())
  {
     // I. no need to save user
     roleId = Session.Save(role);
     ...

然后调用它来摆脱一个用户

...
using (var txn = Session.BeginTransaction())
{
  var user = Session.Get<User>(id);
  var roles = user.Roles.ToList();
  roles.ForEach(role => role.RemoveUser(user))
  // II. not only relations, but even the User is deleted
  // becuase updated roles triggered delete orhpan
  // (no Session.Update() call there)
  txn.Commit();
}
于 2012-12-07T08:19:48.573 回答