【问题标题】:How do I ORM additional columns on a join table in sequelize?如何 ORM 续集连接表上的附加列?
【发布时间】:2018-08-04 01:28:02
【问题描述】:

我正在使用 node v9.5,sequelize v4.33(postgres 方言)。

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

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

第二步:我为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,然后将Car 关联到DriverCar

// 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 需要以不同的方式定义这些关联,并且在阐明新关联时遇到了麻烦。

【问题讨论】:

  • 这是 sequelize 不太出彩的地方之一。您仍然应该能够找到 Driver 并包含 Cars,它应该会带来所有字段。在查询中执行(使用包含)而不是使用魔术方法
  • @yBrodsky:感谢您的提示。我相信我知道您所说的“而不是使用魔术方法”是什么意思,但我必须做更多的研究才能兑现您的建议。
  • 我应该补充一下,关于 sequelize 的这个问题表明 include 方法不起作用:github.com/sequelize/sequelize/issues/9094
  • 这里仍然没有运气。如果有人发现这个,我仍然会很感激帮助。
  • pastebin.com/qT9D4KXE 这不行吗?

标签: javascript sequelize.js


【解决方案1】:

关于别名的说明

在回答之前,我想指出您选择的别名(carListdriverList)可能会更好,因为尽管自动生成的 sequelize 方法 .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 版本不同,但我怀疑这可能与任何事情有关。然后,将我的代码与您的代码进行比较,看看您是否可以找出导致问题的原因。如果您需要进一步的帮助,请随时在评论中提问:)


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

由于我偶然发现了这方面的问题,并且这与您的情况有关,我想我应该在答案中添加一个部分,提醒您注意建立过于复杂的多对多关系的“陷阱”(它是经过一段时间的努力,我学到了自己的教训)。

我将不再重复我自己,我将简单地引用我在Sequelize Issue 9158 中所说的话,并添加链接以供进一步阅读:

联结表是关系数据库中存在的表示多对多关系的表,最初只有两个字段(每个表的外键定义多对多关系)。虽然确实可以在该表上定义额外的字段/属性,即关联本身的额外属性(如您在问题标题中输入的那样),但此处应注意:如果它变得过于复杂,则表明您应该将您的连接表“提升”为成熟的实体。

进一步阅读:

【讨论】:

  • 这看起来很可靠。我需要时间来研究它。谢谢。
  • @Tom - 没问题,慢慢来。如果您需要进一步的说明/帮助,请随时发表评论(:
  • @Tom - 抱歉这么久才回复。对于您所说的复数,请参阅我的问答herehere。但我建议你部分解决你的问题。首先,忘记别名,看看你是否可以让你的代码在没有任何别名的情况下工作。然后,在你完成之后,再次输入你的别名,看看会发生什么。如果您需要进一步的帮助,请告诉我。如果你成功了,也请告诉我,如果我有帮助,请点击绿色复选标记 :)
  • @bluehipy 你好,我明天去看看 :)
  • 不要在包含数据的生产数据库上使用sequelize.sync({ force: true }),而是使用迁移文件
猜你喜欢
  • 2014-01-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多