如何处理 slick 3 中的可选数据库步骤?

How to handle optional db step in slick 3?

我确定我只是面临 Slick 3 功能模型的心理障碍,但我无法辨别如何在 Slick 3 中对可选的依赖数据库步骤进行事务排序。具体来说,我有一个 table 带有可选的(可为空的)外键,我希望将其设置为插入的依赖记录的 ID(如果有的话,否则为空)。即,大致为:

if ( x is non null ) 
    start transaction
    id = insert x
    insert y(x = id)
    commit
else
    start transaction
    insert y(x = null)
    commit

当然,我宁愿没有大的选择。没有 Option[] 的依赖项看起来(相对)简单,但这个选项让我很困惑。

精确的示例代码(无导入)如下。在此示例中,问题是如果 y 是否为 None,如何在同一事务中同时保存 x (a) 和 y (b)。保存 Y 本身似乎很简单,因为每个相关的 C 都有一个非可选的 B 引用,但是解决 A 中的可选引用(对我来说)还不清楚。

    object test {
      implicit val db = Database.forURL("jdbc:h2:mem:DataTableTypesTest;DB_CLOSE_DELAY=-1", driver = "org.h2.Driver")


      /* Data model */
      case class A(id: Long, b: Option[Long], s: String)

      class As(tag: Tag) extends Table[A](tag, "As") {
        def id = column[Long]("ID", O.PrimaryKey, O.AutoInc)
        def b = column[Option[Long]]("B")
        def s = column[String]("S")

        def * = (id, b, s) <> (A.tupled, A.unapply)
      }
      val as = TableQuery[As]

      case class B(id: Long, s: String)

      class Bs(tag: Tag) extends Table[B](tag, "Bs") {
        def id = column[Long]("ID", O.PrimaryKey, O.AutoInc)
        def s = column[String]("S")

        def * = (id, s) <> (B.tupled, B.unapply)
      }
      val bs = TableQuery[Bs]

      case class C(id: Long, b: Long, s: String)

      class Cs(tag: Tag) extends Table[C](tag, "Cs") {
        def id = column[Long]("ID", O.PrimaryKey, O.AutoInc)
        def b = column[Long]("B")
        def s = column[String]("S")

        def * = (id, b, s) <> (C.tupled, C.unapply)
      }
      val cs = TableQuery[Cs]


      /* Object model */
      case class X(id: Long, s: String, y: Option[Y])

      case class Y(id: Long, s: String, z: Set[Z])

      case class Z(id: Long, s: String)


      /* Mappers */
      def xToA(x: X, bId: Option[Long]): A = { A(x.id, bId, x.s) }
      def yToB(y: Y): B = { B(y.id, y.s) }
      def zToC(z: Z, bId: Long): C = { C(z.id, bId, z.s) }

      /* Given */
      val example1 = X(0, "X1", Some(Y(0, "Y1", Set(Z(0, "Z11"), Z(0, "Z12")))))
      val example2 = X(0, "X2", Some(Y(0, "Y2", Set())))
      val example3 = X(0, "X3", None)

      Await.result(db.run((as.schema ++ bs.schema ++ cs.schema).create), 10.seconds)

      val examples = Seq(example1, example2, example3)
      for ( example <- examples ) {
        val saveY = (for { y <- example.y }
            yield ( for {
              id <- (bs returning bs.map(_.id)) += yToB(y)
              _  <- cs ++= y.z.map(zToC(_, id))
            } yield id) transactionally)
        if ( saveY.isDefined ) Await.result(db.run(saveY.get), 10.seconds)
      }

      println(Await.result(
        db.run(
          (for { a <- as } yield a).result
        ),
        10.seconds
      ))

      println(Await.result(
        db.run(
          (for { b <- bs } yield b).result
        ),
        10.seconds
      ))

      println(Await.result(
        db.run(
          (for { c <- cs } yield c).result
        ),
        10.seconds
      ))
    }

这很简单;只需使用 DBIO:

的一元性
// Input B value; this is your `x` in the question.
val x: Option[B] = _
// Assume `y` is fully-initialized with a `None` `b` value.
val y: A = _

// DBIO wrapping the newly-inserted ID, if `x` is set.
val maybeInsertX: DBIO[Option[Int]] = x match {
  case Some(xToInsert) =>
    // Insert and return the new ID.
    val newId: DBIO[Int] = bs.returning(bs.map(_.id)) += xToInsert
    // Map to the expected Option.
    newId.map(Some(_))
  case None =>
    // No x means no ID.
    DBIO.successful(None)
}

// Now perform your insert, copying in the newly-generated ID.
val insertA: DBIO[Int] = maybeInsertX.flatMap(bIdOption =>
  as += y.copy(b = bIdOption)
)

// Run transactionally.
db.run(insertA.transactionally)