Как вставить запись с иностранным ключом, используя slick 3?

У меня есть две модели:

case class User(uid: Option[Int], email: String, password: String, created_at: Timestamp, updated_at: Timestamp)

case class UserProfile(firstname: String, lastname: String, gender: Int, user_id: Long),

И DAO с определенной пользовательской таблицей:

package dao

import java.sql.Timestamp

import scala.concurrent.{Await, Future}
import javax.inject.Inject

import models.{User, UserProfile}
import play.api.db.slick.DatabaseConfigProvider
import play.api.db.slick.HasDatabaseConfigProvider
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import slick.driver.JdbcProfile
import slick.profile.SqlProfile.ColumnOption.SqlType
import scala.concurrent.duration._
import com.github.t3hnar.bcrypt._

class UsersDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] {
  import driver.api._

  private val Users = TableQuery[UsersTable]
  private val UsersProfile = TableQuery[UserProfileTable]


  def all(): Future[Seq[User]] = db.run(Users.result)

  def insert(user: User): Future[Int] = {
    println("coming inside insert of user dao")
    println(user)
//    insertUP(user)
    val hashPassword = user.password.bcrypt
    val updatedUser = user.copy(password = hashPassword)

    val query = db.run((Users returning Users.map(_.uid)) += updatedUser)
//    val uid = Await.result(query, 30 seconds)
//    println(s"UID ---------> $uid")
    query
  }




  def findByEmail(email: String): Option[User] = {

    val query = for {
      u <- Users if u.email === email
    } yield u

    val f: Future[Option[User]] = db.run(query.result).map(_.headOption)
    val result = Await.result(f, 30 seconds)
    println(result.isDefined)
    result
  }

  def authenticate(username: String, password: String): Future[Option[User]] = {

    val query = db.run(Users.filter(_.email === username).result.map(_.headOption.filter(user => password.isBcrypted(user.password)))).map(_.headOption)

    query
  }

  private class UsersTable(tag: Tag) extends Table[User](tag, "users") {

    def uid = column[Int]("uid", O.PrimaryKey, O.AutoInc, O.SqlType("INT"))
    def email = column[String]("email")
    def password = column[String]("password")
    def created_at = column[Timestamp]("created_at", SqlType("timestamp not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP"))
    def updated_at = column[Timestamp]("updated_at", SqlType("timestamp not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP"))
    def idx = index("email_UNIQUE", email, unique = true)

    def * = (uid.?, email, password, created_at, updated_at) <> (User.tupled, User.unapply _)
  }

  private class UserProfileTable(tag: Tag) extends Table[UserProfile](tag, "user_profile"){

    def firstname = column[String]("firstname")
    def lastname = column[String]("lastname")
    def gender = column[Int]("gender")
    def user_id = column[Int]("user_id")

    def * = (firstname, lastname, gender, user_id) <> (UserProfile.tupled, UserProfile.unapply)

    def fk_user_id = foreignKey("fk_user_id", user_id, Users)(_.uid)
  }



}

В функции вставки, как я могу добавить uid в поле user_id таблицы профиля пользователя в той же функции или вызове?


Изменить 1

Пробовал решение, предложенное Павлом, но получил исключение:

failed slick.SlickException: This DBMS allows only a single AutoInc column to be returned from an INSERT

Редактировать - 2

Теперь, попробовав некоторые решения, функция вставки выглядит так:

def insert(user: User): Future[Int] = {
    val hashPassword = user.password.bcrypt
    val updatedUser  = user.copy(password = hashPassword)

    val insertUser = (Users returning Users.map(_.uid)) += updatedUser
    def insertUserProfile(updatedUserProfile: UserProfile) = (UsersProfile returning UsersProfile.map(_.user_id)) += updatedUserProfile

    val insertUserThenProfile = for {
      createdUserId        <- insertUser
      createdUserProfileId <- insertUserProfile(UserProfile("First name", "Last name", gender = 0, user_id = createdUserId))
    } yield createdUserProfileId

    db.run(insertUserThenProfile.transactionally)
  }

Но все равно получаю ошибку: failed slick.SlickException: This DBMS allows only a single AutoInc column to be returned from an INSERT


Решение

Решение Pawels должно работать нормально, но некоторые СУБД дают исключение для невозврата поля AUtoInc и для попытки вернуть что-то еще. Вы можете увидеть примечание в документации: http://slick.lightbend.com/doc/3.0.0/queries.html

Note

Many database systems only allow a single column to be returned which must be the table’s auto-incrementing primary key. If you ask for other columns a SlickException is thrown at runtime (unless the database actually supports it).

Итак, теперь мои модели такие:

case class User(uid: Option[Int], email: String, password: String, created_at: Timestamp, updated_at: Timestamp)

case class UserProfile(upid: Option[Int], firstname: String, lastname: String, gender: Int, user_id: Int)

