自定义映射到 Slick 中的嵌套案例 class 结构(超过 22 列)

Custom mapping to nested case class structure in Slick (more than 22 columns)

我正在尝试将超过 22 列的数据库行映射到案例 class 树。 我宁愿不使用 HList,因为我不想使用它 API,还有我在某处读到的一些指数编译时间反馈...

我已经阅读了 Stefan Zeiger 回答的这个帖子:How can I handle a > 22 column table with Slick using nested tuples or HLists?

我看过这个演示如何定义自定义映射函数的测试,我想这样做:

https://github.com/slick/slick/blob/2.1/slick-testkit/src/main/scala/com/typesafe/slick/testkit/tests/JdbcMapperTest.scala#L129-141

def * = (
        id,
        (p1i1, p1i2, p1i3, p1i4, p1i5, p1i6),
        (p2i1, p2i2, p2i3, p2i4, p2i5, p2i6),
        (p3i1, p3i2, p3i3, p3i4, p3i5, p3i6),
        (p4i1, p4i2, p4i3, p4i4, p4i5, p4i6)
      ).shaped <> ({ case (id, p1, p2, p3, p4) =>
        // We could do this without .shaped but then we'd have to write a type annotation for the parameters
        Whole(id, Part.tupled.apply(p1), Part.tupled.apply(p2), Part.tupled.apply(p3), Part.tupled.apply(p4))
      }, { w: Whole =>
        def f(p: Part) = Part.unapply(p).get
        Some((w.id, f(w.p1), f(w.p2), f(w.p3), f(w.p4)))
      })

问题是我做不到!

我试过更小的步骤。

class UserTable(tag: Tag) extends TableWithId[User](tag,"USER") {
  override def id = column[String]("id", O.PrimaryKey)
  def role = column[UserRole.Value]("role", O.NotNull)
  def login = column[String]("login", O.NotNull)
  def password = column[String]("password", O.NotNull)
  def firstName = column[String]("first_name", O.NotNull)
  def lastName = column[String]("last_name", O.NotNull)
  //
  def * = (id, role, login, password, firstName, lastName) <> (User.tupled,User.unapply)
  //
  def login_index = index("idx_user_login", login, unique = true)
}

好像我打电话的时候

(id, (firstName, lastName)).shaped

类型是ShapedValue[(Column[String], (Column[String], Column[String])), Nothing]

虽然这个似乎工作正常

(id, firstName, lastName).shaped

U型参数不是Nothing而是符合预期(String, String, String)

我不太了解 Slick 的所有内部结构是如何工作的。有人可以解释为什么我不能让我的代码工作吗?是否缺少导入或其他内容?

我想我需要得到一个

类型的值
ShapedValue[(Column[String], (Column[String], Column[String])), (String, (String, String))]

但我不知道为什么 returns 我 Nothing 也不太明白这些隐式 Shape 参数来自哪里...

我想要的只是能够轻松地将我的专栏分成 2 个案例 classes

谢谢

此外,22 列限制也有同样的问题,test case 很有帮助。不确定为什么示例代码不适合你,下面的代码对我来说很好,

case class UserRole(var role: String, var extra: String)
case class UserInfo(var login: String, var password: String, var firstName: String, var lastName: String)

case class User(id: Option[String], var info: UserInfo, var role: UserRole)

class UserTable(tag: Tag) extends Table[User](tag, "USER") {

  def id = column[String]("id", O.PrimaryKey)
  def role = column[String]("role", O.NotNull)
  def extra = column[String]("extra", O.NotNull)
  def login = column[String]("login", O.NotNull)
  def password = column[String]("password", O.NotNull)
  def firstName = column[String]("first_name", O.NotNull)
  def lastName = column[String]("last_name", O.NotNull)

  /** Projection */
  def * = (
    id,
    (login, password, firstName, lastName),
    (role, extra)
  ).shaped <> (

  { case (id, userInfo, userRole) =>
    User(Option[id], UserInfo.tupled.apply(userInfo), UserRole.tupled.apply(userRole))
  },
  { u: User =>
      def f1(p: UserInfo) = UserInfo.unapply(p).get
      def f2(p: UserRole) = UserRole.unapply(p).get
      Some((u.id.get, f1(u.info), f2(u.role)))
  })

  def login_index = index("id_user_login", login, unique = true)
}

正如 Izongren 所回答的那样,它工作正常,但是很难编写这样的代码,因为使用很长的元组很烦人...我决定这样做 "step by step" 并始终明确提供类型以避免类型推断问题,现在它工作正常。

