由于带有 sequelize.js 的 UUID 外键,尝试将行插入表时出错

Posted

技术标签:

【中文标题】由于带有 sequelize.js 的 UUID 外键,尝试将行插入表时出错【英文标题】:Error when trying to insert row to table because of UUID foreign key with sequelize.js 【发布时间】:2019-04-14 10:37:33 【问题描述】:

我将 sequelize.js 与 node.js 和 postgres 一起使用。 我从一个示例中得到了 2 个简单的表作为各种“POC”。 我将 ID 更改为 UUID,但在插入第二个表时遇到问题(使用 UUID FK)。

我正在使用邮递员对其进行测试。 我正在使用 UUID 创建待办事项行,没有任何问题, 然后我试图创建一个 todo 项目,它有一个 todo id 作为外键 它似乎无法识别该 ID!

我在 postgres 中尝试了一个手动脚本,它成功了。 我可能在代码方面遗漏了一些东西,但我不知道是什么。

这是邮递员返回给我的错误 -


    "name": "SequelizeDatabaseError",
    "parent": 
        "name": "error",
        "length": 96,
        "severity": "ERROR",
        "code": "22P02",
        "file": "uuid.c",
        "line": "137",
        "routine": "string_to_uuid",
        "sql": "INSERT INTO \"TodoItems\" (\"id\",\"content\",\"complete\",\"createdAt\",\"updatedAt\",\"todoId\") VALUES ($1,$2,$3,$4,$5,$6) RETURNING *;"
    ,
    "original": 
        "name": "error",
        "length": 96,
        "severity": "ERROR",
        "code": "22P02",
        "file": "uuid.c",
        "line": "137",
        "routine": "string_to_uuid",
        "sql": "INSERT INTO \"TodoItems\" (\"id\",\"content\",\"complete\",\"createdAt\",\"updatedAt\",\"todoId\") VALUES ($1,$2,$3,$4,$5,$6) RETURNING *;"
    ,
    "sql": "INSERT INTO \"TodoItems\" (\"id\",\"content\",\"complete\",\"createdAt\",\"updatedAt\",\"todoId\") VALUES ($1,$2,$3,$4,$5,$6) RETURNING *;"

这里是相关的js文件-

tod​​oItems.js 控制器 -

const TodoItem = require('../dal/models').TodoItem;
const uuid = require('uuid/v4');

module.exports = 
  create(req, res) 
    return TodoItem
      .create(
        content: req.body.content,
        todoId: req.params.todoId,
      )
      .then(todoItem => res.status(201).send(todoItem))
      .catch(error => res.status(400).send(error));
  ,

  update(req, res) 
    return TodoItem
      .find(
        where: 
          id: req.params.todoItemId,
          todoId: req.params.todoId,
        ,
      )
      .then(todoItem => 
        if (!todoItem) 
          return res.status(404).send(
            message: 'TodoItem Not Found',
          );
        

        return todoItem
          .update(
            content: req.body.content || todoItem.content,
            complete: req.body.complete || todoItem.complete,
          )
          .then(updatedTodoItem => res.status(200).send(updatedTodoItem))
          .catch(error => res.status(400).send(error));
      )
      .catch(error => res.status(400).send(error));
  ,

  destroy(req, res) 
    return TodoItem
      .find(
        where: 
          id: req.params.todoItemId,
          todoId: req.params.todoId,
        ,
      )
      .then(todoItem => 
        if (!todoItem) 
          return res.status(404).send(
            message: 'TodoItem Not Found',
          );
        

        return todoItem
          .destroy()
          .then(() => res.status(204).send())
          .catch(error => res.status(400).send(error));
      )
      .catch(error => res.status(400).send(error));
  ,
;

tod​​os.js 控制器-

const Todo = require('../dal/models').Todo;
const TodoItem = require('../dal/models').TodoItem;

module.exports = 
  create(req, res) 
    return Todo
      .create(
        title: req.body.title,
      )
      .then((todo) => res.status(201).send(todo))
      .catch((error) => res.status(400).send(error));
  ,

  list(req, res) 
    return Todo
      .findAll(
        include: [
          model: TodoItem,
          as: 'todoItems',
        ],
        order: [
          ['createdAt', 'DESC'],
          [ model: TodoItem, as: 'todoItems' , 'createdAt', 'ASC'],
        ],
      )
      .then((todos) => res.status(200).send(todos))
      .catch((error) => res.status(400).send(error));
  ,

  retrieve(req, res) 
    return Todo
      .findByPk(req.params.todoId, 
        include: [
          model: TodoItem,
          as: 'todoItems',
        ],
      )
      .then((todo) => 
        if (!todo) 
          return res.status(404).send(
            message: 'Todo Not Found',
          );
        
        return res.status(200).send(todo);
      )
      .catch((error) => res.status(400).send(error));
  ,

  update(req, res) 
    return Todo
      .findByPk(req.params.todoId, 
        include: [
          model: TodoItem,
          as: 'todoItems',
        ],
      )
      .then(todo => 
        if (!todo) 
          return res.status(404).send(
            message: 'Todo Not Found',
          );
        
        return todo
          .update(
            title: req.body.title || todo.title,
          )
          .then(() => res.status(200).send(todo))
          .catch((error) => res.status(400).send(error));
      )
      .catch((error) => res.status(400).send(error));
  ,

  destroy(req, res) 
    return Todo
      .findByPk(req.params.todoId)
      .then(todo => 
        if (!todo) 
          return res.status(400).send(
            message: 'Todo Not Found',
          );
        
        return todo
          .destroy()
          .then(() => res.status(204).send())
          .catch((error) => res.status(400).send(error));
      )
      .catch((error) => res.status(400).send(error));
  ,
