1

我在这里对我最近提出的另一个 Slick 问题(Slick table Query: Trouble with identify values)进行了跟进。请多多包涵!!我是数据库新手,Slick 在文档方面似乎特别差。无论如何,我有这张桌子:

object Users extends Table[(Int, String)]("Users") {

  def userId          = column[Int]("UserId", O.PrimaryKey, O.AutoInc)
  def userName        = column[String]("UserName")

  def * = userId ~ userName
}

第一部分

我正在尝试使用此功能进行查询:

def findByQuery(where: List[(String, String)]) = SlickInit.dbSlave withSession {    

  val q = for {
    x <- Users if foo((x.userId, x.userName), where)
           } yield x
      q.firstOption.map { case(userId, userName) =>
                    User(userId, userName)}
   }

其中“where”是搜索查询列表 //ex. ("userId", "1"),("userName", "Alex") "foo" 是一个测试相等性的辅助函数。我遇到了类型错误。
x.userId 的类型为 Column[Int]。如何将其作为 Int 进行操作?我试过铸造,例如:

foo(x.userId.asInstanceOf[Int]...)

但我也遇到了麻烦。如何处理 Slick 返回类型?

第二部分 有没有人熟悉强制转换功能:

def * = userId ~ userName <> (User, User.unapply _)

? 我知道这个问题有一些很好的答案,最值得注意的是:scala slick method I can't understand so far and a very similar question here: mapped projection with companion object in SLICK。但是任何人都可以解释为什么编译器会响应

<> method overloaded 

对于那一行简单的代码?

4

2 回答 2

6

让我们从问题开始:

val q = for {
    x <- Users if foo((x.userId, x.userName), where)
} yield x

请参阅,Slick 将 Scala 表达式转换为 SQL。为了能够根据需要将条件转换为 SQL 语句,Slick 需要使用一些特殊类型。这些类型的工作方式实际上是 Slick 执行的转换的一部分。

例如,当您编写List(1,2,3) filter { x => x == 2 }过滤器谓词时,将对列表中的每个元素执行。但是 Slick 做不到!所以 Query[ATable] filter { arow => arow.id === 2 } 实际上意味着“使用条件 id = 2 进行选择”(我在这里跳过细节)。

我为您的函数编写了一个模拟,foo并要求 Slick 为查询生成 SQL q

select x2."UserId", x2."UserName" from "Users" x2 where false

看到了false吗?那是因为fooScala 评估为 Boolean 的简单谓词false。在查询中完成的类似谓词,而不是列表,评估为对 SQL 生成中需要完成的内容的描述。filter比较List 和 Slick中 s 的区别:

List[A].filter(A => Boolean):List[A]
Query[E,U].filter[T](f: E => T)(implicit wt: CanBeQueryCondition[T]):Query[E,U]  

列表过滤器评估为 As 列表,而 Query.filter 评估为新查询!

现在,迈向解决方案的一步。

看来你要的其实是inSQL的操作符。如果in列表中有元素,则运算符返回 true,例如:4 in (1,2,3,4)为 true。请注意,这(1,2,3,4)是一个SQL list,而不是Scala 中的Tuple

对于inSQL 运算符的这个用例,Slick 使用运算符inSet

现在是问题的第二部分。(我将where变量重命名为list,因为where它是一个 Slick 方法)

你可以试试:

val q = for {
  x <- Users if (x.userId,x.userName) inSet list
} yield x

但这不会编译!那是因为 SQL 不像 Scala 那样拥有元组。在 SQL 中你不能这样做(1,"Alfred") in ((1,"Alfred"),(2, "Mary"))(记住,这(x,y,z)是列表的 SQL 语法,我在这里滥用语法只是为了表明它是无效的——还有很多 SQL 方言,其中一些可能确实支持元组和列表以类似的方式。)

一种可能的解决方案是仅使用 userId 字段:

val q = for {
  x <- Users if x.userId inSet list2
} yield x

这会产生select x2."UserId", x2."UserName" from "Users" x2 where x2."UserId" in (1, 2, 3)

但由于您明确使用用户 ID 和用户名,因此可以合理地假设用户 ID 不能唯一标识用户。因此,要修改我们可以连接两个值。当然,我们需要在列表中做同样的事情。

val list2 = list map { t => t._1 + t._2 }
val q2 = for {
  x <- Users if (x.userId.asColumnOf[String] ++ x.userName) inSet list2
} yield x

查看生成的 SQL:

select x2."UserId", x2."UserName" from "Users" x2 
where (cast(x2."UserId" as VARCHAR)||x2."UserName") in ('1a', '3c', '2b')

看到上面了||吗?它是 H2Db 中使用的字符串连接运算符。H2Db 是我用来运行您的示例的 Slick 驱动程序。根据您使用的数据库,此查询和其他查询可能会略有不同。

希望它能阐明 slick 的工作原理并解决您的问题。至少是第一个。:)

于 2013-07-16T00:37:16.603 回答
1

第一部分:

Slick 使用 Column[...] 类型而不是普通的 Scala 类型。您还需要使用 Column 类型定义 Slick 辅​​助函数。你可以像这样实现 foo :

def foo( columns: (Column[Int],Column[String]), values: List[(Int,String)] ) : Column[Boolean] = values.map( value => columns._1 === value._1 && columns._2 === value._2 ).reduce( _ || _ )

另请阅读 pedrofurla 的回答,以更好地了解 Slick 的工作原理。

第二部分:

方法 <> 确实是重载的,当类型不正确时,Scala 编译器很容易不确定它应该使用哪个重载。(我认为我们应该摆脱 Slick 中的重载。)

def * = userId ~ userName <> (User.tupled _, User.unapply _)

可能会稍微改善您收到的错误消息。要解决此问题,请确保 userId 和 userName 的 Column 类型与您的 User case 类的成员类型完全对应,应该类似于case class User( id:Int, name:String ). 还要确保在映射到 User 时扩展 Table[User](而不是 Table[(Int,String)])。

于 2013-07-16T11:39:43.623 回答