在Scala Slick中实现类实例的成员修改的最佳方法?

时间:2013-11-09 17:25:17

标签: scala mutable slick

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

我考虑过两种可能性: 1)维护类实例的不变性并实现通用的setter(参见代码) 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持久化对象实现可修改类实例的最佳实践,会建议什么?

提前感谢任何提示。

此致

2 个答案:

答案 0 :(得分: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]传递。

欢迎提出意见和建议。

此致

答案 1 :(得分: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"))   

我不建议在你的case类中做数据库函数。我们一般用例 类作为存储对象的简单数据,只定义有助于管理数据的函数。

成员的更新应该在您的成员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。此外,您的设置功能也有副作用,这可能会使其他开发人员感到困惑(并且功能不太强)。如果您只想设置名字然后将其传递到另一个类来设置生日,该怎么办?你想做两个数据库事务吗?我猜不会。

这就是Data Access Object and its advantages发挥作用的地方。您可以将案例类的所有数据库操作放在一个类中,从而分离关注点:您的案例类包含数据并且具有仅对数据执行操作的函数,并且您的DAO具有查询/持久化/更新/删除您的案例类。这也使得测试应用程序变得更容易,因为模拟DAO比使用案例类的保存功能更简单。

很多人have differing opinions on DAO's,这些陈述只是我的意见。

不使用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中吗?”。如果是,那么继续:)