Scala 使用slick 2加载相关实体的策略

Scala 使用slick 2加载相关实体的策略,scala,playframework-2.0,slick-2.0,Scala,Playframework 2.0,Slick 2.0,我正在使用play 2.3和slick 2.1 我有两个相关的实体-消息和用户(一个简化的示例域)。消息由用户编写。 表达这种关系的方法是在消息中使用显式userId 我的类和表映射如下所示: case class Message ( text: String, userId: Int, date: Timestamp = new Timestamp(new Date().getTime()), id: Option[Int] = None) {} case c

我正在使用play 2.3和slick 2.1

我有两个相关的实体-
消息
用户
(一个简化的示例域)。消息由用户编写。 表达这种关系的方法是在
消息中使用显式
userId

我的类和表映射如下所示:

case class Message (
    text: String,
    userId: Int,
    date: Timestamp = new Timestamp(new Date().getTime()),
    id: Option[Int] = None) {}

case class User (
    userName: String,
    displayName: String,
    passHash: String,
    creationDate: Timestamp = new Timestamp(new Date().getTime()),
    lastVisitDate: Option[Timestamp] = None,
    // etc
    id: Option[Int] = None){}

class MessageTable(tag: Tag) extends Table[Message](tag, "messages") {
    def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
    def text = column[String]("text")
    def userId = column[Int]("user_id")
    def date = column[Timestamp]("creation_date")

    def * = (title, text, userId, date, id.?) <> (Post.tupled, Post.unapply 
    def author = foreignKey("message_user_fk", userId, TableQuery[UserTable])(_.id)
}

class UserTable(tag: Tag) extends Table[User](tag, "USER") {
    def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
    def username = column[String]("username")
    def passHash = column[String]("password")
    def displayname = column[String]("display_name")

    def * = (username, passHash,created, lastvisit, ..., id.?) <> (User.tupled, User.unapply)
}
val messageList = (
  for (
    u <- db.users;
    m <- db.messages if u.id === m.userId
  ) yield (u.displayName, m.date, m.text))
  .sortBy({ case (name, date, text) => date })
  .drop(n)
  .take(amount)
  .list()
@(messagesAndAuthors: List[(User, Message)])
现在,这在内部是完美的,但是如果我想显示实际的消息,我希望消息类在模板中使用时能够返回它的作者名称

以下是我的考虑:

  • 我不想(无论如何我也不能)在它不属于的地方传递隐式的slick
    Session
    ,比如模板引擎和模型类
  • 在这种特殊情况下,我希望避免逐个请求消息的附加数据
  • 我对Hibernate比对Slick更熟悉;在Hibernate中,我会使用join抓取。有了Slick,我想到的最好办法是使用另一个data holder类进行显示:

    class LoadedMessage (user:User, message:Message) {
        // getters
        def text = message.text
        def date = message.date
        def author = user.displayName
    }
    object LoadedMessage {
        def apply( u:User , m:Message ) = new LoadedMessage(u, m)
    }
    
    并用联接查询的结果填充它:

    val messageList: List[LoadedMessage] = (
        for (
            u <- db.users;
            m <- db.messages if u.id === m.userId
        ) yield (u, m))
        .sortBy({ case (u, m) => m.date })
        .drop(n)
        .take(amount)
        .list.map { case (u, m) => LoadedMessage(u, m) }
    
