TypeORM upsert - 如果不存在则创建

68

TypeORM是否包含某些功能来避免这种情况:

let contraption = await thingRepository.findOne({ name : "Contraption" });

if(!contraption) // Create if not exist
{
    let newThing = new Thing();
    newThing.name = "Contraption"
    await thingRepository.save(newThing);
    contraption = newThing;
}

类似于:

let contraption = await thingRepository.upsert({ name : "Contraption" });

1
一个库可以实现这个功能 - https://github.com/danielmhanover/typeorm-upsert - danielmhanover
1
在这里所述的文档中,很容易创建一个扩展标准存储库的自定义存储库。https://typeorm.io/#/custom-repository - wheelmaker
感谢@danielmhanover的分享。那个仓库有一个更受欢迎的分支,支持批量更新 - https://github.com/lupu60/nestjs-toolbox - Noitidart
11个回答

68

正如Tomer Amir所指出的,目前有针对真正的upsert的部分解决方案,并且在TypeORM的存储库上已经开放了一个功能请求:

TypeORM upsert功能请求

部分解决方案:

await connection.createQueryBuilder()
        .insert()
        .into(Post)
        .values(post2)
        .onConflict(`("id") DO NOTHING`)
        .execute();

await connection.createQueryBuilder()
        .insert()
        .into(Post)
        .values(post2)
        .onConflict(`("id") DO UPDATE SET "title" = :title`)
        .setParameter("title", post2.title)
        .execute();

旧的答案实际上指向了做 OP 所要求的事情的 "更新" 方式:

已经有一种方法可以实现此操作:Repository<T>.save(),其文档说明如下:

将所有给定的实体保存到数据库中。如果实体在数据库中不存在,则插入;否则更新。

但是,如果您没有指定 id 或唯一字段集,则 save 方法无法知道您正在引用一个现有的数据库对象。

因此,在 typeORM 中进行 upsert 的方法是:

let contraption = await thingRepository.save({id: 1, name : "New Contraption Name !"});

1
这个解决方案并不是真正的“upsert”,它只是一个“update”。Upsert 是这个:https://github.com/typeorm/typeorm/issues/1090 - Tomer Amir
2
我该如何指定一个自动生成的UUID字段,以便.save可以正常工作,并且如果对象已经存在则不进行插入操作? - WeSt
4
values 参数传入一个记录数组而不是单个记录时,如何使用 onConflict - Landon Kuhn
@TomerAmir @BeyondTheSea 抱歉我理解不够,但是.save()和upsert有什么不同呢? - Ben Creasy
似乎 onConflict 在 MySQL 中不起作用,但 orUpdate 起作用:https://github.com/typeorm/typeorm/issues/1090#issuecomment-634391487 - AverageHelper
显示剩余3条评论

41

如果有人在2021年找到了这篇文章,Typeorm的Repository.save()方法会根据主键更新或插入匹配项。这在sqlite中也适用。

从文档中可以看到:

 /**
 * Saves all given entities in the database.
 * If entities do not exist in the database then inserts, otherwise updates.
 */

18
可以使用非主键进行更新操作吗?比如我有一个唯一的“电子邮件”字段。我能够执行 .save({ email: 'foo', age: 29 }) 这样的操作来更新具有 email: foo 的行吗? - Noitidart
1
我支持Noitidart的评论/问题:我们是否可以通过某些唯一字段而不是必须使用主键来完成这个操作? - gmagno
1
请问您能否将文档的本节链接一下? - Teodoro
有兴趣的人可以查看文档链接:https://typeorm.io/#/repository-api您需要滚动到“save”条目。 - callmetwan
1
对于想要使用非主键进行操作的所有人,以下是如何实现的方法:https://www.kindacode.com/snippet/typeorm-upsert-update-if-exists-create-if-not-exists/ - aviggiano
显示剩余3条评论

25

2023年更新

现在您可以使用upsert方法了。

await this.yourRepository.upsert({name: 'John'}, ['id']) // assuming id is unique

如果您有两个或更多列组成的约束,请执行以下操作:

首先使用Unique装饰器定义该约束。

@Entity()
@Unique('constraint_name', ['col_one', 'col_two'])

