Slick 3.1.1 查询类型的上限

Upper bound for Slick 3.1.1 query type

我有一个为 DAO 提供通用功能的 DAO 助手特征。它需要能够访问 table 查询和 运行 操作。我在定义或以其他方式向助手特征提供查询类型时遇到问题。

下面是一些代码,也可以在 action 分支中的简短 demo project on GitHub 中找到。 首先,db 定义在 trait DBComponent:

trait DBComponent {
  import slick.driver.JdbcProfile

  val driver: JdbcProfile
  import driver.api._

  val db: Database
}

要持久化的 classes 扩展 HasId:

trait HasId {
  def id: Option[Int] = None
}

这里有一个 class 要坚持的:

case class BankInfo(
  owner: String,
  branches: Int,
  bankId: Int,
  override val id: Option[Int] = None
) extends HasId

问题是我不知道如何在下面的DAO helper trait中设置QueryType;我预计接下来的大部分错误都是由于我使用的类型不正确造成的:

/** Handles all actions pertaining to HasId or that do not require parameters */
trait DbAction[T <: HasId] { this: DBComponent =>
  import driver.api._ // defines DBIOAction

  type QueryType <: slick.lifted.TableQuery[Table[T]] // this type is wrong! What should it be?
  def tableQuery: QueryType

  // Is this defined correctly?
  def run[R](action: DBIOAction[R, NoStream, Nothing]): Future[R] = db.run { action }

  def deleteById(id: Option[Long]): Unit =
    for { i <- id } run { tableQuery.filter(_.id === id).delete } // id is unknown because QueryType is wrong

  def findAll: Future[List[T]] = run { tableQuery.to[List].result } // also b0rked

  // Remaining methods shown on GitHub
}

仅供参考,以上内容的使用方式如下。首先,定义 table 查询的特征:

trait BankInfoTable extends BankTable { this: DBComponent =>
  import driver.api._

  class BankInfoTable(tag: Tag) extends Table[BankInfo](tag, "bankinfo") {
    val id       = column[Int]("id", O.PrimaryKey, O.AutoInc)
    val owner    = column[String]("owner")
    val bankId   = column[Int]("bank_id")
    val branches = column[Int]("branches")

    def bankFK = foreignKey("bank_product_fk", bankId, bankTableQuery)(_.id)

    def * = (owner, branches, bankId, id.?) <> (BankInfo.tupled, BankInfo.unapply)
  }

  val tableQuery = TableQuery[BankInfoTable]

  def autoInc = tableQuery returning tableQuery.map(_.id)
}

一切都在这里:

trait BankInfoRepositoryLike extends BankInfoTable with DbAction[BankInfo]
{ this: DBComponent =>

  import driver.api._

  @inline def updateAsync(bankInfo: BankInfo): Future[Int] =
    run { tableQuery.filter(_.id === bankInfo.id.get).update(bankInfo) }

  @inline def getByIdAsync(id: Int): Future[Option[BankInfo]] =
    run { tableQuery.filter(_.id === id).result.headOption }
}

建议?

您正在尝试使用 HasId 对结果类型进行抽象,但您的代码实际上并不关心这一点。您使用的 id 值来自 lifted 类型,即 table 行 class,因此您需要在这个级别:

trait LiftedHasId {
  def id: slick.lifted.Rep[Int]
}

然后在DbAction:

type QueryType <: slick.lifted.TableQuery[_ <: Table[T] with LiftedHasId]

并且BankInfoTable必须为其定义一个具体类型:

type QueryType = slick.lifted.TableQuery[BankInfoTable]

或者您可以将它作为第二个类型参数添加到 DbAction(就像 Query 有两个类型参数用于提升类型 结果类型)。

完整的工作示例:

 package com.knol.db.repo

import com.knol.db.connection.DBComponent
import com.knol.db.connection.MySqlDBComponent
import scala.concurrent.{Await, Future}
import concurrent.duration.Duration

trait LiftedHasId {
  def id: slick.lifted.Rep[Int]
}

trait HasId {
  def id: Option[Int]
}

trait GenericAction[T <: HasId]{this: DBComponent =>
  import driver.api._

  type QueryType <: slick.lifted.TableQuery[_ <: Table[T] with LiftedHasId]

  val tableQuery: QueryType

  @inline def deleteAsync(id: Int): Future[Int] = db.run { tableQuery.filter(_.id === id).delete }
  @inline def delete(id: Int): Int = Await.result(deleteAsync(id), Duration.Inf)

  @inline def deleteAllAsync(): Future[Int] = db.run { tableQuery.delete }
  @inline def deleteAll(): Int = Await.result(deleteAllAsync(), Duration.Inf)

  @inline def getAllAsync: Future[List[T]] = db.run { tableQuery.to[List].result }
  @inline def getAll: List[T] = Await.result(getAllAsync, Duration.Inf)

  @inline def getByIdAsync(id: Int): Future[Option[T]] =
    db.run { tableQuery.filter(_.id === id).result.headOption }

  @inline def getById(id: Int): Option[T] = Await.result(getByIdAsync(id), Duration.Inf)

  @inline def deleteById(id: Option[Int]): Unit =
    db.run { tableQuery.filter(_.id === id).delete }

  @inline def findAll: Future[List[T]] = db.run { tableQuery.to[List].result }




}
trait BankInfoRepository extends BankInfoTable  with GenericAction[BankInfo] { this: DBComponent =>

  import driver.api._

  type QueryType  = TableQuery[BankInfoTable]

  val tableQuery=bankInfoTableQuery

  def create(bankInfo: BankInfo): Future[Int] = db.run { bankTableInfoAutoInc += bankInfo }

  def update(bankInfo: BankInfo): Future[Int] = db.run { bankInfoTableQuery.filter(_.id === bankInfo.id.get).update(bankInfo) }

  /**
   * Get bank and info using foreign key relationship
   */
  def getBankWithInfo(): Future[List[(Bank, BankInfo)]] =
    db.run {
      (for {
        info <- bankInfoTableQuery
        bank <- info.bank
      } yield (bank, info)).to[List].result
    }

  /**
   * Get all bank and their info.It is possible some bank do not have their product
   */
  def getAllBankWithInfo(): Future[List[(Bank, Option[BankInfo])]] =
    db.run {
      bankTableQuery.joinLeft(bankInfoTableQuery).on(_.id === _.bankId).to[List].result
    }
}

private[repo] trait BankInfoTable extends BankTable{ this: DBComponent =>

  import driver.api._

  class BankInfoTable(tag: Tag) extends Table[BankInfo](tag, "bankinfo") with LiftedHasId {
    val id = column[Int]("id", O.PrimaryKey, O.AutoInc)
    val owner = column[String]("owner")
    val bankId = column[Int]("bank_id")
    val branches = column[Int]("branches")
    def bank = foreignKey("bank_product_fk", bankId, bankTableQuery)(_.id)
    def * = (owner, branches, bankId, id.?) <> (BankInfo.tupled, BankInfo.unapply)

  }

  protected val bankInfoTableQuery = TableQuery[BankInfoTable]

  protected def bankTableInfoAutoInc = bankInfoTableQuery returning bankInfoTableQuery.map(_.id)

}

object BankInfoRepository extends BankInfoRepository with MySqlDBComponent

case class BankInfo(owner: String, branches: Int, bankId: Int, id: Option[Int] = None) extends HasId