最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to use Knex transactions for inserting records with one-to-many relationships? - Stack Overflow

programmeradmin3浏览0评论

I'm in a pretty common situation, but I'm struggling to figure out exactly how to achieve what I want by looking at Knex's documentation.

I have a set of entities with a one-to-many relationship to another set of entities. In the relational model, this is usually represented by a table containing a composite key, referencing the keys of the two related tables.

I'll provide an example to clarify. Let's say we have books, each of which can belong to many genres. I want to create a function that creates a book by its name, user, and a set of genres (and returns the created book with all properties; not just the id e.g.). If a genre doesn't already exist, we insert it, or if it does exist, we find it and retrieve its ID.

I want all this to happen within a single transaction - so if at any point something fails, and we've already added several genres to the book, we delete both the genres and the book.

How can I model this scenario with Knex?

Here's my attempt:

async create(input: {
    name: string;
    user: User;
    genres: string[];
  }): Promise<Book | undefined> {
    return await knex
      .transaction(async (trx) => {
        const [insertedBook] = await trx<Book>("books").insert(
          {
            name: input.name,
            user_id: input.user.id,
          },
          ["id", "name", "user_id", "created_at", "updated_at"]
        );

        input.genres.forEach(async (g) => {
          const { id } = await this.gs.findOrCreate(g);

          await trx<BookGenre>("book_genres").insert({
            book_id: insertedBook.id,
            genre_id: id,
          });
        });

        return insertedBook;
      })
      .then((ib: Book) => ib)
      .catch((error) => {
        console.error("Creating a book with genres failed", error);
        return undefined;
      });
  }

I'm in a pretty common situation, but I'm struggling to figure out exactly how to achieve what I want by looking at Knex's documentation.

I have a set of entities with a one-to-many relationship to another set of entities. In the relational model, this is usually represented by a table containing a composite key, referencing the keys of the two related tables.

I'll provide an example to clarify. Let's say we have books, each of which can belong to many genres. I want to create a function that creates a book by its name, user, and a set of genres (and returns the created book with all properties; not just the id e.g.). If a genre doesn't already exist, we insert it, or if it does exist, we find it and retrieve its ID.

I want all this to happen within a single transaction - so if at any point something fails, and we've already added several genres to the book, we delete both the genres and the book.

How can I model this scenario with Knex?

Here's my attempt:

async create(input: {
    name: string;
    user: User;
    genres: string[];
  }): Promise<Book | undefined> {
    return await knex
      .transaction(async (trx) => {
        const [insertedBook] = await trx<Book>("books").insert(
          {
            name: input.name,
            user_id: input.user.id,
          },
          ["id", "name", "user_id", "created_at", "updated_at"]
        );

        input.genres.forEach(async (g) => {
          const { id } = await this.gs.findOrCreate(g);

          await trx<BookGenre>("book_genres").insert({
            book_id: insertedBook.id,
            genre_id: id,
          });
        });

        return insertedBook;
      })
      .then((ib: Book) => ib)
      .catch((error) => {
        console.error("Creating a book with genres failed", error);
        return undefined;
      });
  }
Share Improve this question edited Mar 15 at 18:50 bambi asked Mar 15 at 18:41 bambibambi 134 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

You are almost there! Here is your corrected code where I have added in the .transacting(trx)

async create(input: {
    name: string;
    user: User;
    genres: string[];
  }): Promise<Book | undefined> {
    return await knex
      .transaction(async (trx) => {
        const [insertedBook] = await knex("books").insert({
            name: input.name,
            user_id: input.user.id,
          }) // removed what was here. since these are not raw statements, knex will handle sanitation for you anyways.
        .transacting(trx);

        input.genres.forEach(async (g) => {
          const { id } = await this.gs.findOrCreate(g);

          await knex("book_genres").insert({
            book_id: insertedBook.id,
            genre_id: id,
          })
          .transacting(trx);
        });

        return insertedBook;
      })
      .then((ib: Book) => ib)
      .catch((error) => {
        console.error("Creating a book with genres failed", error);
        return undefined;
      });
  }

Oh, you may have to add back the types again.

发布评论

评论列表(0)

  1. 暂无评论