И класс таблицы:

private class UserProfileTable(tag: Tag) extends Table[UserProfile](tag, "user_profile"){

    def upid= column[Int]("upid", O.PrimaryKey, O.AutoInc, O.SqlType("INT"), O.Default(0))
    def firstname = column[String]("firstname")
    def lastname = column[String]("lastname")
    def gender = column[Int]("gender")
    def user_id = column[Int]("user_id")

    def * = (upid.?, firstname, lastname, gender, user_id) <> (UserProfile.tupled, UserProfile.unapply)

    def fk_user_id = foreignKey("fk_user_id", user_id, Users)(_.uid)
  }

И, наконец, метод вставки:

def insert(user: User): Future[Int] = {
    val hashPassword = user.password.bcrypt
    val updatedUser  = user.copy(password = hashPassword)

    val insertUser = (Users returning Users.map(_.uid)) += updatedUser
    def insertUserProfile(updatedUserProfile: UserProfile) = (UsersProfile returning UsersProfile.map(_.upid)) += updatedUserProfile

    val insertUserThenProfile = for {
      createdUserId        <- insertUser
      createdUserProfileId <- insertUserProfile(UserProfile(Some(0), "First name", "Last name", gender = 0, user_id = createdUserId))
    } yield createdUserProfileId

    db.run(insertUserThenProfile.transactionally)
  }

person Maverick    schedule 12.07.2016    source источник
comment
Вы хотите добавить как User, так и UserProfile в этот метод insert?   -  person Paweł Jurczenko    schedule 12.07.2016
comment
@PawełJurczenko да.   -  person Maverick    schedule 12.07.2016


Ответы (2)


Я не знаю, как вы планируете предоставлять значения для UserProfile (это зависит от вас, может быть, дополнительный параметр в методе insert), но я бы попробовал что-то вроде этого:

def insert(user: User): Future[UserProfile] = {
  val hashPassword = user.password.bcrypt
  val updatedUser  = user.copy(password = hashPassword)

  val insertUser = (Users returning Users.map(_.uid)) += updatedUser
  def insertUserProfile(updatedUserProfile: UserProfile) = (UsersProfile returning UsersProfile.map(_.user_id)) += updatedUserProfile

  val insertUserThenProfile = for {
    createdUserId        <- insertUser
    createdUserProfileId <- insertUserProfile(UserProfile("First name", "Last name", gender = 0, user_id = createdUserId))
  } yield createdUserProfileId

  db.run(insertUserThenProfile.transactionally)
}
person Paweł Jurczenko    schedule 12.07.2016
comment
Я пробовал этот метод, но получаю: failed slick.SlickException: This DBMS allows only a single AutoInc column to be returned from an INSERT - person Maverick; 13.07.2016
comment
Какую базу данных вы используете? - person Paweł Jurczenko; 13.07.2016
comment
Это версия Mysql 5 - person Maverick; 13.07.2016
comment
Я обновил свой ответ, теперь это должно работать. - person Paweł Jurczenko; 13.07.2016
comment
Нет, все еще жалуется на то же самое. - person Maverick; 13.07.2016
comment
Не могли бы вы проверить решение сейчас? Я обновил еще одну вещь. - person Paweł Jurczenko; 13.07.2016
comment
Теперь я получаю эту ошибку компиляции: type mismatch; found : slick.dbio.DBIOAction[Int,slick.dbio.NoStream,slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Transactional] required: slick.dbio.DBIOAction[models.UserProfile,slick.dbio.NoStream,Nothing] - person Maverick; 13.07.2016
comment
Я обновил свой вопрос в Edit - 2. Мне пришлось изменить подпись возврата на Future [Int], но все равно та же ошибка. - person Maverick; 13.07.2016
comment
Есть ли какая-то конкретная причина, по которой вы импортируете slick.driver.JdbcProfile вместо slick.driver.MySQLDriver? - person Paweł Jurczenko; 13.07.2016
comment
Я решил это :), обновлю свой вопрос и отмечу ваше решение как ответ. - person Maverick; 13.07.2016

Сделайте запрос, чтобы вставить нового члена и вернуть член с его идентификатором

val memberWithId = (queryMember returning queryMember.map(_.id) into ((c, id) => c.copy(id = id))) += registerMember.copy(password = pwHash)

Затем реализуйте запрос как транзакцию. Вставьте участника, вставьте профиль пользователя (вы можете использовать идентификатор из успеха первого оператора)

val transaction = (for {
  m: Member <- memberWithId
  p: UsersProfile <- queryProfile returning queryProfile += MemberProfile(m.id, firstName, ...)
} yield m).transactionally

Запустите его как попытку, чтобы вам было легче анализировать ошибки, а не сбой.

db.run(transaction.asTry)
person toidiu    schedule 12.07.2016