如何在续集中对连接表的其他列进行 ORM



我正在使用节点v9.5,续集v4.33(postgres方言)。

我有两个一流的模型:Driver(特定的人)和Car(通用的make+模型组合)。到目前为止,它们已通过多对多联接表进行连接。现在,我想开始跟踪该联接表上的其他属性,但是在声明这些关系以便它们实际工作时遇到问题。

const Driver = sqlz.define('Driver', {
id: { primaryKey: true, type: DataTypes.UUID },
name: DataTypes.string
})
const Car = sqlz.define('Car', {
id: { primaryKey: true, type: DataTypes.UUID },
make: DataTypes.string,
model: DataTypes.string
})
// old associations; worked great when requirements were simpler
Driver.belongsToMany(Car, {
through: 'DriverCar',
as: 'carList',
foreignKey: 'driverId'
})
Car.belongsToMany(Driver, {
through: 'DriverCar',
as: 'driverList',
foreignKey: 'carId'
})

现在,我想开始跟踪有关汽车与其驾驶员之间关系的更多信息,例如特定汽车的颜色。

第 1 步:我更新迁移脚本,在连接表中添加一个新列,如下所示:

queryInterface.createTable( 'DriverCar', {
driverId: {
type: sqlz.UUID,
allowNull: false,
primaryKey: true,
references: {
model: 'Driver',
key: 'id'
}
},
carId: {
type: sqlz.UUID,
allowNull: false,
primaryKey: true,
references: {
model: 'Car',
key: 'id'
}
},
createdAt: {
type: sqlz.DATE,
allowNull: false
},
updatedAt: {
type: sqlz.DATE,
allowNull: false
},
// new column for join table
color: {
type: Sequelize.STRING
}
})

第 2 步:我为DriverCar定义一个新的 sqlz 模型:

const DriverCar = sqlz.define('DriverCar', {
color: DataTypes.string
})

