1

我们正在将我们的 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 mixin(我们自己的)中,相应地设置了这些属性

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列正在运行,那么同样的事情也会发生在那里。

感觉就像我在这里转圈子,可能忽略了一些基本的东西。有什么建议么?

编辑

所以看起来它不仅限于混合...当我们完全删除它并在有效负载中手动设置 k:v 对(即'created-by': 1)时,我们仍然从 Postgres 得到相同的错误。

4

1 回答 1

1

造成这种情况的根本原因是不正确的关系。

我将其创建为要点,但也将其粘贴在这里以防万一它对其他人有所帮助。

使用小写名称是PostgreSQL最佳实践,如果需要,可以使用蛇形命名。即my_column_name

此外,由于我使用的是JSON API客户端,因此我安装了出色的 loopback-component-jsonapi来处理反序列化的东西......但这只会增加额外的复杂性。

JSON API 调用 dasherized 属性名称。当您从my-property-name、 Loopback 或 PostgreSQL 驱动程序(并不重要)开始时mypropertyname,默认情况下会将 dasherized 属性折叠为。

这很糟糕......尤其是当您拥有正在使用的现有架构时。

在处理关系时情况会更糟,因为 Loopback默认情况下还会附加id后缀,所以现在除非您碰巧有一mypropertynameid列,否则您会遇到问题。

一个例子

假设我们有一个Customer模型。我需要小写的端点(和虚线,如果适用),所以只需在此处更改复数以匹配。

{ 
  "name": "Customer",
  "plural": "customers",
  "base": "PersistedModel",
   ...
 }

在里面options.postgresql,你可以设置一个tableName. Loopback 将name 默认使用该值,但请记住 PostgreSQL 不喜欢 CamelCase。除非您使用小写模型名称,否则您需要覆盖它。

(这是一种宗教偏好,但我喜欢我的桌子是复数形式。打我吧。)

{ 
  ...
  "options": {
    "validateUpsert": true,
    "postgresql": {
      "tableName": "customers"
    }
  }
  ...
}

回到属性,使用postgresql.columnName属性映射到db中正确的列名。如果它不是一个 dasherized 属性名称(即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) 中,然后在你的customers表中查找该列。这没有很好地突出显示,但已经足够清楚了。

这部分不清楚 =>我原本以为foreignKey值指向模型的属性Person所以我在这里有 dasherizedcustomer-id属性。这是不正确的。它实际上是在向您询问数据库列名,这感觉有点像反模式......在属性中,columnName如果您想引用 ORM 下的 db 列,则必须定义 a 。

另外,请注意,该属性在关系中被重用,但它对不同的上下文foreignKey意味着不同的东西。type在 ahasMany中,它询问“那里的哪一列映射到这里的主键?”

最终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模型。

关系的问题foreignKeybelongsTo相反的问题......“这里的哪个属性映射到那里的主键?”

此外,如果您有不想暴露的属性(特别是如果您继承了模型并且出于任何原因不想/需要所有这些属性),那么您可以使用hidden元素隐藏它们。见下文。

{
  "name": "Person",
  "plural": "people",
  "base": "User",
  "idInjection": false,
  "options": {
    "validateUpsert": true,
    "postgresql": {
      "tableName": "people"
    }
  },
  "hidden": [
    "emailVerified",
    "realm",
    "username",
  ],
  "properties": {
    "first-name": {
      "type": "string",
      "postgresql": {
        "columnName": "first_name"
      }
    },
    "last-name": {
      "type": "string",
      "postgresql": {
        "columnName": "last_name"
      }
    },
    "email": {
      "type": "string"
    },
    ...
  },
  "validations": [],
  "relations": {
    "customer": {
      "type": "belongsTo",
      "model": "Customer",
      "foreignKey": "customer_id"
    }
  },
  "acls": [],
  "methods": {}
}
于 2018-08-24T03:09:43.683 回答