如何在不假设外键列名的情况下在sequelize中创建行时引用关联?

时间:2015-12-03 06:16:15

标签: node.js sequelize.js

我有以下代码:

#!/usr/bin/env node
'use strict';
var Sequelize = require('sequelize');
var sequelize = new Sequelize('sqlite:file.sqlite');

var User = sequelize.define('User', { email: Sequelize.STRING});
var Thing = sequelize.define('Thing', { name: Sequelize.STRING});
Thing.belongsTo(User);

sequelize.sync({force: true}).then(function () {
  return User.create({email: 'asdf@example.org'});
}).then(function (user) {
  return Thing.create({
    name: 'A thing',
    User: user
  }, {
    include: [User]
  });
}).then(function (thing) {
  return Thing.findOne({where: {id: thing.id}, include: [User]});
}).then(function (thing) {
  console.log(JSON.stringify(thing));
});

我得到以下输出:

ohnobinki@gibby ~/public_html/turbocase1 $ ./sqltest.js
Executing (default): INSERT INTO `Users` (`id`,`email`,`updatedAt`,`createdAt`) VALUES (NULL,'asdf@example.org','2015-12-03 06:11:36.904 +00:00','2015-12-03 06:11:36.904 +00:00');
Executing (default): INSERT INTO `Users` (`id`,`email`,`createdAt`,`updatedAt`) VALUES (1,'asdf@example.org','2015-12-03 06:11:36.904 +00:00','2015-12-03 06:11:37.022 +00:00');
Unhandled rejection SequelizeUniqueConstraintError: Validation error
    at Query.formatError (/home/ohnobinki/public_html/turbocase1/node_modules/sequelize/lib/dialects/sqlite/query.js:231:14)
    at Statement.<anonymous> (/home/ohnobinki/public_html/turbocase1/node_modules/sequelize/lib/dialects/sqlite/query.js:47:29)
    at Statement.replacement (/home/ohnobinki/public_html/turbocase1/node_modules/sqlite3/lib/trace.js:20:31)

似乎指定{include: [User]}指示Sequelize创建与User的内容匹配的新user实例。那不是我的目标。事实上,我发现很难相信这种行为会有用 - 我至少对它毫无用处。我希望能够在数据库中拥有一个长期存在的User记录,并在任意时间创建引用Thing的新User。在我显示的示例中,我等待创建User,但在实际代码中,它可能是通过User.findOne()新加载的。

我看到other questionsanswers表示我必须在UserId调用中明确指定隐式创建的Thing.create()列。当Sequelize提供类似Thing.belongsTo(User)的API时,我不应该必须知道创建Thing.UserId字段的事实。那么创建新Thing的干净API尊重方式是什么,引用特定User而不必猜测UserId字段的名称?当我加载Thing并指定{include: [User]}时,我通过thing.User属性访问加载的用户。我认为我不应该知道或尝试访问thing.UserId字段。在Thing.belongsTo(User)调用中,我从未指定UserId,我只是将其视为一个我不应该关心的实现细节。在创建Thing

时,如何继续避免关注该实施细节

有效的Thing.create()电话,但对我来说不合适:

Thing.create({
  name: 'A thing',
  UserId: user.id
});

2 个答案:

答案 0 :(得分:1)

选项1 - 存在数据库不一致的风险

Sequelize动态生成用于设置实例关联的方法,例如thing.setUser(user);。在您的用例中:

sequelize.sync({force: true})
.then(function () {
  return Promise.all([
    User.create({email: 'asdf@example.org'}),
    Thing.create({name: 'A thing'})  
  ]);
})
.spread(function(user, thing) {
  return thing.setUser(user);
})
.then(function(thing) {
  console.log(JSON.stringify(thing));
});

选项2 - 无效/错误

它没有记录,但从代码潜水中我认为以下内容应该有效。它没有,但似乎是因为一些错误:

// ...
.then(function () {
  return models.User.create({email: 'asdf@example.org'});
})
.then(function(user) {
  // Fails with SequelizeUniqueConstraintError - the User instance inherits isNewRecord from the Thing instance, but it has already been saved
  return models.Thing.create({
    name: 'thingthing',
    User: user
  }, {
    include: [{
      model: models.User
    }],
    fields: ['name'] // seems nec to specify all non-included fields because of line 277 in instance.js - another bug?
  });
})

models.User.create替换models.User.build并不起作用,因为已构建但未保存的实例的主键为空。 Instance#_setInclude如果主键为空,则忽略该实例。

选项3

在事务中包装事物create可防止状态不一致。

sq.sync({ force: true })
.then(models.User.create.bind(models.User, { email: 'asdf@example.org' }))
.then(function(user) {
  return sq.transaction(function(tr) {
    return models.Thing.create({name: 'A thing'})
    .then(function(thing) { return thing.setUser(user); });
  });
})
.then(print_result.bind(null, 'Thing with User...'))
.catch(swallow_rejected_promise.bind(null, 'main promise chain'))
.finally(function() {
  return sq.close();
});

我上传了一个脚本演示&#39选项2和选项3 here

答案 1 :(得分:0)

sequelize@6.5.1 sqlite3@5.0.2 上测试我可以使用 User.associations.Comments.foreignKey

const Comment = sequelize.define('Comment', {
  body: { type: DataTypes.STRING },
});
const User = sequelize.define('User', {
  name: { type: DataTypes.STRING },
});
User.hasMany(Comment)
Comment.belongsTo(User)
console.dir(User);
await sequelize.sync({force: true});
const u0 = await User.create({name: 'u0'})
const u1 = await User.create({name: 'u1'})
await Comment.create({body: 'u0c0', [User.associations.Comments.foreignKey]: u0.id});

在创建过程中也会返回关联,因此您还可以:

const Comments = User.hasMany(Comment)
await Comment.create({body: 'u0c0', [Comments.foreignKey]: u0.id});

在多对多的表中,第二个外键得到 foreignKeyotherKey

User.associations.Comments.foreignKey 包含 foreignKey UserId

或者类似地使用别名:

User.hasMany(Post, {as: 'authoredPosts', foreignKey: 'authorId'});
Post.belongsTo(User, {as: 'author', foreignKey: 'authorId'});

User.hasMany(Post, {as: 'reviewedPosts', foreignKey: 'reviewerId'});
Post.belongsTo(User, {as: 'reviewer', foreignKey: 'reviewerId'});
await sequelize.sync({force: true});

// Create data.
const users = await User.bulkCreate([
  {name: 'user0'},
  {name: 'user1'},
])

const posts = await Post.bulkCreate([
  {body: 'body00', authorId: users[0].id, reviewerId: users[0].id},
  {body: 'body01', [User.associations.authoredPosts.foreignKey]: users[0].id, 
                   [User.associations.reviewedPosts.foreignKey]: users[1].id},
])

但是那个语法太长了,我很想在任何地方对键进行硬编码。