Reputation: 101
I'm trying to update a one to many relationship in Prisma. My schema looks like this
model A_User {
id Int @id
username String
age Int
bio String @db.VarChar(1000)
createdOn DateTime @default(now())
features A_Features[]
}
model A_Features {
id Int @id @default(autoincrement())
description String
A_User A_User? @relation(fields: [a_UserId], references: [id])
a_UserId Int?
}
I'm trying to add a couple of new features to user with id: 1, or update them if they are already there.
I'm trying doing something like
const post = await prisma.a_User.update({
where: { id: 1},
data: {
features: {
upsert: [
{ description: 'first feature'},
{ description: 'second feature'}
]
}
}
})
The compiler isn't happy, it tells me
Type '{ features: { upsert: { description: string; }[]; }; }' is not assignable to type '(Without<A_UserUpdateInput, A_UserUncheckedUpdateInput> & A_UserUncheckedUpdateInput) | (Without<...> & A_UserUpdateInput)'.
Object literal may only specify known properties, and 'features' does not exist in type '(Without<A_UserUpdateInput, A_UserUncheckedUpdateInput> & A_UserUncheckedUpdateInput) | (Without<...> & A_UserUpdateInput)'.ts(2322)
index.d.ts(1572, 5): The expected type comes from property 'data' which is declared here on type '{ select?: A_UserSelect; include?: A_UserInclude; data: (Without<A_UserUpdateInput, A_UserUncheckedUpdateInput> & A_UserUncheckedUpdateInput) | (Without<...> & A_UserUpdateInput); where: A_UserWhereUniqueInput; }'
(property) features: {
upsert: {
description: string;
}[];
}
I can't work out how to do it nor I can find clear help in the documentation. Any idea on how to implement it or where I can find some examples?
Upvotes: 9
Views: 57293
Reputation: 7198
I'm providing my solution based on the clarifications you provided in the comments. First I would make the following changes to your Schema.
model A_User {
id Int @id
username String
age Int
bio String @db.VarChar(1000)
createdOn DateTime @default(now())
features A_Features[]
}
model A_Features {
id Int @id @default(autoincrement())
description String @unique
users A_User[]
}
Notably, the relationship between A_User
and A_Features
is now many-to-many. So a single A_Features
record can be connected to many A_User
records (as well as the opposite).
Additionally, A_Features.description
is now unique, so it's possible to uniquely search for a certain feature using just it's description.
You can read the Prisma Guide on Relations to learn more about many-to-many relations.
Again, based on the clarification you provided in the comments, the update operation will do the following:
Overwrite existing features
in a A_User
record. So any previous features
will be disconnected and replaced with the newly provided ones. Note that the previous features
will not be deleted from A_Features
table, but they will simply be disconnected from the A_User.features
relation.
Create the newly provided features that do not yet exist in the A_Features
table, and Connect the provided features that already exist in the A_Features
table.
You can perform this operation using two separate update
queries. The first update will Disconnect all previously connected features
for the provided A_User
. The second query will Connect or Create the newly provided features
in the A_Features
table. Finally, you can use the transactions API to ensure that both operations happen in order and together. The transactions API will ensure that if there is an error in any one of the two updates, then both will fail and be rolled back by the database.
//inside async function
const disconnectPreviouslyConnectedFeatures = prisma.a_User.update({
where: {id: 1},
data: {
features: {
set: [] // disconnecting all previous features
}
}
})
const connectOrCreateNewFeatures = prisma.a_User.update({
where: {id: 1},
data: {
features: {
// connect or create the new features
connectOrCreate: [
{
where: {
description: "'first feature'"
}, create: {
description: "'first feature'"
}
},
{
where: {
description: "second feature"
}, create: {
description: "second feature"
}
}
]
}
}
})
// transaction to ensure either BOTH operations happen or NONE of them happen.
await prisma.$transaction([disconnectPreviouslyConnectedFeatures, connectOrCreateNewFeatures ])
If you want a better idea of how connect
, disconnect
and connectOrCreate
works, read the Nested Writes section of the Prisma Relation queries article in the docs.
Upvotes: 14
Reputation: 3218
The TypeScript definitions of prisma.a_User.update
can tell you exactly what options it takes. That will tell you why the 'features' does not exist in type
error is occurring. I imagine the object you're passing to data
takes a different set of options than you are specifying; if you can inspect the TypeScript types, Prisma will tell you exactly what options are available.
If you're trying to add new features, and update specific ones, you would need to specify how Prisma can find an old feature (if it exists) to update that one. Upsert won't work in the way that you're currently using it; you need to provide some kind of identifier to the upsert call in order to figure out if the feature you're adding already exists.
https://www.prisma.io/docs/reference/api-reference/prisma-client-reference/#upsert
You need at least create
(what data to pass if the feature does NOT exist), update
(what data to pass if the feature DOES exist), and where
(how Prisma can find the feature that you want to update or create.)
You also need to call upsert
multiple times; one for each feature you're looking to update or create. You can batch the calls together with Promise.all
in that case.
const upsertFeature1Promise = prisma.a_User.update({
data: {
// upsert call goes here, with "create", "update", and "where"
}
});
const upsertFeature2Promise = prisma.a_User.update({
data: {
// upsert call goes here, with "create", "update", and "where"
}
});
const [results1, results2] = await Promise.all([
upsertFeaturePromise1,
upsertFeaturePromise2
]);
Upvotes: 2