跳至主要內容
版本:v6 - 穩定版

約束條件與循環參照

當使用 sequelize.sync 時,在資料表之間加入約束條件意味著資料表必須依照特定順序在資料庫中建立。如果 Task 參照了 User,則必須先建立 User 資料表,才能建立 Task 資料表。這有時會導致循環參照,Sequelize 無法找到同步的順序。想像一個文件和版本的場景。一個文件可以有多個版本,為了方便起見,文件會參照其目前版本。

const { Sequelize, Model, DataTypes } = require('sequelize');

class Document extends Model {}
Document.init(
{
author: DataTypes.STRING,
},
{ sequelize, modelName: 'document' },
);

class Version extends Model {}
Version.init(
{
timestamp: DataTypes.DATE,
},
{ sequelize, modelName: 'version' },
);

Document.hasMany(Version); // This adds documentId attribute to version
Document.belongsTo(Version, {
as: 'Current',
foreignKey: 'currentVersionId',
}); // This adds currentVersionId attribute to document

然而,不幸的是,上述程式碼會導致以下錯誤

Cyclic dependency found. documents is dependent of itself. Dependency chain: documents -> versions => documents

為了緩解這種情況,我們可以將 constraints: false 傳遞給其中一個關聯

Document.hasMany(Version);
Document.belongsTo(Version, {
as: 'Current',
foreignKey: 'currentVersionId',
constraints: false,
});

這將允許我們正確同步資料表

CREATE TABLE IF NOT EXISTS "documents" (
"id" SERIAL,
"author" VARCHAR(255),
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
"currentVersionId" INTEGER,
PRIMARY KEY ("id")
);

CREATE TABLE IF NOT EXISTS "versions" (
"id" SERIAL,
"timestamp" TIMESTAMP WITH TIME ZONE,
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
"documentId" INTEGER REFERENCES "documents" ("id") ON DELETE
SET
NULL ON UPDATE CASCADE,
PRIMARY KEY ("id")
);

在沒有約束的情況下強制執行外部索引鍵參照

有時您可能想要參照另一個資料表,而無需新增任何約束或關聯。在這種情況下,您可以手動將參照屬性新增至您的結構定義,並標記它們之間的關係。

class Trainer extends Model {}
Trainer.init(
{
firstName: Sequelize.STRING,
lastName: Sequelize.STRING,
},
{ sequelize, modelName: 'trainer' },
);

// Series will have a trainerId = Trainer.id foreign reference key
// after we call Trainer.hasMany(series)
class Series extends Model {}
Series.init(
{
title: Sequelize.STRING,
subTitle: Sequelize.STRING,
description: Sequelize.TEXT,
// Set FK relationship (hasMany) with `Trainer`
trainerId: {
type: DataTypes.INTEGER,
references: {
model: Trainer,
key: 'id',
},
},
},
{ sequelize, modelName: 'series' },
);

// Video will have seriesId = Series.id foreign reference key
// after we call Series.hasOne(Video)
class Video extends Model {}
Video.init(
{
title: Sequelize.STRING,
sequence: Sequelize.INTEGER,
description: Sequelize.TEXT,
// set relationship (hasOne) with `Series`
seriesId: {
type: DataTypes.INTEGER,
references: {
model: Series, // Can be both a string representing the table name or a Sequelize model
key: 'id',
},
},
},
{ sequelize, modelName: 'video' },
);

Series.hasOne(Video);
Trainer.hasMany(Series);