在不假定外键列名称的情况下在 sequelize 中创建行时如何引用关联?
How do I reference an association when creating a row in sequelize without assuming the foreign key column name?
我有以下代码:
#!/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()
.
重新加载的
我看到 and 说我必须在 Thing.create()
调用中明确指定隐式创建的 UserId
列。当 Sequelize 提供像 Thing.belongsTo(User)
这样的 API 时,我 不应该 必须知道创建 Thing.UserId
字段的事实。那么 什么是干净的 API-尊重创建新 Thing
的方法,它引用特定的 User
而不必猜测 UserId
的名称field? 当我加载一个 Thing
并指定 {include: [User]}
时,我通过 thing.User
属性 访问加载的用户。我认为我不应该知道或尝试访问 thing.UserId
字段。在我的 Thing.belongsTo(User)
调用中,我从不指定 UserId
,我只是将其视为我不应该关心的实现细节。在创建 Thing
?
时,如何继续避免关心该实现细节
Thing.create()
调用有效但在我看来是错误的:
Thing.create({
name: 'A thing',
UserId: user.id
});
选项 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 - 不 work/buggy
它没有记录在案,但从代码潜水来看,我认为以下内容应该有效。不是,但这似乎是因为一些错误:
// ...
.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();
});
我已经上传了演示选项 2 和选项 3 的脚本 here
在 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});
在多对多的表中,你得到 foreignKey
和 otherKey
作为第二个外键。
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},
])
但是语法太长了,我很想在所有地方对键进行硬编码。
我有以下代码:
#!/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()
.
我看到 Thing.create()
调用中明确指定隐式创建的 UserId
列。当 Sequelize 提供像 Thing.belongsTo(User)
这样的 API 时,我 不应该 必须知道创建 Thing.UserId
字段的事实。那么 什么是干净的 API-尊重创建新 Thing
的方法,它引用特定的 User
而不必猜测 UserId
的名称field? 当我加载一个 Thing
并指定 {include: [User]}
时,我通过 thing.User
属性 访问加载的用户。我认为我不应该知道或尝试访问 thing.UserId
字段。在我的 Thing.belongsTo(User)
调用中,我从不指定 UserId
,我只是将其视为我不应该关心的实现细节。在创建 Thing
?
Thing.create()
调用有效但在我看来是错误的:
Thing.create({
name: 'A thing',
UserId: user.id
});
选项 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 - 不 work/buggy
它没有记录在案,但从代码潜水来看,我认为以下内容应该有效。不是,但这似乎是因为一些错误:
// ...
.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();
});
我已经上传了演示选项 2 和选项 3 的脚本 here
在 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});
在多对多的表中,你得到 foreignKey
和 otherKey
作为第二个外键。
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},
])
但是语法太长了,我很想在所有地方对键进行硬编码。