簡體   English   中英

如何在 Sequelize 的多對多連接表中使用 3 個外鍵

[英]How to use 3 foreign keys in many-to-many join table in Sequelize

我要這個:

在此處輸入圖像描述

我可以在幾分鍾內使用 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

在此處輸入圖像描述

我工作的真實世界 model 看起來像這樣:

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 更多的歷史 w/ 關系數據。 我還為連接表嘗試了這個 model,它創建了一個更奇怪的 model,它在 user_id 和 organization_id 字段上強制使用復合主鍵,即使我設置了 primaryKey: false。

編輯 1

我懷疑這完全取決於我如何為 model 構建 FK,只是來自之前的 Sequelize 冒險。 我只是嘗試將 unique 設置為 false 並像這樣設置 FKs - 它現在抱怨“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:

找到了原因,無論我對 model 做什么。外鍵都添加了唯一約束:這是連接表的最新 model:

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)

如果我手動刪除它們,我可以播種預期的數據並查詢它 w/oa 問題,如下所示:

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:

我已經在 model 本身上嘗試了一些不同的東西,以及鍵之間的關系,但是我得到了完全相同的結果和完全相同的唯一約束。 如果我什至可以讓它在所有 3 個鍵上設置一個唯一約束(現在它們都是復合鍵的一部分),那就足夠了。

當前 model,我更喜歡:

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

似乎“約束”和“獨特”的效果為零。 與我之前的嘗試相比,這唯一的區別是復合鍵比無用的自動增量 PK 更有意義。

讓我看看我是否可以簡化問題……您有三個 Postgres 表:組織、角色和用戶。 每個都有唯一的標識符。

一個用戶在給定組織中只允許一個角色,但可以與多個組織相關聯,每個組織具有不同的角色(您的示例是 Acme 的管理員,但 Microsoft 的用戶)。

Sequelize 中的模型需要是什么樣子才能實現這一點?

這對嗎?

看起來你到了那里。

通常,在 model 中提供一組 FK 值的任何表都要求將值列本身聲明為唯一(至少)。 在某些模型中,提供鍵值的表將主鍵設置為實際數據值,而不是 ID 列。

您的設計有問題,請參閱此處的 James Barton 回答。 我建議您使用以下表格而不是 User_Role_Organization:

  1. 角色_用戶

  2. 組織_角色

如果一個角色屬於多個組織,那么所有角色用戶也屬於那些可能不是預期的組織。

答案是強迫桌子按我想要的方式工作。 它在 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"
        );

好的,我明白這個問題。 我將給出相同的示例:在 test.js 中

const module = require('./module')(sequelize, DataTypes)
    const permission = require('./permission')(sequelize, DataTypes)
    const role = require('./role')(sequelize, DataTypes)
    const test = sequelize.define('test', {
        moduleId: {
            type: DataTypes.INTEGER(4),
            ref: {
                model: module,
                key: 'id'
            }
        },
        roleId: {
            type: DataTypes.INTEGER(4),
            ref: {
                model: role,
                key: 'id'
            }
        },
        permissionId: {
            type: DataTypes.INTEGER(4),
            ref: {
                model: permission,
                key: 'id'
            }
        },
        createdDate: {
            type: DataTypes.DATE,
            defaultValue: DataTypes.NOW
        }
    }, {
        timestamps: false
    });

    test.belongsTo(module, { foreignKey: 'moduleId' });
    test.belongsTo(permission, { foreignKey: 'permissionId' });
    test.belongsTo(role, { foreignKey: 'roleId' });

    module.hasMany(test, { foreignKey: 'moduleId' });
    permission.hasMany(test, { foreignKey: 'permissionId' });
    role.hasMany(test, { foreignKey: 'roleId' });

    

    return test;

現在,我們只能添加一個唯一約束

const queryInterface = sequelize.getQueryInterface();


sequelize.sync({force: true}).then(()=>{
    return queryInterface.addConstraint('tests',  {
        fields:['moduleId', 'permissionId', 'roleId'],
        type: 'unique',
        name: 'custom_unique_constraint'
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM