【问题标题】:Dynamic order by in scala slick with several columns在具有几列的scala slick中动态排序
【发布时间】:2017-07-12 01:13:22
【问题描述】:

我一直在学习 scala、playframework 和 slick,但是我发现了一个问题。 我正在尝试制作一个简单的 CRUD,其中包含一个接收自定义过滤器字段的列表控制器、一些分页信息(页面大小和数量)以及带有字段名称和顺序(asc 或 desc)的字符串元组的 Seq,以及所有内容工作正常,除了按 seq 排序,我不能按动态排序。

我从Scadiddle blog 获得了基本结构。 所以,基本代码如下:

我有我的基本颜色模型:

case class Color(
  id: Int,
  name: String)

这是一个简单的表定义:

    class ColorsTable(tag: Tag) extends Table[Color](tag, "color") {
        def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
        def name = column[String]("name")
        def * = (id, name) <> ((Color.apply _).tupled, Color.unapply)
      }

在我的仓库中,我有搜索方法:

def findAll(searchTerm: Option[String], page: Int, top: Int, sortsBy: Seq[(String, SortDirection)]): Future[Seq[Color]] = {
    var query = searchTerm match {
      case Some(term) => colors.filter(_.name like s"%$term%")
      case _ => colors
    }
    val offset = (page - 1) * top
    // This is building the sort clause, matching on each column in the sort
    sortsBy.foreach {
      sortTuple =>
        val (sortColumn, sortDirection) = sortTuple
        query = query.sortBy(sortColumn match {
          case "id" => if (sortDirection == Desc) _.id.desc else _.id.asc
          case _ => if (sortDirection == Desc) _.name.desc else _.name.asc
        })
    }
    // The "list" method actually executes the query
    val colorsQuery = query.drop(offset).take(top).result

    db.run(colorsQuery)
  }

问题是,当我用这个序列调用搜索方法时:

val sortsBy = Seq[(String, SortDirection)](("name", Desc), ("id", Asc))
    colorService.getColors(None, 1, 10, sortsBy).map(colorList => Ok(Json.toJson(colorList)))

已生成此查询:

select "id", "name" from "color" order by "id", "name" desc limit 10 offset 0

如您所见,sortBy 的顺序颠倒了(id 然后是 name,而不是 name 和 id 作为顺序)。

如果我使用元组而不是 foreach,则遵守顺序:

query = query.sortBy(
      s => (s.name.desc, s.id.asc)
    )

但是没有办法生成 dynamic-size 元组。为了增加一些混乱,另一个给我带来麻烦的事情是光滑文档中的this part:

请注意,包含多个列的单个 ORDER BY 不是 相当于多个 .sortBy 调用,但相当于一个 .sortBy 调用 传递一个元组

那么,实际上我可以使用 foreach 并连接订单吗?还是因为这个限制,顺序被颠倒了?

如果只有元组可以用于 sortBy,我如何实现动态大小的 order by?

PD:感谢您的关注,抱歉英语不好

编辑:

感谢您的快速响应,我尝试了您的代码并且看起来不错,遗憾的是我几乎不知道它是如何工作的:((scala 是一种非常好的语言,但很难学习:S)。

当我看到Higher Kinded Type Should be Enabled 时,我吓坏了,寻找答案this 并没有给我带来简单理解的希望,希望当我完成《Scala 编程》第三版时,我会有更多的理解和知道到底发生了什么。

再问一个问题,这是否等同于进行多次 sortBy 调用?这与使用元组相比如何?我仍然对光滑文档的这一部分感到困惑:

具有多个列的单个 ORDER BY 不等同于多个 .sortBy 调用,而是一个传递元组的单个 .sortBy 调用

我检查了我的方法,并通过在 seq 中添加反向使其工作正常,当然不像 功能性 和你的代码一样好,所以我将使用你的建议并努力解决使用助手制作其余的过滤器并避免使用变量。 (是的,在另一部分我仍在使用 var,但当我了解更多 Scala 时,我会做得更好。

自白:在使用多种语言(从 JavaScript 到 Java、C#、Python 等)编程超过 8 年之后,我不得不重复一遍,Scala 看起来是一门漂亮但非常复杂的语言,但我不会放弃学习它

【问题讨论】:

    标签: scala slick


    【解决方案1】:

    让我们定义DynamicSortBySupport helper

    object DynamicSortBySupport {
      import slick.ast.Ordering.Direction
      import slick.ast.Ordering
      import slick.lifted.Query
      import slick.lifted.ColumnOrdered
      import slick.lifted.Ordered
      type ColumnOrdering = (String, Direction) //Just a type alias
      trait ColumnSelector {
        val select: Map[String, Rep[_]] //The runtime map between string names and table columns 
      }
      implicit class MultiSortableQuery[A <: ColumnSelector, B, C[_]](query: Query[A, B, C]) {
        def dynamicSortBy(sortBy: Seq[ColumnOrdering]): Query[A, B, C]  =
          sortBy.foldRight(query){ //Fold right is reversing order
            case ((sortColumn, sortOrder), queryToSort) =>
              val sortOrderRep: Rep[_] => Ordered = ColumnOrdered(_, Ordering(sortOrder))
              val sortColumnRep: A => Rep[_] = _.select(sortColumn)
              queryToSort.sortBy(sortColumnRep)(sortOrderRep)
          }
      }
    }
    

    并重新定义您的Table 添加“排序图”

    class ColorsTable(tag: Tag) extends Table[Color](tag, "color") with DynamicSortBySupport.ColumnSelector {
      def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
      def name = column[String]("name")
      def * = (id, name) <> ((Color.apply _).tupled, Color.unapply)
      val select = Map(
        "id" -> (this.id),
        "name" -> (this.name)
      )
    }
    

    最后在你的代码中使用整个东西:

    object FindAll extends App {
      import DynamicSortBySupport._
      import slick.ast.Ordering.Direction
      import slick.ast.Ordering
      object colors extends TableQuery(new ColorsTable(_))
      val sortsBy = Seq[(String, Direction)](("name", Ordering.Desc), ("id", Ordering.Asc)) //Replaced 
      val db = Database.forURL("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver="org.h2.Driver") //Just for testing
      findAll(sortsBy)
      def findAll(sortsBy: Seq[(String, Direction)]): Future[Seq[Color]] = {
        val query = colors.dynamicSortBy(sortsBy).result
        db.run(query)
      }
    }
    

    笔记和cmets:

    • 字符串名称和表列之间的运行时映射Map[String, Rep[_]] 可以通过错误处理(现在它只是引发必须正确管理的运行时异常)或从表定义本身自动派生来改进;
    • 我已将SortDirection 替换为正确的slick.ast.Ordering.Direction,请随意编写转换器;
    • 您还可以为可选过滤器编写帮助程序,例如 filterOption;
    • 注意使用foldRight 来反转排序顺序;
    • 如果可能,请避免使用 vars 并保持正常运行 :-)
    • 如果您想让ColumnSelector 出现在数据层之外(实际上这是一个很好的做法),您可以重写它,隐含地要求类似ColumnSelector[T];

    【讨论】:

    • 我刚刚尝试了你的代码并且工作正常,我编辑了我的问题以添加一些观察,因为评论部分非常有限
    • 如何从表定义中自动派生映射Map[String, Rep[_]]?
    • 请注意,这对于单表多排序非常有效,但如果加入等式,排序逻辑会在 Slick 查询中丢失。
    • 有多个表join且排序列属于多个表的情况下动态多列排序有什么解决方案吗??
    猜你喜欢
    • 1970-01-01
    • 2017-05-07
    • 2014-08-02
    • 1970-01-01
    • 2016-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多