然后在代码中,您可以使用upsert方法。

await this.yourRepository.upsert({name: 'John'}, ['constraint_name'])

1
这对我有效。 但是,我必须设置conflictPaths的详细信息,而不仅仅是constraint_name。就像这样: await this.yourRepository.upsert({name: 'John'}, ['col_one', 'col_two']) - ninoorta
如果性能对你很重要,确实应该使用upsert。TypeORM文档中指出:与save方法不同,它执行的是一个原始操作,没有级联、关联和其他操作。执行快速高效的INSERT ... ON CONFLICT DO UPDATE/ON DUPLICATE KEY UPDATE查询 - MartijnvdB
约束的名称显示为“未知属性”,传递属性名称或列名称会出现“驱动程序错误:错误:没有与之匹配的唯一约束或排除约束”。 - itinance
myRepository.upsert(data, ['..'])的返回值是什么? - undefined
在我的情况下,使用id进行upsert操作并不起作用,至少在TypeORM 0.2.41版本中不起作用(当冲突的列是其他列时可以正常工作)。我不得不使用save方法,就像这个答案中建议的那样。 - undefined

25

使用INSERT IGNORE在MySQL和Postgres中忽略重复项:

await connection.createQueryBuilder()
        .insert()
        .into(Post)
        .values(post)
        .orIgnore()
        .execute();

2
不仅在MySQL上,Postgres也适用。 - Tai Ly
2
这应该是被接受的答案,因为onConflict已被标记为弃用。 - Prince Odame

17

这可能会帮助解决在MySQL中无法使用ONCONFLICT的问题。来自Github

await getConnection()
  .createQueryBuilder()
  .insert()
  .into(GroupEntity)
  .values(updatedGroups)
  .orUpdate({ conflict_target: ['id'], overwrite: ['name', 'parentId', 'web', 'avatar', 'description'] })
  .execute();

这个答案必须被接受为正确的答案。它是最好的! - Cassio Seffrin

12

对于任何希望通过Postgres和TypeORM更新/插入多个记录的人,您可以通过“excluded”关键字访问要更新/插入的行。

const posts = [{ id: 1, title: "First Post" }, { id: 2, title: "Second Post" }];

await connection.createQueryBuilder()
        .insert()
        .into(Post)
        .values(posts)
        .onConflict(`("id") DO UPDATE SET "title" = excluded."title"`)
        .execute();

1
onConflict方法已被标记为弃用,请查看Moshe Simantov的回答https://dev59.com/_FYN5IYBdhLWcg3w4bn1#63678950。 - grexlort

8

这在我这里有效。

我结合了@DedaDev的回答。

你的实体

@Entity()
@Unique('constraint_name', ['id'])

您的服务

await this.yourRepository.upsert(
  {
    id: uuid,
    key1: value1,
    ...
  },
  {
    skipUpdateIfNoValuesChanged: true, // If true, postgres will skip the update if no values would be changed (reduces writes)
    conflictPaths: ['id'], // column(s) name that you would like to ON CONFLICT
  },
);

5
2021 年发布的 TypeORM v0.2.40 版本增加了原生 upsert 支持。upsert 可以插入一个新实体或实体数组,除非它们已经存在,否则将进行更新。该功能受 AuroraDataApi、Cockroach、Mysql、Postgres 和 Sqlite 数据库驱动程序的支持。更多详情请参考 TypeORM Repository API

3
现在有一个可以插入到TypeORM中,帮助完成此操作。

2
您可能想要查看 Repository 类的 "preload" 方法:https://typeorm.delightful.studio/classes/repository_repository.repository.html#preload 创建一个新实体,使用给定的计划 JavaScript 对象。如果实体已经存在于数据库中,则加载它(以及与之相关的所有内容),将所有值替换为给定对象中的新值,并返回此新实体。这个新实体实际上是从数据库中加载的实体,其中所有属性都替换为新对象的属性。请注意,给定的类似实体的对象必须具有实体 ID / 主键才能通过其找到实体。如果找不到具有给定 ID 的实体,则返回 undefined。
如上所述,限制是需要按ID搜索。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接