我有以下简化代码来反映我面临的问题。它使用了sclick 3.1.1,scala 2.10.4和mysql。
我有一个User表,其中一列有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,它可以正常工作;遗憾的是,我的数据模型需要一个是可选的,另一个是必需的。 关于这里发生了什么的任何想法?谢谢!
答案 0 :(得分:1)
看起来真正重要的不是有两个映射列,而是除了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
}