TableQuery 到大小写 class

TableQuery to case class

我有一些理解循环。此处使用的对象是使用 slick.codegen.SourceCodeGenerator:

从数据库自动生成的
for {
  boxer <- Boxers.filter { b => b.address === someAddress }
  fullBoxer <- buildFullBoxer(boxer)
} yield {
  fullBoxer
}

buildFullBoxer 函数将 case class BoxersRow 作为参数,因此循环无法编译并生成错误:

type mismatch; found : models.Tables.Boxers required: models.Tables.BoxersRow

生成的架构代码为:

case class BoxersRow(id: Long, firstName: String, lastName: String, nick: Option[String] = None, boxingTypeId: Int = 0, birthDate: Option[java.sql.Date] = None, address: Option[String] = None, lastUpdated: java.sql.Timestamp)
implicit def GetResultBoxersRow(implicit e0: GR[Long], e1: GR[String], e2: GR[Option[String]], e3: GR[Int], e4: GR[Option[java.sql.Date]], e5: GR[java.sql.Timestamp]): GR[BoxersRow] = GR{
    prs => import prs._
    BoxersRow.tupled((<<[Long], <<[String], <<[String], <<?[String], <<[Int], <<?[java.sql.Date], <<?[String], <<[java.sql.Timestamp]))
}
class Boxers(_tableTag: Tag) extends Table[BoxersRow](_tableTag, "boxers") {
    def * = (id, firstName, lastName, nick, boxingTypeId, birthDate, address, lastUpdated) <> (BoxersRow.tupled, BoxersRow.unapply)
    def ? = (Rep.Some(id), Rep.Some(firstName), Rep.Some(lastName), nick, Rep.Some(boxingTypeId), birthDate, address, Rep.Some(lastUpdated)).shaped.<>({r=>import r._; _1.map(_=> BoxersRow.tupled((_1.get, _2.get, _3.get, _4, _5.get, _6, _7, _8.get)))}, (_:Any) =>  throw new Exception("Inserting into ? projection not supported."))

    val id: Rep[Long] = column[Long]("id", O.AutoInc, O.PrimaryKey)
    ....
}
lazy val Boxers = new TableQuery(tag => new Boxers(tag))

当然我不想更改自动生成的模式对象。 buildFullBoxer 函数从数据库中读取额外的数据并构建一个包含所有必要数据的通用 FullBoxer 对象。

private def buildFullBoxer(boxersRow: BoxersRow): DBIO[FullBoxer] = {
    val query = for {
      ((((boxer, fight), division), b1), b2) <-
      Boxers.filter(_.id === boxersRow.id)
        .joinLeft(Fights).on((b, f) => (b.id === f.firstBoxerId) || (b.id === f.secondBoxerId))
        .joinLeft(Divisions).on((bf, d) => bf._2.map { _.divisionId === d.id })
        .joinLeft(Boxers).on((bfd, b1) => bfd._1._2.map { _.firstBoxerId === b1.id } )
        .joinLeft(Boxers).on((bfdb1, b2) => bfdb1._1._1._2.map { _.secondBoxerId === b2.id } )
    } yield (boxer, fight, division, b1, b2)

    val action = query.result.map {case sequence => sequence.groupBy(x => x._1) }.
      map { _.map { case (box, tup) => (box, tup.map { case (b, f, d, b1, b2) => f.map { fight => (fight, d.getOrElse(throw NoDivisionException("No such a division: " + fight.divisionId)), b1.getOrElse(throw NoBoxerException("No boxer with id " + fight.firstBoxerId, Seq.empty, None)), b2.getOrElse(throw NoBoxerException("No boxer with id " + fight.secondBoxerId, Seq.empty, None)), Seq.empty) }  } map (_.map(FullFight.tupled)) flatten ) } toSeq }.
      map {_.map(FullBoxer.tupled).head }
    action
}

如何在这个 for comprehension 循环中将 case class BoxersRow 传递给 buildFullBoxer 函数?

此致!

Boxers.filter { b => b.address === someAddress } return只是一个查询,不是结果。可以组合和扩展查询。考虑编写强类型 SQL 查询。为了得到一个结果集,你需要运行对数据库的查询分别操作。

所以首先您需要创建 DBIOAction:Boxers.filter(b => b.address === someAddress).result。可以再次组合动作,但不能再扩展。

其次 运行 针对数据库的操作:db.run(Boxers.filter(b => b.address === someAddress).result),而 dbDatabase 对象(参见 Slick docs)。 db.run 终于 return 变成了 Future[Seq[BoxerRow]].

然后您可以 运行 buildFullBoxer 直接使用 map:

val fullBoxers: Future[Seq[WhateverBuildFullBoxerReturns]] = {
  db.run(Boxers.filter(b => b.address === someAddress).result).map { results =>
    results.map(boxer => buildFullBoxer(boxer))
  }
}

我在这里添加另一个答案,因为您没有利用 slicks 查询功能。工作流一般是Query -> Action -> Result。根据经验,保持尽可能低的水平。这意味着使用 Query 直到不再可能。然后合并 DBIOAction,最后如果确实需要,开始合并 Future(结果)。

slicks 的核心功能之一是,您可以将多个查询组合并混合为一个查询。普通 SQL 不可能做到的事情。您的用例可以通过混合两个查询轻松解决。它可能看起来像这样(前面未经测试的代码):

object BoxerQuery {
   val findFullBoxers = {
     Boxers
      .joinLeft(Fights).on((b, f) => (b.id === f.firstBoxerId) || (b.id === f.secondBoxerId))
      .joinLeft(Divisions).on((bf, d) => bf._2.map { _.divisionId === d.id })
      .joinLeft(Boxers).on((bfd, b1) => bfd._1._2.map { _.firstBoxerId === b1.id } )
      .joinLeft(Boxers).on((bfdb1, b2) => bfdb1._1._1._2.map { _.secondBoxerId === b2.id } )
      .map { 
         case ((((boxer, fight), division), b1), b2) => (boxer, fight, division, b1, b2)
       }
   }

   def findFullBoxerByAddress(address: Rep[Address]) = findFullBoxers.filter(fbQuery => fbQuery._1.address === address)

   def findFullBoxerByWebaddress(webaddress: Rep[Webaddress] = findFullBoxers.filter(fbQuery => fbQuery._1.webaddress === webaddress)
}

上面的整个代码块仍然处于 Query 级别。您可以根据需要混合和组合查询。一个体面的 IDE 在这里很有帮助。如果查询最后returns你需要什么,创建一个returns a FullBoxer:

的action
val action: DBIOAction[Seq[FullBoxer]] = Query.findFullBoxerByAddress(address).result.map(_.groupBy(_._1).map {
  case (boxer, grp) => FullBoxer(
    boxer,
    grp.flatMap(_._2).distinct,
    grp.flatMap(_._3).distinct,
    grp.flatMap(_._4).distinct
  )
})

现在我们已经准备就绪,运行 action 对数据库并在一次往返中获取所有 FullBoxer 个对象:

val fullBoxers: Future[Seq[FullBoxer]] = db.run(action)