【问题标题】:How to use 3 foreign keys in many-to-many join table in Sequelize如何在 Sequelize 的多对多连接表中使用 3 个外键
【发布时间】:2022-08-02 21:25:26
【问题描述】:

我要这个:

我可以在几分钟内使用 SQL 手动完成此操作,这是一个非常常见的场景,所以我不得不认为 Sequelize 中有一种方法。

用户可以在许多组织中担任许多角色。我可能是 Acme 的管理员,但只是 Microsoft 的用户。看起来像这样的数据:

用户数据:

组织数据:

角色:

然后,当然,我可以把它们放在一起:

select
    u.username,
    r.name,
    o.name
from
    \"user\" u
inner join
    user_role_organization uro on u.id = uro.user_id
inner join
    organization o on uro.organization_id = o.id
inner join
    role r on uro.role_id = r.id

我正在使用的真实世界模型如下所示:

const orgModel = {
    id: {
        type: DataTypes.UUID,
        primaryKey: true,
        allowNull: false
    },
    name: {
        type: DataTypes.STRING(100),
        allowNull: false
    }
};
const roleModel = {
    id: {
        type: DataTypes.UUID,
        primaryKey: true,
        allowNull: false
    },
    name: {
        type: DataTypes.STRING(100),
        allowNull: false
    }
};
const userModel = {
    id: {
        type: DataTypes.UUID,
        primaryKey: true,
        allowNull: false
    },
    username: {
        type: DataTypes.STRING(100),
        allowNull: false
    }
};
const organizationUserToRoleModel = {
    id : {
        type: DataTypes.INTEGER,
        autoIncrement: true,
        primaryKey: true
    },
    organization_id: {
        type: DataTypes.UUID,
        allowNull: false
    },
    role_id: {
        type: DataTypes.UUID,
        allowNull: false
    },
    user_id: {
        type: DataTypes.UUID,
        allowNull: false
    }
};

...以及他们各自的关系