    val messageList:List[LoadedMessage]=(
    为了(
    u LoadedMessage(u,m)}
    
    然后把它传递到任何地方。我关心的是这个额外的类-不是很枯燥,所以不必要的转换(而且似乎我不能使它隐式),很多样板

    常用的方法是什么? 有没有办法减少额外的类并使模型能够返回其关联性?

    以下是我的评论:

    如何处理连接结果在我看来是个人喜好的问题,我也会使用您的方法,您有一个特殊的数据结构,它封装了您的数据,并且可以轻松地传递(例如在视图中)和访问

    我想到的另外两种方法是

  • 查询字段而不是对象,它不太清晰,我通常不喜欢使用元组(在本例中是三元组),因为我发现符号
    。\u 1
    MyClass.myField
    更不清晰。这意味着这样做:

    case class Message (
        text: String,
        userId: Int,
        date: Timestamp = new Timestamp(new Date().getTime()),
        id: Option[Int] = None) {}
    
    case class User (
        userName: String,
        displayName: String,
        passHash: String,
        creationDate: Timestamp = new Timestamp(new Date().getTime()),
        lastVisitDate: Option[Timestamp] = None,
        // etc
        id: Option[Int] = None){}
    
    class MessageTable(tag: Tag) extends Table[Message](tag, "messages") {
        def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
        def text = column[String]("text")
        def userId = column[Int]("user_id")
        def date = column[Timestamp]("creation_date")
    
        def * = (title, text, userId, date, id.?) <> (Post.tupled, Post.unapply 
        def author = foreignKey("message_user_fk", userId, TableQuery[UserTable])(_.id)
    }
    
    class UserTable(tag: Tag) extends Table[User](tag, "USER") {
        def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
        def username = column[String]("username")
        def passHash = column[String]("password")
        def displayname = column[String]("display_name")
    
        def * = (username, passHash,created, lastvisit, ..., id.?) <> (User.tupled, User.unapply)
    }
    
    val messageList = (
      for (
        u <- db.users;
        m <- db.messages if u.id === m.userId
      ) yield (u.displayName, m.date, m.text))
      .sortBy({ case (name, date, text) => date })
      .drop(n)
      .take(amount)
      .list()
    
    @(messagesAndAuthors: List[(User, Message)])
    
    在这里,您可以在视图中传递如下内容:

    case class Message (
        text: String,
        userId: Int,
        date: Timestamp = new Timestamp(new Date().getTime()),
        id: Option[Int] = None) {}
    
    case class User (
        userName: String,
        displayName: String,
        passHash: String,
        creationDate: Timestamp = new Timestamp(new Date().getTime()),
        lastVisitDate: Option[Timestamp] = None,
        // etc
        id: Option[Int] = None){}
    
    class MessageTable(tag: Tag) extends Table[Message](tag, "messages") {
        def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
        def text = column[String]("text")
        def userId = column[Int]("user_id")
        def date = column[Timestamp]("creation_date")
    
        def * = (title, text, userId, date, id.?) <> (Post.tupled, Post.unapply 
        def author = foreignKey("message_user_fk", userId, TableQuery[UserTable])(_.id)
    }
    
    class UserTable(tag: Tag) extends Table[User](tag, "USER") {
        def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
        def username = column[String]("username")
        def passHash = column[String]("password")
        def displayname = column[String]("display_name")
    
        def * = (username, passHash,created, lastvisit, ..., id.?) <> (User.tupled, User.unapply)
    }
    
    val messageList = (
      for (
        u <- db.users;
        m <- db.messages if u.id === m.userId
      ) yield (u.displayName, m.date, m.text))
      .sortBy({ case (name, date, text) => date })
      .drop(n)
      .take(amount)
      .list()
    
    @(messagesAndAuthors: List[(User, Message)])
    
    然后使用元组和类访问功能访问数据,但您的模板仍然是
    \u 1
    的集合,而且读取起来很糟糕,而且您必须执行类似
    消息和作者的操作。_1.name
    仅获取一个值

  • 最后,我更喜欢在我的观点中尽可能干净地传递变量(我猜这是计算机科学中为数不多的普遍接受的原则之一)隐藏模型中使用的逻辑,因为在我看来,使用一个特别的case类是一个很好的方法。case类是一个非常强大的工具,拥有它很好,对你来说,它可能看起来不枯燥,比其他方法更冗长(比如你提到的
    Hibernate
    )但是想想看,当一个开发人员阅读你的代码时,这将是最简单的,因为它可以在这一刻得到,额外的小类将节省几个小时的头痛

    对于
    会话
    问题,目前无法避免传递它(据我所知),但您应该能够控制这种情况并避免在模板中传递会话,我通常在我的入口点(大部分时间是控制器)创建一个会话并将其传递给模型


    注意:代码未经测试,可能存在一些错误。

    仅需考虑一点,
    Hibernate
    是一个ORM,而
    Slick
    是一个允许您编写类型安全查询的层,您不能期望相同的功能,在这种情况下,您尤其不能期望Slick为您获取对象,除非您在q中指定uery使用联接。如果您更喜欢ORM,请看一看示例。我并不讨厌编写查询或联接表。我更不知道如何用高效的方式表示结果。谢谢技巧,我也会看一看Squeryl。我只是开始玩Play,几乎整个堆栈都不熟悉。我使用Play slick来o、 因此,控制器中的隐式
    会话
    并没有太大问题。我最初的尝试是实现延迟加载关联,但我在视图中传递隐式
    会话
    方面并不是很成功。现在回想起来,这是一个可怕的想法。我明白,使用play和slick,在将其传递给用户之前,必须加载所有内容关于你的第二种方法,我还可以考虑用author(u:User,m:Message){}创建一个简单的
    case类MessageWithAuthor(u:User,m:Message){}
    虽然代码较少,但访问它却不那么漂亮。虽然仍然比元组好,但我担心它可能会以地狱般的
    MessageWithAuthor
    MessageWithResponses
    MessageWithParent
    等结尾。或者可能会使用相关的
    选项制作
    messagewithallrelatedstup
    ,但恩,我会丢失编译时检查我是否实际加载了我使用的东西。您可以使用一个封装
    User
    Message
    的case类,但这不会比您以前的方法更枯燥,相反,访问会更冗长(类似于
    MessageWithAuthor.u.name
    ).如果你真的担心在每种情况下都会有一个case类,那么你应该使用元组,如果你需要3或4个case类来完成这项工作,那么最终这是一个折衷,这就是我要做的,对我来说,易读性比干燥性(到一定长度)更重要,如果你的用例有所有可能的组合,那么元组就是最好的选择。我用元组做了尝试;这并不坏,因为我可以在模板中分解它们,以便于理解和匹配。最后,将元组用于奇怪的组合,将类用于普通组合是最有意义的。