Fluent NHibernate 仅级联删除关联记录

Posted

技术标签:

【中文标题】Fluent NHibernate 仅级联删除关联记录【英文标题】:Fluent NHibernate only cascade delete of association record 【发布时间】:2012-12-06 20:47:51 【问题描述】:

我们将 NHibernate 用于我们的会员系统。 User 可以是多个 Roles 的成员,Role 可以拥有多个用户。

RoleUser 被删除时,它应该只级联删除关联记录(“RoleUsers”表)。

删除Role 按预期工作。但是,删除 User 不会删除关联记录,因此由于外键约束而失败。

我在Role 一侧的映射:

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

User 侧的映射:

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

以及失败的测试:

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

我已经在用户映射上尝试了 Cascade 的所有组合,但它要么失败要么删除关联记录和角色(不是我想要的)。

【问题讨论】:

【参考方案1】:

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

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

1) 首先,我们可以去掉User.Roles集合的inverse设置。这将理顺 UsersRoles 关系的行为,并在 User 本身被删除之前强制删除它们。

2) 其次。如果UserRoles集合被标记为inverse

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

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

所以如果我们想继续你的场景:

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

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

[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) 那里没有使用 Cascade,但可以帮助减少 Session.Save(user)...

编辑:扩展点 3)

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

3) 我们甚至应该允许Role 完全管理它的Users 集合。让我们介绍一下casdace="all"(casdace="all-delete-orhpan" 如果应该删除没有任何角色的用户)。现在我们只能通过 Role 对象添加/更新用户。

角色的用户集合的映射应如下所示:

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

【讨论】:

您的回答实际上并没有回答问题,但很接近。我在问如何删除 User 和关联,而不是 just 关联。由于 Inverse 设置告诉 NHibernate 查找更改的哪一边,答案是我们需要在删除之前从其所有角色中删除我们想要删除的用户,即user.Roles.ForEach(role =&gt; role.RemoveUser(this))。如果您想更新您的答案,我很乐意将其标记为更新。 很遗憾,这不符合我们的要求,因为它不支持从用户中删除角色分配。我们不应该在角色上级联删除。我实际上是在自己回答这个问题:)

以上是关于Fluent NHibernate 仅级联删除关联记录的主要内容,如果未能解决你的问题,请参考以下文章

使用 Fluent NHibernate AutoMapping 进行级联保存

NHibernate Definitive Cascade 应用指南

Fluent Nhibernate 在使用引用的集合时引用了不正确的列名

如何使用 Fluent NHibernate 自动映射禁用特定抽象基类的子类化

当设置了级联保存更新属性时,NHibernate 会不必要地更新对象吗?

使用 NHibernate 进行级联更新一对多