该*
方法:
这将返回默认的投影 -这是您描述的方式:
“我通常感兴趣的所有列(或计算值)”。
您的表可以有几个字段;您只需要为默认投影设置一个子集。默认投影必须与表的类型参数匹配。
让我们一次来一个。没有这些<>
东西,只有*
:
// First take: Only the Table Defintion, no case class:
object Bars extends Table[(Int, String)]("bar") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def * = id ~ name // Note: Just a simple projection, not using .? etc
}
// Note that the case class 'Bar' is not to be found. This is
// an example without it (with only the table definition)
像这样的表定义将使您进行如下查询:
implicit val session: Session = // ... a db session obtained from somewhere
// A simple select-all:
val result = Query(Bars).list // result is a List[(Int, String)]
诸如此类的简单查询的(Int, String)
引线的默认投影List[(Int, String)]
。
// SELECT b.name, 1 FROM bars b WHERE b.id = 42;
val q =
for (b <- Bars if b.id === 42)
yield (b.name ~ 1)
// yield (b.name, 1) // this is also allowed:
// tuples are lifted to the equivalent projection.
什么是q
?它Query
与投影有关(String, Int)
。当被调用时,它返回一个List
的(String, Int)
元组按投影。
val result: List[(String, Int)] = q.list
在这种情况下,您已经yield
在for
理解子句中定义了所需的投影。
现在大约<>
和Bar.unapply
。
这提供了所谓的“ 映射投影”。
到目前为止,我们已经看到了如何使用slick在Scala中表达查询以返回列(或计算值)的投影;执行这些查询时,那么你必须考虑的结果行的查询作为Scala的元组。元组的类型将与定义的Projection相匹配(根据您
for
在上一个示例中的理解,由默认*
投影定义)。这就是为什么field1 ~ field2
返回的投影Projection2[A, B]
这里
A
是类型field1
和B
是的类型field2
。
q.list.map {
case (name, n) => // do something with name:String and n:Int
}
Queury(Bars).list.map {
case (id, name) => // do something with id:Int and name:String
}
我们正在处理元组,如果我们有太多的列,这可能会很麻烦。我们不想将结果视为TupleN
具有命名字段的对象。
(id ~ name) // A projection
// Assuming you have a Bar case class:
case class Bar(id: Int, name: String) // For now, using a plain Int instead
// of Option[Int] - for simplicity
(id ~ name <> (Bar, Bar.unapply _)) // A MAPPED projection
// Which lets you do:
Query(Bars).list.map ( b.name )
// instead of
// Query(Bars).list.map { case (_, name) => name }
// Note that I use list.map instead of mapResult just for explanation's sake.
这是如何运作的?<>
接受一个投影Projection2[Int, String]
并返回该类型的映射投影Bar
。这两个参数Bar, Bar.unapply _
说明了如何将此(Int, String)
投影映射到案例类。
这是双向映射。Bar
就是如此类的构造函数,所以这是从去所需要的信息(id: Int, name: String)
的Bar
。而且unapply
,如果你已经猜到了,是相反的。
哪里unapply
来的?这是任何普通案例类都可以使用的标准Scala方法-只需定义即可Bar
为您提供一个Bar.unapply
,它是一个提取器,可用于取回id
和name
,该
提取器是通过以下方式Bar
构建的:
val bar1 = Bar(1, "one")
// later
val Bar(id, name) = bar1 // id will be an Int bound to 1,
// name a String bound to "one"
// Or in pattern matching
val bars: List[Bar] = // gotten from somewhere
val barNames = bars.map {
case Bar(_, name) => name
}
val x = Bar.unapply(bar1) // x is an Option[(String, Int)]
因此,您的默认投影可以映射到您最希望使用的案例类:
object Bars extends Table[Bar]("bar") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def * = id ~ name <>(Bar, Bar.unapply _)
}
或者甚至可以按查询使用它:
case class Baz(name: String, num: Int)
// SELECT b.name, 1 FROM bars b WHERE b.id = 42;
val q1 =
for (b <- Bars if b.id === 42)
yield (b.name ~ 1 <> (Baz, Baz.unapply _))
在这里,类型为,q1
且投影到Query
的映射Baz
。当被调用时,它返回List
的Baz
对象:
val result: List[Baz] = q1.list
最后,顺便说一句,.?
提供了“ 选项提升 ”-Scala处理可能不是的值的方法。
(id ~ name) // Projection2[Int, String] // this is just for illustration
(id.? ~ name) // Projection2[Option[Int], String]
最后,它可以很好地与您的原始定义配合使用Bar
:
case class Bar(id: Option[Int] = None, name: String)
// SELECT b.id, b.name FROM bars b WHERE b.id = 42;
val q0 =
for (b <- Bars if b.id === 42)
yield (b.id.? ~ b.name <> (Bar, Bar.unapply _))
q0.list // returns a List[Bar]
针对有关Slick如何使用for
理解的评论:
不知何故,单子总是设法出现并要求成为解释的一部分。
对于理解,并不仅仅针对集合。它们可以在任何类型的Monad上使用,并且集合只是Scala中可用的多种monad类型之一。
但是,随着收藏的熟悉,它们为解释提供了良好的起点:
val ns = 1 to 100 toList; // Lists for familiarity
val result =
for { i <- ns if i*i % 2 == 0 }
yield (i*i)
// result is a List[Int], List(4, 16, 36, ...)
在Scala中,for comprehension是方法(可能是嵌套的)方法调用的语法糖:上面的代码(或多或少)等效于:
ns.filter(i => i*i % 2 == 0).map(i => i*i)
基本上,任何与filter
,map
,flatMap
的方法(换言之,一个单子)可以以用于
for
理解在适当位置的ns
。Option monad是一个很好的例子。这是前面的示例,其中相同的for
语句在monad List
和Option
monad 上均起作用
:
// (1)
val result =
for {
i <- ns // ns is a List monad
i2 <- Some(i*i) // Some(i*i) is Option
if i2 % 2 == 0 // filter
} yield i2
// Slightly more contrived example:
def evenSqr(n: Int) = { // return the square of a number
val sqr = n*n // only when the square is even
if (sqr % 2 == 0) Some (sqr)
else None
}
// (2)
result =
for {
i <- ns
i2 <- evenSqr(i) // i2 may/maynot be defined for i!
} yield i2
在最后一个示例中,转换可能看起来像这样:
// 1st example
val result =
ns.flatMap(i => Some(i*i)).filter(i2 => i2 %2 ==0)
// Or for the 2nd example
result =
ns.flatMap(i => evenSqr(i))
在油滑,查询是一元-它们与刚对象map
,flatMap
和filter
方法。因此,for
理解(显示在*
方法的说明中)仅转换为:
val q =
Query(Bars).filter(b => b.id === 42).map(b => b.name ~ 1)
// Type of q is Query[(String, Int)]
val r: List[(String, Int)] = q.list // Actually run the query
正如你所看到的,flatMap
,map
和filter
用于产生Query
通过反复改造Query(Bars)
与每个调用filter
和map
。对于集合,这些方法实际上会迭代和过滤集合,但是在Slick中,它们用于生成SQL。此处有更多详细信息:
Scala Slick如何将Scala代码转换为JDBC?