0

我正在尝试在现实案例中使用 Slick 的提升嵌入方法(体育俱乐部成员个人数据的自我管理)。我已经设法从数据库中检索信息并更新记录(将成员实现为案例类并使用案例类复制方法,如下所示),但我很难找到实现成员修改的最佳方法自然的方式。

我考虑了 2 种可能性:1)保持类实例的不变性并实现通用设置器(参见代码) 2)使构造函数参数为“var”(什么会抑制不变性,因此不理想)

坚持选项 1,我想出了以下代码(摘录,不是整个源代码):

    case class Member(id: Int, name: String, firstname: Option[String] = None,
      birthDate: Option[Date] = None, gender: Option[String] = None, country: Option[String] = None,
      healthNotes: Option[String]) {

      // Just a try so far
      def set(n: String = name, fn: Option[String] = firstname, bd : Option[Date] = birthDate)
        (implicit session: Session) = { 
        val m = this.copy(id,n,fn,bd)
        Members.update(m)
        m
      }
    }

    object Members extends Table[Member]("clubmembers") 
        with CayenneAutoPKSupport {

      def id = column[Int]("Member_ID", O.PrimaryKey) // This is the primary key column
      def name = column[String]("Name")
      def firstname = column[Option[String]]("Firstname")
      def birthDate = column[Option[Date]]("BirthDate")
      def gender = column[Option[String]]("Gender")
      def country = column[Option[String]]("Country")
      def healthNotes = column[Option[String]]("HealthNotes")

      // Every table needs a * projection with the same type as the table's type parameter
      def * = id ~ name ~ firstname ~ birthDate ~ gender ~ country ~ healthNotes <> (Member.apply _, Member.unapply _)
}

这可以按预期工作,但我希望 set 方法的命名参数具有相同的名称(这会使调用更“自然”)。我尝试了以下(无济于事)