auth_user.belongsToMany(auth_organization, { as: \"AuthOrganizations\", through: organization_to_user_to_role, foreignKey: \"user_id\" });
auth_organization.belongsToMany(auth_user, { as: \"AuthUsers\", through: organization_to_user_to_role, foreignKey: \"organization_id\" });

auth_organization.belongsToMany(role, { as: \"Roles\", through: organization_to_user_to_role, foreignKey: \"organization_id\" });
role.belongsToMany(auth_organization, { as: \"RoleOrganizations\", through: organization_to_user_to_role, foreignKey: \"role_id\" });

auth_user.belongsToMany(role, { as: \"OrganizationUserRoles\", through: organization_to_user_to_role, foreignKey: \"user_id\" });
role.belongsToMany(auth_user, { as: \"OrganizationRoleUsers\", through: organization_to_user_to_role, foreignKey: \"role_id\" });

我最终得到了一些看起来正确的东西:

但是,在播种类似数据时出现以下错误:

ValidationErrorItem {
  message: \'organization_id must be unique\',
  type: \'unique violation\',
  path: \'organization_id\',
  value: \'385e2860-094d-11ed-a072-25e64f3c77e7\',
  origin: \'DB\',
  instance: null,
  validatorKey: \'not_unique\',
  validatorName: null,
  validatorArgs: []
}

没有任何意义,除了“id”之外的任何东西都需要在该表中是唯一的,不是吗?我猜它是因为是外键而强制唯一性?我使用如下填充的值来做到这一点:

let acmeOrg = await auth_organization.findOne({ where: { name: \"ACME Corp.\" } });
let fakeOrg = await auth_organization.findOne({ where: { name: \"Fake, Inc.\" } });

let user1 = await auth_user.findOne({ where: { username: \"user1\" } });
let user2 = await auth_user.findOne({ where: { username: \"user2\" } });

let ownerRole = await role.findOne({ where: { name: \"Owner\" } });
let adminRole = await role.findOne({ where: { name: \"Admin\" } });
let userRole = await role.findOne({ where: { name: \"User\" } });

await user1.addAuthOrganizations(acmeOrg, 
    { 
        through: { 
            role_id: ownerRole.id
        } 
    });
await user2.addAuthOrganizations(acmeOrg, 
    { 
        through: { 
            role_id: adminRole.id
        } 
    });
await user1.addAuthOrganizations(fakeOrg, 
    { 
        through: { 
            role_id: userRole.id
        } 
    });

我拥有比 Sequelize 更多的历史和关系数据。我还为连接表尝试了这个模型,它创建了一个非常陌生的模型,它在 user_id 和 organization_id 字段上强制使用复合主键,即使我设置了 primaryKey: false。

编辑 1

我怀疑这完全取决于我如何为模型构建 FK,只是来自之前的 Sequelize 冒险。我只是尝试将 unique 设置为 false 并像这样设置 FK - 它现在抱怨 \"user_id\" 必须是唯一的,即使这不是真的,至少根据我的意图。

let organizationUserToRoleModel = {
    id: {
        type: DataTypes.INTEGER,
        autoIncrement: true,
        primaryKey: true
    },
    organization_id: {
        type: DataTypes.UUID,
        allowNull: false,
        unique: false
    },
    role_id: {
        type: DataTypes.UUID,
        allowNull: false,
        unique: false
    },
    user_id: {
        type: DataTypes.UUID,
        allowNull: false,
        unique: false
    }
};

auth_user.belongsToMany(auth_organization, { as: \"AuthUserOrganizations\", through: organization_to_user_to_role, foreignKey: \"user_id\" });
auth_organization.belongsToMany(auth_user, { as: \"OrganizationAuthUsers\", through: organization_to_user_to_role, foreignKey: \"organization_id\" });

auth_organization.belongsToMany(role, { as: \"AuthOrganizationRoles\", through: organization_to_user_to_role, foreignKey: \"organization_id\" });
role.belongsToMany(auth_organization, { as: \"RoleAuthOrganizations\", through: organization_to_user_to_role, foreignKey: \"role_id\" });

编辑2:

找到原因了!无论我对模型做什么,都会将唯一约束添加到外键中。这是连接表的最新模型:

let organizationUserToRoleModel = {
    id: {
        type: DataTypes.INTEGER,
        autoIncrement: true,
        primaryKey: true
    },
    organization_id: {
        type: DataTypes.UUID,
        allowNull: true,
        constraints: false,
        unique: false
    },
    role_id: {
        type: DataTypes.UUID,
        allowNull: true,
        constraints: false,
        unique: false
    },
    user_id: {
        type: DataTypes.UUID,
        allowNull: true,
        constraints: false,
        unique: false
    }
};

但是,当我检查结果时它们仍然被创建:

ALTER TABLE auth.organization_to_user_to_role ADD CONSTRAINT organization_to_user_to_role_organization_id_role_id_key UNIQUE (organization_id, role_id)

ALTER TABLE auth.organization_to_user_to_role ADD CONSTRAINT organization_to_user_to_role_user_id_key UNIQUE (user_id)

如果我手动删除它们,我可以播种预期的数据并在没有问题的情况下查询它,如下所示:

select
    u.username
from
    auth_user u
inner join
    organization_to_user_to_role our
    on u.id = our.user_id 
inner join
    auth_organization ao 
    on ao.id = our.organization_id 
inner join
    \"role\" r 
    on r.id = our.role_id 

我觉得我超级接近,但不知道如何防止FK 约束被创建。将约束设置为 false 似乎在这里没有任何作用。我想我可以在事后对它们的删除进行编码,但这似乎很笨拙且不正确。

编辑 3:

我在模型本身上尝试了一些不同的东西,以及键的关系,但是我得到了完全相同的结果和完全相同的唯一约束。如果我什至可以让它设置一个单身的对所有 3 个键的唯一约束(现在它们都是复合键的一部分),这就足够了。

当前型号,我更喜欢:

let organizationUserToRoleModel = {
    organization_id: {
        type: DataTypes.UUID,
        primaryKey: true,
        constraints: false,
        unique: false
    },
    role_id: {
        type: DataTypes.UUID,
        primaryKey: true,
        constraints: false,
        unique: false
    },
    user_id: {
        type: DataTypes.UUID,
        primaryKey: true,
        constraints: false,
        unique: false
    }
};

似乎 \"constraints\" 和 \"unique\" 的效果为零。与我之前的尝试相比,唯一的区别是复合键比无用的自动增量 PK 更有意义。

标签: javascript sql database database-design sequelize.js


【解决方案1】:

看起来你到了那里。

通常,在模型中提供一组 FK 值的任何表都要求将值列本身声明为唯一(至少)。在某些模型中,提供键值的表将主键设置为实际数据值,而不是 ID 列。

【讨论】:

  • 需要?不,您当然可以在没有约束的情况下创建、关联和使用它们。如果我上面的复合键(EDIT 3)对所有 3 个键都有一个唯一的约束,我会很高兴——这实际上是有意义的。它会生成相同的不需要的唯一约束,但不是那个实际上是有效的并且合乎逻辑的。感谢您的努力,但这并没有真正尝试回答这个问题。
  • 明白了。我的想法太早了 - 非常抱歉。然后根据您的问题 - sequeliize 看起来不像它应该表现的那样。 “连接表”端的 FK 绝对不应该在 DDL 中作为唯一约束生成。
【解决方案2】:

您的设计有问题,请参阅 James Barton 的回答 here。 我建议您使用以下表格而不是 User_Role_Organization:

  1. 角色_用户

  2. 组织_角色

    如果一个角色属于多个组织,那么所有角色用户也属于那些可能不属于预期的组织。

【讨论】:

    【解决方案3】:

    答案是强迫桌子按我想要的方式工作。它在 Sequelize 中运行良好。

    迁移使 Sequelize 中的更改和读/写工作完美无缺。所以,我想只是一个错误。大多数 ORM 似乎都有这样有趣的边缘情况。

        await queryInterface.createTable("organization_to_user_to_role", {
            organization_id: {
                type: Sequelize.DataTypes.UUID,
                primaryKey: true,
                constraints: false
            },
            role_id: {
                type: Sequelize.DataTypes.UUID,
                primaryKey: true,
                constraints: false
            },
            user_id: {
                type: Sequelize.DataTypes.UUID,
                primaryKey: true,
                constraints: false
            }
        });
    
        queryInterface.addConstraint("organization_to_user_to_role", {
            type: "FOREIGN KEY",
            name: "organization_to_user_to_role_organization_id_fkey",
            fields: ["organization_id"],
            references: {
                table: "auth_organization",
                field: "id"
            }
        });
        queryInterface.addConstraint("organization_to_user_to_role", {
            type: "FOREIGN KEY",
            name: "organization_to_user_to_role_user_id_fkey",
            fields: ["user_id"],
            references: {
                table: "auth_user",
                field: "id"
            }
        });
        queryInterface.addConstraint("organization_to_user_to_role", {
            type: "FOREIGN KEY",
            name: "organization_to_user_to_role_role_id_fkey",
            fields: ["role_id"],
            references: {
                table: "role",
                field: "id"
            }
        });
    

    如果您生成一个新数据库,则必须删除不需要的约束,如果它们存在,我将其放入迁移中:

            queryInterface.removeConstraint(
                "organization_to_user_to_role"
                "organization_to_user_to_role_organization_id_key"
            );
            queryInterface.removeConstraint(
                "organization_to_user_to_role",
                "organization_to_user_to_role_role_id_user_id_key"
            );
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多