;

tod​​o 表创建迁移 -

module.exports = 
  up: (queryInterface, Sequelize) =>
    queryInterface.createTable('Todos', 
      id: 
        allowNull: false,
        primaryKey: true,
        type: Sequelize.UUID,
      ,
      title: 
        type: Sequelize.STRING,
        allowNull: false,
      ,
      createdAt: 
        allowNull: false,
        type: Sequelize.DATE,
      ,
      updatedAt: 
        allowNull: false,
        type: Sequelize.DATE,
      ,
    ),
  down: (queryInterface /* , Sequelize */) => queryInterface.dropTable('Todos'),
;

tod​​o-item 表创建迁移 -

module.exports = 
  up: (queryInterface, Sequelize) =>
    queryInterface.createTable('TodoItems', 
      id: 
        allowNull: false,
        primaryKey: true,
        type: Sequelize.UUID,
      ,
      content: 
        type: Sequelize.STRING,
        allowNull: false,
      ,
      complete: 
        type: Sequelize.BOOLEAN,
        defaultValue: false,
      ,
      createdAt: 
        allowNull: false,
        type: Sequelize.DATE,
      ,
      updatedAt: 
        allowNull: false,
        type: Sequelize.DATE,
      ,
      todoId: 
        type: Sequelize.UUID,
        onDelete: 'CASCADE',
        references: 
          model: 'Todos',
          key: 'id',
          as: 'todoId',
        ,
      ,
    ),
  down: (queryInterface /* , Sequelize */) =>
    queryInterface.dropTable('TodoItems'),
;

待办事项模型 -

const uuid = require('uuid/v4');

'use strict';

module.exports = (sequelize, DataTypes) => 
  const Todo = sequelize.define('Todo', 
    title: 
      type: DataTypes.STRING,
      allowNull: false,
    
  );
  Todo.associate = (models) => 
    Todo.hasMany(models.TodoItem, 
      foreignKey: 'todoId',
      as: 'todoItems',
    );
  ;
  Todo.beforeCreate((item, _ ) => 
    return item.id = uuid();
  );
  return Todo;
;

待办事项模型 -

const uuid = require('uuid/v4');

'use strict';

module.exports = (sequelize, DataTypes) => 
  const TodoItem = sequelize.define('TodoItem', 
    content: 
      type: DataTypes.STRING,
      allowNull: false,
    ,
    complete: 
      type: DataTypes.BOOLEAN,
      defaultValue: false,
    
  );
  TodoItem.associate = (models) => 
    TodoItem.belongsTo(models.Todo, 
      foreignKey: 'todoId',
      onDelete: 'CASCADE',
    );
  ;
  TodoItem.beforeCreate((item, _ ) => 
    return item.id = uuid();
  );
  return TodoItem;
;

【问题讨论】:

【参考方案1】:

您的路由器代码是什么样的?您是否为 todoId 使用正确的路径参数?例如,如果您使用的是快递。它应该看起来像 app.post("/todos/:todoId/todo_items", todoItemController.create) 。注意驼峰 todoId 。这将确保您在 todoItems 控制器中引用的 req.params.todoId 具有正确的值。

另外,请确保您有正确的正文解析器来正确处理 req.body.content。在 express 中,这将通过 body body-parser library 和 app.use(bodyParser.json()) 来完成。在 todoItem 控制器创建代码中添加断点或日志语句并验证您是否确实具有正确的参数值。

【讨论】:

遗憾的是,我删除了待办事项代码并将其替换为我的真实模型,因此我无法确定,但现在一切正常。我很确定这个问题确实与路由器路径中的错误命名有关。【参考方案2】:

如果您碰巧遇到上述错误,可能是因为您在请求正文中嵌套了其他实体,因此 UUID 没有从字符串转换为 UUID。

例如,如果您有类似的请求正文


   "Transaction": 
      "id" : "f2ec9ecf-31e5-458d-847e-5fcca0a90c3e",
      "currency" : "USD",
      "type_id" : "bfa944ea-4ce1-4dad-a74e-aaa449212ebf", 
      "total": 8000.00,
      "fees": 43.23,
      "description":"Description here"
    ,

因此在您的控制器中,您正在创建您的实体,例如

try 
  await Transaction.create(
    
      id: req.body.Transaction.id,
      currency: req.body.Transaction.currency,
      type_id: req.body.Transaction.type_id,
      total: req.body.Transaction.total,
      fees: req.body.Transaction.fees,
      description: req.body.Transaction.description,
    ......

您的 idtype_id 很可能没有从字符串转换为 UUID。

有多种方法可以解决这个问题。最直接的方法是进行从字符串到 UUID 的显式转换。

为此,请从uuid npm module 导入parse 并进行显式转换,如下面的代码示例所示。

const  parse: uuidParse  = require("uuid");
try 
  await Transaction.create(
    
      id: uuidParse(req.body.Transaction.id),
      currency: req.body.Transaction.currency,
      type_id: uuidParse(req.body.Transaction.type_id),
      total: req.body.Transaction.total,
      fees: req.body.Transaction.fees,
      description: req.body.Transaction.description,
    .....

这种从字符串到 UUID 的显式转换将主要解决问题。

【讨论】:

以上是关于由于带有 sequelize.js 的 UUID 外键,尝试将行插入表时出错的主要内容,如果未能解决你的问题,请参考以下文章

sequelize.js 中的字母数字 ID

Sequelize.js 与非别名关联一起加入别名关联

使用 Sequelize.js 和 PostgreSQL 在关联模型上查询 JSONB 字段

Linux蓝牙找不到带有UUID的Android服务

Sequelize.js 外键

获取由 sequelize.js 生成的原始查询 [重复]