def set( name: String = this.name, …

这不会编译,我可以想象为什么编译器不高兴(OTOH 当前的实现似乎可以工作)但我也可以想象它可以工作。无论如何:有人看到实现这一目标的方法吗?

或者,作为实现 Slick 持久对象的可修改类实例的最佳实践,人们会推荐什么?

提前感谢您的任何提示。

问候

4

2 回答 2

1

好吧,实际上,如果对参数和默认值使用相同的名称,原始代码就可以工作。只有 Scala IDE 中的语法高亮器似乎无法理解:虽然如果名称不同,默认值会(正确地)突出显示为类成员,但它们只是在名称相同的情况下显示为参数本身。

这是当前版本(按预期工作但未正确突出显示语法):

 def set(name: String = name, firstname: String = firstname, birthDate : Option[Date] = birthDate,
        gender: String = gender, country: String = country, 
        addressBlockId: Option[Int] = addressBlockId, healthNotes: String = healthNotes)
        (implicit session: Session) = { 
    val m = this.copy(id,name,Option(firstname),birthDate,
        Option(gender),Option(country),addressBlockId,Option(healthNotes))
    m
  }

注意:字符串参数最好也作为 Option[String] 传递。

欢迎提出意见和建议。

问候

于 2013-11-10T12:54:39.163 回答
0

如果这是您想要实现的目标,这将为我编译:

  def set(name: String = name, firstname: Option[String] = firstname, birthDate : Option[Date] = birthDate)(implicit session: Session) = {
    val m = Member(id, name, firstname, birthDate, gender, country, healthNotes)
    Members.update(m)
    m
  }
...
member.set(firstname = Some("name"))   

我不建议在您的案例类中执行数据库功能。我们通常将案例类用作简单的数据存储对象,并且只定义帮助管理该数据的函数。

成员的更新应该发生在您的成员 DAO 类中,可能最简单的解决方案是这样的:

object MemberDao {
  def updateMemberDetails(member: Member, name: String, firstname: Option[String], birthdate : Option[Date]) = {
    val updatedMember = member.copy(name = name, firstname = firstname, birthDate = birthdate)
    Members.update(updatedMember)
  }
}

处理可修改类的另一种解决方案是使用以下模式:

case class Member(id: Int, name: String, firstname: Option[String] = None, birthDate: Option[Date] = None, gender: Option[String] = None, country: Option[String] = None, healthNotes: Option[String]) {
  def updateName(n: String) = this.copy(name = n)
  def updateFirstName(fn: Option[String]) = this.copy(firstname = fn)
  def updateBirthDate(bd: Option[Date]) = this.copy(birthDate = bd)
}

最后,如果你想要一个保存函数,你可以通过隐式语法注入流畅的样式语法,它总是返回一个定义了保存函数的新成员。

case class Member(id: Int, name: String, firstname: Option[String] = None, birthDate: Option[Date] = None, gender: Option[String] = None, country: Option[String] = None, healthNotes: Option[String])

object Updatable {
  implicit class UpdatableMember(member: Member) {
    def updateName(n: String) = member.copy(name = n)
    def updateFirstName(fn: Option[String]) = member.copy(firstname = fn)
    def updateBirthDate(bd: Option[Date]) = member.copy(birthDate = bd)
    def save(implicit session: Session) = {
      ???
    }
  }
}

object MemberDao {
  def updateMember(member: Member) = {
    import Updatable._
    DB withSession { implicit session =>
      member.updateFirstName(Some("First Name")).updateBirthDate(Some(new Date(123456789))).save
    }
  }
}

希望这些选项对您有所帮助。如果我误解了您的要求,请发表评论!

更新

您不一定必须为每个成员使用一个更新方法,但您可能会在那里执行其他逻辑。如果您想要一个简单的解决方案,请像以前一样使用 Scala 的内置复制功能。

val member = MemberDao.getMember(123)
val updatedMember = member.copy(birthDate = Some(new Date(123456789)), name = "Updated name")
MemberDao.persist(updatedMember)

如果这对您不起作用,请解释原因。

我在一个本质上存储数据的类中有一个 save 方法的问题是它没有考虑Seperation of Concerns。此外,您的 set 函数具有副作用,可能会使其他开发人员感到困惑(并且功能不太强大)。如果您只想设置名字然后将其传递给另一个类来设置生日怎么办?你想做两个数据库事务吗?我猜不会。

这就是数据访问对象及其优势发挥作用的地方。您会将案例类的所有数据库操作放入一个类中,从而分离关注点:您的案例类保存数据并具有仅对数据进行操作的函数,并且您的 DAO 具有查询/持久化/更新/删除您的案例类。这也使测试您的应用程序更容易,因为模拟 DAO 比案例类的保存功能更简单。

很多人对 DAO 有不同的看法,这些陈述只是我的看法。

不使用 DAO

我建议为可更新的案例类定义一个特征:

trait DatabaseAccess[T] {
  def create(implicit session: Session): T
  def update(implicit session: Session): T
  def delete(implicit session: Session): Boolean
}

case class Member(id: Int, name: String, firstname: Option[String] = None, birthDate: Option[Date] = None, gender: Option[String] = None, country: Option[String] = None, healthNotes: Option[String]) extends DatabaseAccess[Member] {
  def create(implicit session: Session): Member = ???
  def delete(implicit session: Session): Boolean = ???
  def update(implicit session: Session): Member = ???
}

这些只是想法,我认为没有正确或错误的解决方案。选择一种对您有意义并为您提供必要的可用性/灵活性比的产品。

最佳实践

您询问了 Scala 最佳实践。您的问题更多是软件设计问题,因此阅读一般 OO 软件设计可以帮助您。Scala 的案例类只是编写 POJO 的一种更简单的方法。因此,在将任何新函数添加到案例类之前,请先问自己一个问题:“我会把它放在 POJO 中吗?”。如果是,那就继续吧:)

于 2013-11-09T23:18:45.843 回答