环回设置一个 属性 以某种方式不会持久保存到数据库 sql?

Loopback setting a property that somehow doesn't persist to the database sql?

我们正在将 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 **************

但是当 loopback 执行更新 SQL 时,它不知何故 omits/removes updated-by 的值? (第二个参数应该是 1,而不是 null

LoopbackJS  | 2018-08-16T17:57:23.666Z loopback:connector:postgresql SQL: UPDATE "public"."asset_types" SET "is_active"=,"updated_by"=,"updated_on_utc"=::TIMESTAMP WITH TIME ZONE,"tenant_id"= WHERE "id"=
LoopbackJS  | Parameters: [false,null,"2018-08-16T17:57:23.660Z",1,5]

updated_by 在 Postgres 中是可以为 null 的,因此不应产生错误...但是 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 回来。

根本原因是关系不正确。

我创建了这个 as a gist,但也将其粘贴在这里以防它对其他人有帮助。

使用小写名称是 PostgreSQL 最佳做法,如果需要,请使用蛇形字母。即,my_column_name

另外,因为我使用 JSON API client, I've installed the excellent loopback-component-jsonapi 来处理 de/serialization 的东西...但这只是增加了额外的复杂性。

JSON API 要求破折号 属性 名称。当您从 my-property-name 之类的内容开始时,Loopback 或 PostgreSQL 驱动程序(并不重要)默认情况下会将破折号 属性 折叠为 mypropertyname

这很糟糕……尤其是当您有一个正在使用的现有模式时。

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

一个例子

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

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

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

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

{ 
  ...
  "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 负载的关系元素的名称。

A "gotcha" 对我来说是 foreignKey 属性.

Loopback 文档说它是可选的 - 它是 - 但如果您将其省略,则会将 id 后缀添加到名称 (people) 中,然后在您的customerstable。这一点没有很好地突出显示,但已经足够清楚了。

这部分没看清楚=>我本来以为foreignKey值指向属性Person 模型,所以我在这里使用了破折号 customer-id 属性。 这是不正确的。它实际上是在询问您数据库列名,这感觉有点反模式...如果您需要,您必须在属性中定义一个 columnName引用 ORM 下的数据库列。

此外,请注意 foreignKey 属性 在关系中重复使用,但它对不同的 type 上下文意味着不同的事情。在 hasMany 中,它询问 "Which column there maps to the primary key here?"

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

belongsTo 关系的 foreignKey 问的是相反的问题... "Which property here maps to the primary key there?"

此外,如果您有不想公开的属性(特别是如果您继承了一个模型并且出于某种原因没有 want/need 所有这些属性),那么您可以使用 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": {}
}