0
0

More than 1 year has passed since last update.

Sequelizeのupsertではフックが思った挙動にならない件

Posted at

sequelize-typescriptを使っていて、自動で更新者IDを設定する仕組みをSequelizeのフックを使って実現しようとしたんですが、upsertだけ上手くいかなかったのでメモを残しておきます。

やろうとしたこと

createやupdate、upsert等の更新系のメソッド呼び出しでリクエスト中のユーザー情報を渡して、フック内でカラムを自動設定したい

sample.ts
const user: User = { id: 1 };
this.model.upsert(data, { user });

なお、以下のように型宣言してあげるとupsertの第2引数に好きな型を渡せるようになります。
この第2引数の値はフック側で受け取れるようになるので、必要に応じて拡張します。

sequelize.d.ts
declare module 'sequelize' {
  export interface UpsertOptions {
    user?: User;
  }
}

現象

createやupdate、bulkCreateはbefore~フックで変更した内容がDBにちゃんと反映されるのにupsertだけDBに反映されない

調査

beforeUpsertの中で値を更新しても実際のカラムは更新されないみたいで、かなり昔からある問題みたいです。。。
(2018年に作成されたIssueが2023年9月時点でいまだにOpenのままです)
参考:https://github.com/sequelize/sequelize/issues/8829

sample.model.ts
@BeforeUpsert
onBeforeUpsert(values: any, options: UpsertOptions) {
  // ここで値を更新しても実際のカラムには反映されない
  values.createUserId = options.user.id;
}

補足:beforeUpsertに渡されるオブジェクトはモデルクラスではなくupsert呼び出し時の更新オブジェクトらしい

beforeCreateやbeforeUpdate、beforeBulkCreate等のフックでは更新対象のモデルクラスがvaluesに渡されるんですが、upsertはなぜか更新用オブジェクトがそのまま渡されるそうです。。。

以下でコメントされてました
https://github.com/sequelize/sequelize/issues/8829#issuecomment-355338534

sample.ts
const data = { key: value };
this.model.upsert(data);
sample.model.ts
@BeforeUpsert
onBeforeUpsert(values: any, options: UpsertOptions) {
  // ここで渡されるvaluesは上記upsertの第一引数に渡したオブジェクトが参照渡しされる
  // valuesを編集するとsample.ts型のdataの値が変わります
}

回避策①:updateに分ける

更新対象データから追加用と更新用にデータを分けて、bulkCreateとupdateそれぞれでクエリを実行する

sample.ts
const arryForAdd = [];
const arryForUpd = [];
for (const d of data) {
  if (d.id) {
    // idがあるデータを更新対象と判断する
    arryForUpd.push(d);
  } else {
    arryForAdd.push(d);
  }
}

return this.sequelize.transaction(async (transaction) => {
  if (arryForAdd.length) {
    await this.model.bulkCreate(arryForAdd, { transaction });
  }
  if (arryForAdd.length) {
    await this.model.update(arryForAdd, { transaction });
  }
}

回避策②:bulkCreateを使う

bulkCreateのupdateOnDuplicateオプションを使うことでupsertと同じような挙動を実現できます。
beforeBulkCreateはちゃんとデータ更新されます。
ただ、更新対象のカラムを文字列で指定しないといけないので、メンテナンス性は微妙。。。(カラムの追加や名称変更時に見逃しやすい)

sample.ts
this.model.bulkCreate(data, {
  updateOnDuplicate: [
    'createUserId',
    ・・・
  ]
});

参考:https://sequelize.org/api/v6/class/src/model.js~model#static-method-bulkCreate

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0