简体   繁体   中英

Prisma multiple create queries with nested connectOrCreate throws unique constraint failed

I'm trying to create a model where there's a many-to-many relation between 2 types, Movie and Genre. I retrieve a list of movies from an external data source asynchronously, create each in my own database if it's not already created, and return the movie.

const retrieveList = async () => {
   const results = await API.getMovies();
   return results.map((item) => getMovieItem(item.id));
}

const getMovieItem = async (movieId) => {
    // see if is already in database
    const movie = await prisma.movie.findUnique({
        where: { tmdbId: movieId },
    });
    if (movie) {
        return movie;
    } else {
        // create and return if doesn't exist
        const details = await API.getDetails(movieId);
        const genresData = details.genres.map((genre) => ({
            create: {
                name: genre.name,
            },
            where: {
                name: genre.name
            },
        }));
        return prisma.movie.create({
            data: {
                title: details.title,
                description: details.overview,
                genres: {
                    connectOrCreate: genresData,
                },
            },
            select: {
                tmdbId: true,
                id: true,
                title: true,
                description: true,
                //...
            },
        });
    }
};

the problem is that there seems to be some sort of inconsistency where if the connection to the external API is slow enough in some runs, everything seems to be working fine; but also if it's fast enough, it throws the error:

Invalid `prisma.movie.create()` invocation:
  Unique constraint failed on the fields: (`name`)"

likely because it is trying to create a genre that's already created instead of connecting it.

As the docs states :

Multiple connectOrCreate queries that run as concurrent transactions can result in a race condition. ...

So if you request 2 movies of the same genre at the same time and this genre is not yet exist then both queries would try to create this genre and only the first one will succeed.

Docs recommends catching for specific error:

To work around this scenario, we recommend catching the unique violation exception ( PrismaClientKnownRequestError , error P2002 ) and retrying failed queries.

You can also create all the genres at first, for example:

// Some abstract function which extracts all the genres
const genres = gatherGenres(results);

await prisma.genres.createMany({
    data: genres,
    // Do nothing on duplicates
    skipDuplicates: true,
});

And then create all the movies and connect them to genres, because genres have already been created.

Also, small nitpick, you function retrieveList is not awaitable because it returns an array of promises, and to actually wait until all the promises are done you need to use Promise.all on it

const retrieveList = async () => {
   const results = await API.getMovies();
   return results.map((item) => getMovieItem(item.id));
}

// ...

// You can't just await it ❌
await retrieveList()

// Need to use Promise.all ✅
await Promise.all(retrieveList())

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM