具有多个 MappedColumnType 的平滑投影

Slick projection with multiple MappedColumnType

我有以下简化代码来反映我面临的问题。它使用 slick 3.1.1,scala 2.10.4 和 mysql.

我有一个用户 table,其中一列有 Option[Seq[String]],另一列是 Seq[String]。

有2个MappedColumnType;这会将 Seq[String] 转换为 String,并将 Option[Seq[String]] 转换为 String

下面是简化的代码:

package models

import slick.driver.MySQLDriver.api._

case class User(id: Long,
                name: Option[String],
                cities: Option[Seq[String]] = None,
                countries: Seq[String])

class UserMapping(tag: Tag) extends Table[User](tag, "USERS") {

  implicit val stringListMapper = MappedColumnType.base[Seq[String], String](
    list => list.mkString(","),
    string => string.split(',').toSeq
  )

  implicit val stringOptionalListMapper = MappedColumnType.base[Option[Seq[String]], String](
    list => list.get.mkString(","),
    string => Some(string.split(',').toSeq)
  )

  def id: Rep[Long] = column[Long]("ID", O.PrimaryKey, O.AutoInc)
  def name: Rep[Option[String]] = column[Option[String]]("NAME")
  def cities: Rep[Option[Seq[String]]] = column[Option[Seq[String]]]("CITIES")(stringOptionalListMapper)
  def countries: Rep[Seq[String]] = column[Seq[String]]("COUNTRIES")(stringListMapper)

  // scalastyle:off method.name public.methods.have.type
  def * = (id, name, cities, countries) <> (User.tupled, User.unapply)
  // scalastyle:on method.name public.methods.have.type

}

编译器在投影时失败:

[error] Slick does not know how to map the given types.
[error] Possible causes: T in Table[T] does not match your *     projection. Or you use an unsupported type in a Query (e.g. scala List).
[error]   Required level: slick.lifted.FlatShapeLevel
[error]      Source type: (slick.lifted.Rep[Long], slick.lifted.Rep[Option[String]], slick.lifted.Rep[Option[Seq[String]]], slick.lifted.Rep[Seq[String]])
[error]    Unpacked type: (Long, Option[String], Option[Seq[String]], Seq[String])
[error]      Packed type: Any
[error]   def * = (id, name, cities, countries) <> (User.tupled, User.unapply)
[error]                                         ^
[error] one error found
[error] (compile:compileIncremental) Compilation failed
[error] Total time: 2 s, completed Jan 23, 2018 3:24:23 PM

如果我在这里只定义一个MappedColumnType,它工作正常;不幸的是,我的数据模型需要一个是可选的,另一个是必需的。 对这里发生的事情有什么想法吗?谢谢!

看起来真正重要的不是有两个映射列,而是它们除了 Option 之外具有相同的形状。这很糟糕,因为它会让您引入两个 implicit val 用于映射,这会使它们在将 (id, name, cities, countries) 转换为 ProvenShape

时变得模棱两可

如果这种形状的逻辑实际上与您的示例中的逻辑相同,那么 Slick 似乎能够自行添加 Option 包装器,因此您只需要一个(非 Option) 隐式如:

class UserMapping(tag: Tag) extends Table[User](tag, "USERS") {

  implicit val stringListMapper = MappedColumnType.base[Seq[String], String](
    list => list.mkString(","),
    string => string.split(',').toSeq
  )

  def id: Rep[Long] = column[Long]("ID", O.PrimaryKey, O.AutoInc)

  def name: Rep[Option[String]] = column[Option[String]]("NAME")

  def cities: Rep[Option[Seq[String]]] = column[Option[Seq[String]]]("CITIES")   // share stringListMapper

  def countries: Rep[Seq[String]] = column[Seq[String]]("COUNTRIES")        // share stringListMapper

  // scalastyle:off method.name public.methods.have.type
  def * = (id, name, cities, countries) <> (User.tupled, User.unapply)
  // scalastyle:on method.name public.methods.have.type

}

然而,如果你不走运并且相同形状的映射实际上是不同的,那么你需要将它们显式地传递给 column(例如,如果分隔符不同),那么你将不得不显式地提供 implicit 正确 Shape 的证据,例如:

class UserMapping(tag: Tag) extends Table[User](tag, "USERS") {

  val stringListMapper = MappedColumnType.base[Seq[String], String](
    list => list.mkString(","),
    string => string.split(',').toSeq
  )

  val stringOptionalListMapper = MappedColumnType.base[Option[Seq[String]], String](
    list => list.get.mkString(","),
    string => Some(string.split(',').toSeq)
  )

  def id: Rep[Long] = column[Long]("ID", O.PrimaryKey, O.AutoInc)

  def name: Rep[Option[String]] = column[Option[String]]("NAME")

  def cities: Rep[Option[Seq[String]]] = column[Option[Seq[String]]]("CITIES")(stringOptionalListMapper)

  def countries: Rep[Seq[String]] = column[Seq[String]]("COUNTRIES")(stringListMapper)


  // explicitly provide proper Shape evidence
  import slick.lifted.Shape
  implicit val shape = Shape.tuple4Shape(
    Shape.repColumnShape(longColumnType),
    Shape.optionShape(Shape.repColumnShape(stringColumnType)),
    Shape.repColumnShape(stringOptionalListMapper),
    Shape.repColumnShape(stringListMapper))

  // scalastyle:off method.name public.methods.have.type
  def * = (id, name, cities, countries) <> (User.tupled, User.unapply)
  // scalastyle:on method.name public.methods.have.type

}