case class Patient(
                    id: String = java.util.UUID.randomUUID().toString,
                    companyScopeId: String,
                    assignedToUserId: Option[String] = None,
                    info: PatientInfo
                    ) extends ModelWithId



case class PatientInfo(
                        firstName: Option[String] = None,
                        lastName: Option[String] = None,
                        gender: Option[Gender.Value] = None,
                        alias: Option[String] = None,
                        street: Option[String] = None,
                        city: Option[String] = None,
                        postalCode: Option[String] = None,
                        phone: Option[String] = None,
                        mobilePhone: Option[String] = None,
                        email: Option[String] = None,
                        age: Option[AgeRange.Value] = None,
                        companySeniority: Option[CompanySeniorityRange.Value] = None,
                        employmentContract: Option[EmploymentContract.Value] = None,
                        socialStatus: Option[SocialStatus.Value] = None,
                        jobTitle: Option[String] = None
                        )

class PatientTable(tag: Tag) extends TableWithId[Patient](tag,"PATIENT") {
  override def id = column[String]("id", O.PrimaryKey)
  def companyScopeId = column[String]("company_scope_id", O.NotNull)
  def assignedToUserId = column[Option[String]]("assigned_to_user_id", O.Nullable)

  def firstName = column[Option[String]]("first_name", O.Nullable)
  def lastName = column[Option[String]]("last_name", O.Nullable)
  def gender = column[Option[Gender.Value]]("gender", O.Nullable)
  def alias = column[Option[String]]("alias", O.Nullable)
  def street = column[Option[String]]("street", O.Nullable)
  def city = column[Option[String]]("city", O.Nullable)
  def postalCode = column[Option[String]]("postal_code", O.Nullable)
  def phone = column[Option[String]]("phone", O.Nullable)
  def mobilePhone = column[Option[String]]("mobile_phone", O.Nullable)
  def email = column[Option[String]]("email", O.Nullable)
  def age = column[Option[AgeRange.Value]]("age", O.Nullable)
  def companySeniority = column[Option[CompanySeniorityRange.Value]]("company_seniority", O.Nullable)
  def employmentContract = column[Option[EmploymentContract.Value]]("employment_contract", O.Nullable)
  def socialStatus = column[Option[SocialStatus.Value]]("social_status", O.Nullable)
  def jobTitle = column[Option[String]]("job_title", O.Nullable)
  def role = column[Option[String]]("role", O.Nullable)



  private type PatientInfoTupleType = (Option[String], Option[String], Option[Gender.Value], Option[String], Option[String], Option[String], Option[String], Option[String], Option[String], Option[String], Option[AgeRange.Value], Option[CompanySeniorityRange.Value], Option[EmploymentContract.Value], Option[SocialStatus.Value], Option[String])
  private type PatientTupleType = (String, String, Option[String], PatientInfoTupleType)
  //
  private val patientShapedValue = (id, companyScopeId, assignedToUserId,
    (
      firstName, lastName, gender, alias, street, city, postalCode,
      phone, mobilePhone,email, age, companySeniority, employmentContract, socialStatus, jobTitle
      )
    ).shaped[PatientTupleType]
  //
  private val toModel: PatientTupleType => Patient = { patientTuple =>
    Patient(
      id = patientTuple._1,
      companyScopeId = patientTuple._2,
      assignedToUserId = patientTuple._3,
      info = PatientInfo.tupled.apply(patientTuple._4)
    )
  }
  private val toTuple: Patient => Option[PatientTupleType] = { patient =>
    Some {
      (
        patient.id,
        patient.companyScopeId,
        patient.assignedToUserId,
        (PatientInfo.unapply(patient.info).get)
        )
    }
  }

  def * = patientShapedValue <> (toModel,toTuple)
}

另外,你也可以这样使用

class UserTable(tag: Tag) extends TableWithId[User](tag,"USER") {
  def id = column[String]("id", O.PrimaryKey)
  def role = column[String]("role", O.NotNull)
  def extra = column[String]("extra", O.NotNull)
  def login = column[String]("login", O.NotNull)
  def password = column[String]("password", O.NotNull)
  def firstName = column[String]("first_name", O.NotNull)
  def lastName = column[String]("last_name", O.NotNull)

  def loginMap = (login, password, firstName, lastName) <> (UserInfo.tupled, UserInfo.unapply)

  def roleMap = (role, extra) <> (Role.tupled, Role.unapply)

  override def * = (id, roleMap, loginMap) <> (User.tupled,User.unapply)

  def login_index = index("idx_user_login", login, unique = true)
}