Search code examples
nestjstypeorm

QueryRunnerAlreadyReleasedError when executing a series of queries in a single transaction


I add an OnMoudleInitservice to initialize some sample data in my NestJS application.

TypeORM provides several approaches to wrap the queries into a single transcation.

I tried to use EntityManager.transaction to wrap the operations.

 await this.manager.transaction(async (manager) => {
      // NOTE: you must perform all database operations using the given manager instance
      // it's a special instance of EntityManager working with this transaction
      // and don't forget to await things here
      const del = await manager.delete(PostEntity, {});
      console.log('posts deleted: ', del.affected);

      const userDel = await manager.delete(UserEntity, {});
      console.log('users deleted: ', userDel.affected);

      const user = new UserEntity();
      Object.assign(user, {
        firstName: 'hantsy',
        lastName: 'bai',
        email: '[email protected]',
      });
      const savedUser = await manager.save(user);
      console.log('saved user: ', JSON.stringify(savedUser));
      this.data.forEach(async (d) => {
        const p = new PostEntity();
        p.author = savedUser;
        
        // comment out these relation settings it will work well.
        // 
        // const comment = new CommentEntity();
        // comment.content = 'test comment at:' + new Date();
        // p.comments = Promise.resolve([comment]);
        Object.assign(p, d);
        await manager.save(p);
      });
    });

    const savedPosts = await this.postRepository.find({});
    console.log('saved:', JSON.stringify(savedPosts));
  }

When the application is starting up, the following error occurred.

posts deleted:  2
users deleted:  1
saved user:  {"firstName":"hantsy","lastName":"bai","email":"[email protected]","id":"04d5cc63-d36a-4d80-a37f-97424ef168a8"}
D:\hantsylabs\nestjs-graphql-sample\node_modules\typeorm\error\QueryRunnerAlreadyReleasedError.js:10
        var _this = _super.call(this) || this;
                           ^

QueryRunnerAlreadyReleasedError: Query runner already released. Cannot run queries anymore.
    at new QueryRunnerAlreadyReleasedError (D:\hantsylabs\nestjs-graphql-sample\node_modules\typeorm\error\QueryRunnerAlreadyReleasedError.js:10:28)

Update: I found this is caused by the post/comment cascade settings, I was trying to use one command to save post/comments.

  @OneToMany((type) => CommentEntity, (comment) => comment.post, {
    cascade: true,
  })
  comments?: Promise<CommentEntity[]>;

Update 2: If I use the Repository class to execute the save task, it seems it works.

    const post = new PostEntity();
    post.title = 'test title';
    post.content = 'test content';
    const comment = new CommentEntity();
    comment.content = 'test comment';
    post.comments = Promise.resolve([comment]);
    await this.postRepository.save(post);
    //console.log('saved from repository: ', JSON.stringify(savedPost));

When I added the above codes before the manager transaction block, and I found the manager.delete(Post, {}) did not apply the cascade settings?


Solution

  • Got the answer from Nestjs/TypeORM discord discusstion.

    Chnage the following codes to:

    this.data.forEach(async (d) => {
            const p = new PostEntity();
            p.author = savedUser;
            
            // comment out these relation settings it will work well.
            // 
            // const comment = new CommentEntity();
            // comment.content = 'test comment at:' + new Date();
            // p.comments = Promise.resolve([comment]);
            Object.assign(p, d);
            await manager.save(p);
          });
    

    to this, use Promise.all to wrap all async codes.

          await Promise.all(
            this.data.map(async (d) => {
              const p = new PostEntity();
              Object.assign(p, d);
              p.author = user;
    
              const c = CommentEntity.of('test comment at:' + new Date());
              p.comments = Promise.resolve([c]);
              await mgr.save(p);
            }),
          );
        });