Die *
Methode:
Dies gibt die Standardprojektion zurück - so beschreiben Sie:
"Alle Spalten (oder berechneten Werte), an denen ich normalerweise interessiert bin ".
Ihre Tabelle kann mehrere Felder enthalten. Sie benötigen nur eine Teilmenge für Ihre Standardprojektion. Die Standardprojektion muss mit den Typparametern der Tabelle übereinstimmen.
Nehmen wir es einzeln. Ohne das <>
Zeug nur die *
:
// 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)
Mit einer solchen Tabellendefinition können Sie folgende Abfragen durchführen:
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)]
Die Standardprojektion von (Int, String)
führt zu a List[(Int, String)]
für einfache Abfragen wie diese.
// 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.
Was ist die Art von q
? Es ist ein Query
mit der Projektion (String, Int)
. Wenn es aufgerufen wird , gibt es eine List
von (String, Int)
Tupeln gemäß der Projektion.
val result: List[(String, Int)] = q.list
In diesem Fall haben Sie die gewünschte Projektion in der yield
Klausel des for
Verständnisses definiert.
Nun zu <>
und Bar.unapply
.
Dies liefert sogenannte zugeordnete Projektionen .
Bisher haben wir gesehen, wie Sie mit Slick Abfragen in Scala ausdrücken können, die eine Projektion von Spalten (oder berechneten Werten) zurückgeben. Wenn Sie diese Abfragen ausführen , müssen Sie sich die Ergebniszeile einer Abfrage als Scala-Tupel vorstellen . Der Typ des Tupels entspricht der definierten Projektion (nach Ihrem
for
Verständnis wie im vorherigen Beispiel der Standardprojektion *
). Aus diesem Grund wird field1 ~ field2
eine Projektion zurückgegeben, bei der angegeben wird, Projection2[A, B]
wo A
sich der Typ befindet field1
und wo
sich der Typ B
befindet 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
}
Wir haben es mit Tupeln zu tun, was umständlich sein kann, wenn wir zu viele Spalten haben. Wir möchten Ergebnisse nicht als TupleN
ein Objekt mit benannten Feldern betrachten.
(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.
Wie funktioniert das? <>
Nimmt eine Projektion Projection2[Int, String]
und gibt eine zugeordnete Projektion für den Typ zurück Bar
. Die beiden Argumente zeigen Bar, Bar.unapply _
, wie diese (Int, String)
Projektion einer Fallklasse zugeordnet werden muss.
Dies ist eine bidirektionale Zuordnung. Bar
ist der Fallklassenkonstruktor, das sind also die Informationen, die benötigt werden, um von (id: Int, name: String)
zu a zu gelangen Bar
. Und unapply
wenn Sie es erraten haben, ist für das Gegenteil.
Woher kommt unapply
das? Dies ist eine Standard-Scala-Methode, die für jede gewöhnliche Fallklasse verfügbar ist. WennBar
Sie nur definieren, erhalten Sie einen Extraktor , mit dem Sie das zurückerhalten können und mit dem das erstellt
wurde: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)]
So kann Ihre Standardprojektion der Fallklasse zugeordnet werden, die Sie am meisten erwarten:
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 _)
}
Oder Sie können es sogar pro Abfrage haben:
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 _))
Hier ist der Typ q1
ein Query
mit einer abgebildeter Projektion auf Baz
. Beim Aufrufen wird eines List
der Baz
Objekte zurückgegeben:
val result: List[Baz] = q1.list
Abgesehen davon .?
bietet das Angebot Option Lifting - die Scala-Methode für den Umgang mit Werten, die möglicherweise nicht vorhanden sind.
(id ~ name) // Projection2[Int, String] // this is just for illustration
(id.? ~ name) // Projection2[Option[Int], String]
Was zusammenfassend gut zu Ihrer ursprünglichen Definition von 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]
Als Antwort auf den Kommentar, wie Slick for
Verständnis verwendet:
Irgendwie schaffen es Monaden immer, aufzutauchen und zu verlangen, Teil der Erklärung zu sein ...
Zum Verständnis sind nicht nur Sammlungen spezifisch. Sie können für jede Art von Monade verwendet werden , und Sammlungen sind nur eine der vielen Arten von Monadentypen, die in Scala verfügbar sind.
Da Sammlungen jedoch bekannt sind, bieten sie einen guten Ausgangspunkt für eine Erklärung:
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, ...)
In Scala ist a zum Verständnis syntaktischer Zucker für Methodenaufrufe (möglicherweise verschachtelt): Der obige Code entspricht (mehr oder weniger):
ns.filter(i => i*i % 2 == 0).map(i => i*i)
Im Grunde genommen alles mit filter
, map
, flatMap
Methoden (in anderen Worten, eine Monade ) in einen verwendet wird
for
Verständnis anstelle von ns
. Ein gutes Beispiel ist die Optionsmonade . Hier ist das vorherige Beispiel, in dem dieselbe for
Anweisung sowohl für die
Monaden List
als auch für die Option
Monaden gilt:
// (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
Im letzten Beispiel würde die Transformation vielleicht so aussehen:
// 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))
In Slick, Abfragen sind monadische - sie nur Objekte mit dem sind map
, flatMap
und filter
Methoden. Das for
Verständnis (in der Erklärung der *
Methode gezeigt) bedeutet also nur:
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
Wie Sie sehen können flatMap
, map
und filter
werden verwendet, um Query
durch die wiederholte Transformation von Query(Bars)
mit jedem Aufruf von filter
und ein zu erzeugen map
. Bei Sammlungen iterieren und filtern diese Methoden die Sammlung tatsächlich, in Slick werden sie jedoch zum Generieren von SQL verwendet. Weitere Details hier:
Wie übersetzt Scala Slick Scala-Code in JDBC?