(我假设我只需要定义有趣的属性,并且driverIdcarId仍将从将要定义的关联中推断出来。

第 3 步:我需要更新DriverCarDriverCar之间存在的关联。

这就是我卡住的地方。我尝试更新现有关联,如下所示:

Driver.belongsToMany(Car, {
through: DriverCar, // NOTE: no longer a string, but a reference to new DriverCar model
as: 'carList',
foreignKey: 'driverId'
})
Car.belongsToMany(Driver, {
through: DriverCar, // NOTE: no longer a string, but a reference to new DriverCar model
as: 'driverList',
foreignKey: 'carId'
})

这执行没有错误,但是当我尝试driver.getCarList()时,不会从连接表中获取新的color属性。(Sqlz 配置为记录每个 SQL 语句,并且我已经验证了没有请求连接表中的属性。

因此,相反,我尝试更明确地阐明这种关系,方法是将Driver关联到DriverCar,然后CarDriverCar

// Driver -> Car
Driver.hasMany(DriverCar, {
as: 'carList',
foreignKey: 'driverId'
})
// Car -> Driver
Car.hasMany(DriverCar, {
foreignKey: 'carId'
})

我还告诉 sqlz,DriverCar不会有标准的行 ID:

DriverCar.removeAttribute('id')

此时,请求驱动程序的 carList (driver.getCarList()) 似乎有效,因为我可以看到在 SQL 中获取的连接表道具。但是保存失败:

driverModel.setCarList([ carModel1 ])
UPDATE DriverCar
SET "driverId"='a-uuid',"updatedAt"='2018-02-23 22:01:02.126 +00:00'
WHERE "undefined" in (NULL)

错误:

SequelizeDatabaseError: column "undefined" does not exist

我认为发生此错误是因为 sqzl 不了解在连接表中标识行的正确方法,因为我未能建立必要的关联。坦率地说,我不相信我做对了;我是ORM的新手,但我预计我需要指定4个协会:

  1. Driver->DriverCar
  2. DriverCar->Car
  3. Car->DriverCar
  4. DriverCar->Driver

回顾一下:我有 2 个一等实体,以多对多关系加入。我正在尝试向关系添加数据,发现 ORM 需要以不同的方式定义这些关联,并且在阐明新关联时遇到问题。

关于别名的说明

在回答之前,我想指出你选择的别名(carListdriverList)可能会更好,因为虽然自动生成的续集方法.setCarList().setDriverList()确实有意义,但方法.addCarList().addDriverList().removeCarList().removeDriverList()都是无稽之谈,因为它们只接受一个实例作为参数, 不是列表。

对于我的答案,我不会使用任何别名,而是让 Sequelize 默认为.setCars().setDrivers().addCar().removeCar()等,这对我来说更有意义。


工作代码示例

我已经制作了一个 100% 独立的代码来测试这一点。只需复制粘贴并运行它(运行npm install sequelize sqlite3后):

const Sequelize = require("sequelize");
const sequelize = new Sequelize({ dialect: 'sqlite', storage: 'db.sqlite' });
const Driver = sequelize.define("Driver", {
name: Sequelize.STRING
});
const Car = sequelize.define("Car", {
make: Sequelize.STRING,
model: Sequelize.STRING
});
const DriverCar = sequelize.define("DriverCar", {
color: Sequelize.STRING
});
Driver.belongsToMany(Car, { through: DriverCar, foreignKey: "driverId" });
Car.belongsToMany(Driver, { through: DriverCar, foreignKey: "carId" });
var car, driver;
sequelize.sync({ force: true })
.then(() => {
// Create a driver
return Driver.create({ name: "name test" });
})
.then(created => {
// Store the driver created above in the 'driver' variable
driver = created;
// Create a car
return Car.create({ make: "make test", model: "model test" });
})
.then(created => {
// Store the car created above in the 'car' variable
car = created;
// Now we want to define that car is related to driver.
// Option 1:
return car.addDriver(driver, { through: { color: "black" }});
// Option 2:
// return driver.setCars([car], { through: { color: "black" }});
// Option 3:
// return DriverCar.create({
//     driverId: driver.id,
//     carId: car.id,
//     color: "black"
// });
})
.then(() => {
// Now we get the things back from the DB.
// This works:
return Driver.findAll({ include: [Car] });
// This also works:
// return car.getDrivers();
// This also works:
// return driver.getCars();
})
.then(result => {
// Log the query result in a readable way
console.log(JSON.stringify(result.map(x => x.toJSON()), null, 4));
});

上面的代码按预期记录(至少如我所期望):

[
{
"id": 1,
"name": "name test",
"createdAt": "2018-03-11T03:04:28.657Z",
"updatedAt": "2018-03-11T03:04:28.657Z",
"Cars": [
{
"id": 1,
"make": "make test",
"model": "model test",
"createdAt": "2018-03-11T03:04:28.802Z",
"updatedAt": "2018-03-11T03:04:28.802Z",
"DriverCar": {
"color": "black",
"createdAt": "2018-03-11T03:04:28.961Z",
"updatedAt": "2018-03-11T03:04:28.961Z",
"driverId": 1,
"carId": 1
}
}
]
}
]

请注意,没有秘密。请注意,您要查找的额外属性color嵌套在查询结果中,而不是位于CarDriver的同一嵌套级别中。这是 Sequelize 的正确行为。

确保您可以运行此代码并获得与我相同的结果。我的 Node 版本不同,但我怀疑这可能与任何事情有关。然后,将我的代码与您的代码进行比较,看看您是否可以找出导致问题的原因。如果您需要进一步的帮助,请随时在评论中提问:)


关于具有额外字段的多对多关系的说明

由于我偶然发现了这个问题,这与你的情况有关,我想我应该在我的答案中添加一个部分,提醒你建立一个过于复杂的多对多关系的"陷阱"(这是我在挣扎一段时间后学到的教训)。

我不想重复自己,而只是简单地引用我在续集问题9158中所说的话,并添加链接以供进一步阅读:

联结表是关系数据库中存在的表示多对多关系的表

,最初只有两个字段(定义多对多关系的每个表的外键)。虽然确实可以在该表上定义额外的字段/属性,即关联本身的额外属性(如您在问题标题中输入的那样),但这里应该小心:如果它变得过于复杂,这表明您应该将连接表"提升"为一个成熟的实体。

延伸阅读:

  • 我自己的自我回答问题涉及续集中过于复杂的多对多关系设置:FindAll 包括涉及复杂的多对多(多对多)关系(续集)
  • 以及它的同级问题:是否可以建立多对多关系,其中涉及的表之一已经是联结表?

最新更新