Создайте запись и подключите ее к существующей записи prisma client (отношение 1 к 1)

#postgresql #next.js #prisma #next-auth

#postgresql #next.js #prisma #далее -авторизация

Вопрос:

Я создаю следующее JS-приложение с prisma и postgres.
У меня есть 2 таблицы: User и Profile
Их структура схемы prisma выглядит следующим образом:

 model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?

  // foreign keys
  sessions      Session[]
  profile       Profile?
}

model Profile {
  id                Int      @id @default(autoincrement())
  isAdmin           Boolean  @default(false)
  firstName         String
  lastName          String
  email             String   @unique
  phone             String
  address           String
  gender            String
  image             Bytes
  guardianName1     String
  guardianPhone1    String
  guardianRelation1 String
  guardianName2     String?
  guardianPhone2    String?
  guardianRelation2 String?
  guardianName3     String?
  guardianPhone3    String?
  guardianRelation3 String?
  createdAt         DateTime @default(now())
  updatedAt         DateTime @updatedAt

  // foreign keys
  user              User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  userId            String   @default(cuid()) // relation scalar field (used in the `@relation` attribute above)

  requests      Request[]
}
 

Я также использую next-auth для аутентификации часть этого приложения. Поэтому, когда пользователь регистрируется после проверки его электронной почты, next-auth сам добавляет запись пользователя в таблицу пользователей.
Пока здесь нет проблем.

Затем, когда пользователь впервые открывает свою панель мониторинга, ему отображается форма для заполнения, после отправки этой формы запись должна быть вставлена в таблицу профилей. Поскольку таблицы Profile и User связаны, их также необходимо подключить.

Поэтому, когда пользователь отправляет форму сведений о профиле, я делаю это:

 try {
    const newProfileData = {
        // other fields data here...
        user: {
            connect: { id: '1' } // where User table already has a record with - 'id': 1
        }
    };
    const profile = await prisma.profile.create({ data: newProfileData, include: { user: true } });
    if(profile) { 
        console.log("Created: ", profile);
        res.status(200).json({ msg: 'Successfully Created Profile!' }); 
    }
}
catch(err)
{
    console.log(err);
}
 

Но при запуске этого кода я получаю сообщение об ошибке:

 The change you are trying to make would violate the required relation 'ProfileToUser' between the `Profile` and `User` models.
...
code: 'P2014',
clientVersion: '2.30.3',
meta: {
    relation_name: 'ProfileToUser',
    model_a_name: 'Profile',
    model_b_name: 'User'
}
 

Как это можно решить?
Я даже попробовал другой способ (т. Е. Обновить существующего пользователя и Создать связанную с ним запись профиля):

 const user = await prisma.user.update({
    where: {
        email: req.body.email,
    },
    data: {
        profile: {
            create: {
                // data fields here... (without the user field)
            },
        },
    },
});
 

Но это также дает ту же ошибку …
Я хочу понять, почему возникает ошибка. Разве это не правильный способ создать запись для отношения 1 к 1 с помощью prisma-client?

Комментарии:

1. Соотношение 1: 1 всегда вызывает сомнения, в данном случае оно совершенно не нужно. Единственный атрибут в profile этом заключается не в user том, чтобы быть isAdmin столбцом. Если, конечно, вы не разрешите, чтобы столбцы name и email (которые есть в обоих) были разными, но тогда у вас есть другой набор проблем.

2. Нет, на самом деле в таблице профилей намного больше столбцов, чем указано (вместо того, чтобы писать все это, я написал — // other attributes here )

3. Вы пытались поменять свой user: { connect: {id: '1' на простой userId: '1' и сделать include: { user: false } ? Мне интересно userId String @default(cuid()) , заставляет ли ваше определение генерировать новый случайный идентификатор, который не соответствует ни одному существующему User , когда вы выпускаете prisma.profile.create() . И когда вы указываете include: { user: true } во время этого создания, не пытается ли он создать еще одного нового пользователя с еще одним автоматически созданным id ?

Ответ №1:

Исправление:

Я думаю, вам нужно удалить @default(cuid()) из определения поля профиля userId .

 model Profile {
//...
  // foreign keys
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  userId    String   // relation scalar field (used in the `@relation` attribute above)
//...
}
 

А также избавьтесь от include: { user: true } :

 const profile = await prisma.profile.create({ data: newProfileData});
 

Объяснение:

Профили user и userId поля напрямую не преобразуются в фактические столбцы в БД, а являются полями, которые позволяют Prisma обрабатывать связь между отношениями. В конечном итоге он преобразуется в PostgreSQL

 create table profile(
    --...
    userId text references user (id),
    --...
);
 

И позже Prisma заполнит это поле именем вашего пользователя id при выдаче user:{connect:{id:'1'}} . Что могло произойти, так это то, что при использовании @default(cuid()) userId определения поля in вы вмешались в этот процесс. Теперь столбец заканчивается как

     userId text default gen_random_uuid() references user (id)
 

и всякий раз, когда вы создаете профиль, вводится новая строка без указания вашей собственной userId (что Prisma, вероятно, пытается сделать, прежде чем попытается связать вашего пользователя), генерируется случайный идентификатор, который не соответствует ни одному существующему пользователю, что нарушает ограничение ссылки.

Это то, что и / или ваше использование include: { user: true } что-то портит, порождая отдельного нового пользователя, даже если вы пытались связать свой профиль с существующим. Но я бы ожидал, что это будет просто нежелательным побочным эффектом, из-за которого ваш код порождает бесполезный пользовательский объект и строку каждый раз, когда вы создаете профиль.

Как только вы избавитесь от @default(cuid()) , вы также можете просто создать отдельный, несвязанный профиль, а затем связать его с соответствующим пользователем позже с помощью инструкции update.

Комментарии:

1. Большое вам спасибо! Я сделал это, как вы сказали, и запись профиля успешно создается.

Ответ №2:

Объедините две таблицы в одну, что-то вроде:

 model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  isAdmin           Boolean  @default(false)
  createdAt         DateTime @default(now())
  updatedAt         DateTime @updatedAt
    
  // foreign keys
  sessions      Session[]
}
 

Если вам абсолютно необходимо иметь отношение к профилю, создайте представление базы данных:

 create view Profile as
select
  id,
  isAdmin,
  name,
  email,
  createdAt,
  updatedAt,
  userId
from user
 

и сопоставьте ее как отношение только для чтения, но я не вижу смысла.

Комментарии:

1. Я использую next-auth для части аутентификации этого проекта. Поэтому, когда пользователь регистрируется, next-auth вставляет свою запись в таблицу User. Затем, когда пользователь впервые открывает свою панель мониторинга, требуются другие сведения о пользователе, которые будут вставлены в таблицу профилей. Разве это не решение предоставляемой ошибки, например, как вставить запись в таблицу, которая имеет отношение 1 к 1 с другой? Как без изменения структуры БД? Кроме того, я хочу понять причину этой ошибки