我们正在将我们的api从C#移植到Loopback ^v3.19.0
,并遇到了一个阻止程序。
我们的许多模型具有共享的属性,因此我们创建了它们继承自的基本模型“ Base”。
{
"name": "Base",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"mixins": {
"Timestamp": {}
},
"properties": {
"created-by": {
"type": "number",
"postgresql": {
"columnName": "created_by"
}
},
"created-date": {
"type": "date",
"postgresql": {
"columnName": "created_on_utc"
}
},
"updated-by": {
"type": "number",
"postgresql": {
"columnName": "updated_by"
}
},
"updated-date": {
"type": "date",
"postgresql": {
"columnName": "updated_on_utc"
}
},
"soft-deleted": {
"type": "boolean",
"postgresql": {
"columnName": "is_deleted"
}
},
"deleted-by": {
"type": "number",
"postgresql": {
"columnName": "deleted_by"
}
},
"deleted-date": {
"type": "date",
"postgresql": {
"columnName": "deleted_on_utc"
}
},
"tenant-id": {
"type": "number",
"postgresql": {
"columnName": "tenant_id"
}
}
},
...
}
在 Timestamp 混合(我们自己的)中,这些属性会相应地设置
module.exports = function(Model, options) {
Model.observe('before save', function event(ctx, next) {
const token = ctx.options && ctx.options.accessToken;
const userId = token && token.userId;
const now = new Date().toISOString();
if (ctx.instance) {
ctx.instance['created-by'] = userId;
ctx.instance['created-date'] = now;
ctx.instance['updated-by'] = userId;
ctx.instance['updated-date'] = now;
} else {
if (ctx.data['soft-deleted'] &&
ctx.data['soft-deleted'] === true) {
ctx.data['deleted-by'] = userId;
ctx.data['deleted-date'] = now;
ctx.data['is-active'] = false;
}
ctx.data['updated-by'] = userId;
ctx.data['updated-date'] = now;
}
next();
});
};
这在创建新模型时效果很好。 对于更新(PATCH /modelname/:id)
非常有用,但是意外中断了,我们不知道为什么。 (这在从该Base
模型继承的所有模型中都是一致的。)
mixin正确地看到了模型并添加了更新的属性,像这样
LoopbackJS | ************* 'before save' ctx.data **************
LoopbackJS | { 'is-active': false,
LoopbackJS | 'updated-by': 1,
LoopbackJS | 'updated-date': '2018-08-16T17:57:23.660Z' }
LoopbackJS | ************* END 'before save' ctx.data **************
但是当环回执行更新SQL时,它是否以某种方式忽略/删除了updated-by
的值? (第二个参数应该是1
,而不是null
)
LoopbackJS | 2018-08-16T17:57:23.666Z loopback:connector:postgresql SQL: UPDATE "public"."asset_types" SET "is_active"=$1,"updated_by"=$2,"updated_on_utc"=$3::TIMESTAMP WITH TIME ZONE,"tenant_id"=$4 WHERE "id"=$5
LoopbackJS | Parameters: [false,null,"2018-08-16T17:57:23.660Z",1,5]
updated_by
在Postgres中可以为空,因此不应该产生错误...但是Loopback正在发送字符串化的函数?
LoopbackJS | 2018-08-16T18:04:12.522Z loopback:connector:postgresql error: invalid input syntax for integer: "function () { [native code] }"
LoopbackJS | at Connection.parseE (/home/src/back-end/node_modules/pg/lib/connection.js:553:11)
LoopbackJS | at Connection.parseMessage (/home/src/back-end/node_modules/pg/lib/connection.js:378:19)
LoopbackJS | at TLSSocket.<anonymous> (/home/src/back-end/node_modules/pg/lib/connection.js:119:22)
LoopbackJS | at emitOne (events.js:115:13)
LoopbackJS | at TLSSocket.emit (events.js:210:7)
LoopbackJS | at addChunk (_stream_readable.js:264:12)
LoopbackJS | at readableAddChunk (_stream_readable.js:251:11)
LoopbackJS | at TLSSocket.Readable.push (_stream_readable.js:209:10)
LoopbackJS | at TLSWrap.onread (net.js:587:20)
如果我们不触摸updated_by
列,则说明SQL是正确的并进行了更新。
顺便说一句,如果我们进行软删除并且deleted_by
列在起作用,那么在那里也会发生同样的事情。
感觉就像我在这里盘旋,可能忽略了一些基本知识。有什么建议吗?
编辑
因此,似乎它不仅限于mixin ...当我们将其完全删除并在有效负载中手动设置k:v对(即'created-by': 1
)时,我们仍然从Postgres返回相同的错误。
答案 0 :(得分:0)
其根本原因是由于不正确的关系。
我创建了此as a gist,但也将其粘贴到此处以防其他人使用。
使用小写名称是一种PostgreSQL
的最佳实践,如果需要,可以使用蛇形。即 my_column_name 。
此外,由于我使用的是JSON API客户端,因此我安装了 excellent (优秀) loopback-component-jsonapi来处理反序列化的事情……但这只是添加其他复杂性。
JSON API要求使用反斜线的属性名称。当您以my-property-name
之类的名称开始时,默认情况下,Loopback或PostgreSQL驱动程序(没关系)会将破折号属性折叠为mypropertyname
。
这很糟糕……尤其是当您拥有正在使用的现有架构时。
处理关系时,情况会更糟,因为默认情况下,回送还会附加id
后缀,因此,除非碰巧有一个mypropertynameid
,否则您现在就遇到了问题。列。
假设我们有一个Customer
模型。我需要使用小写的端点(在适用的情况下也应使用反斜线表示),因此只需在此处更改复数即可。
{
"name": "Customer",
"plural": "customers",
"base": "PersistedModel",
...
}
在options.postgresql
内,您可以设置tableName
。默认情况下,环回将使用name
值,但请记住PostgreSQL不喜欢CamelCase。除非使用小写型号名称,否则您需要覆盖它。
(这是一种宗教偏爱,但是我希望我的桌子可以是复数形式。与我战斗。)
{
...
"options": {
"validateUpsert": true,
"postgresql": {
"tableName": "customers"
}
}
...
}
返回属性,使用postgresql.columnName
属性映射到数据库中正确的列名。如果该属性名称不是经过反划线的属性(即 status
),则可以忽略postgresql.columnName
位。
{
...
"properties": {
"is-active": {
"type": "boolean",
"default": false,
"postgresql": {
"columnName": "is_active"
}
}
}
}
关系可能令人头疼。
比方说我们的Customer
有在那工作的人。在模型之间建立基本的一对多关系...
{
...
"relations": {
"people": {
"type": "hasMany",
"model": "Person",
"foreignKey": "customer_id"
}
},
...
}
people
是JSON API有效负载的关系元素的名称。
对我来说,这里的“陷阱”是foreignKey
属性。
Loopback文档说它是可选的-是的-但是,如果省略它,则会将id
后缀添加到名称(people
)中,然后在{{ 1}}表。突出显示的不是很好,但是很清楚。
这部分内容不清楚=> 我本来以为customers
值指向foreignKey
模型的属性,所以我在此处具有经过虚线处理的Person
属性。 这是不正确的。这实际上是在询问您数据库列名,这听起来有点像反样式...在属性中,如果要引用,则必须定义一个customer-id
在ORM下的db列。
此外,请注意,columnName
属性在关系中被重用,但对于不同的foreignKey
上下文而言,意味着不同的含义。在type
中,询问“哪个列有映射到主键这里?”
hasMany
模型:
Customer
关系另一端的{
"name": "Customer",
"plural": "customers",
"base": "PersistedModel",
"options": {
"validateUpsert": true,
"postgresql": {
"tableName": "customers"
}
},
"properties": {
"name": {
"type": "string"
},
"is-active": {
"type": "boolean",
"default": false,
"postgresql": {
"columnName": "is_active"
}
}
},
"validations": [],
"relations": {
"people": {
"type": "hasMany",
"model": "Person",
"foreignKey": "customer_id"
}
},
"acls": [],
"methods": {}
}
模型。
Person
关系的foreignKey
提出了相反的问题……“哪个属性此处映射到主键有? “
此外,如果您有不想公开的属性(尤其是继承了模型并且出于某种原因不希望/需要所有这些属性),则可以使用belongsTo
隐藏它们元件。见下文。
hidden