*
Metoda:
Zwraca domyślną projekcję - tak to opisujesz:
„wszystkie kolumny (lub wartości obliczone), które mnie zwykle interesują”.
Twoja tabela może mieć kilka pól; potrzebujesz tylko podzbioru do domyślnej projekcji. Domyślne odwzorowanie musi być zgodne z parametrami typu tabeli.
Weźmy to pojedynczo. Bez tego <>
tylko *
:
object Bars extends Table[(Int, String)]("bar") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def * = id ~ name
}
Tylko taka definicja tabeli pozwoli Ci tworzyć zapytania, takie jak:
implicit val session: Session =
val result = Query(Bars).list
domyślne odwzorowanie (Int, String)
prowadzi do a List[(Int, String)]
dla prostych zapytań, takich jak te.
val q =
for (b <- Bars if b.id === 42)
yield (b.name ~ 1)
Jaki to rodzaj q
? To jest Query
z projekcją (String, Int)
. Po wywołaniu zwraca a List
z (String, Int)
krotek zgodnie z projekcją.
val result: List[(String, Int)] = q.list
W tym przypadku zdefiniowałeś projekcję, którą chcesz, w yield
klauzuli for
zrozumienia.
Teraz o <>
i Bar.unapply
.
Zapewnia to tak zwane odwzorowanie odwzorowania .
Do tej pory widzieliśmy, jak zręczny pozwala na wyrażanie zapytań w Scali, które zwracają rzutowanie kolumn (lub obliczonych wartości); Dlatego wykonując te zapytania , musisz traktować wynikowy wiersz zapytania jako krotkę Scali . Typ krotki będzie zgodny z projekcją, która jest zdefiniowana (przez twoje
for
rozumienie, jak w poprzednim przykładzie, przez domyślną *
projekcję). Dlatego field1 ~ field2
zwraca projekcję Projection2[A, B]
gdzie
A
jest typem field1
i B
jest typem field2
.
q.list.map {
case (name, n) =>
}
Queury(Bars).list.map {
case (id, name) =>
}
Mamy do czynienia z krotkami, które mogą być kłopotliwe, jeśli mamy zbyt wiele kolumn. Chcielibyśmy myśleć o wynikach nie jako o TupleN
obiekcie z nazwanymi polami, ale raczej o nim.
(id ~ name)
case class Bar(id: Int, name: String) // For now, using a plain Int instead
(id ~ name <> (Bar, Bar.unapply _))
Query(Bars).list.map ( b.name )
Jak to działa? <>
przyjmuje odwzorowanie Projection2[Int, String]
i zwraca odwzorowane odwzorowanie na typ Bar
. Te dwa argumenty Bar, Bar.unapply _
mówią zręcznie, jak ta (Int, String)
projekcja musi być odwzorowana na klasę przypadku.
To jest mapowanie dwukierunkowe; Bar
jest konstruktorem klasy przypadku, więc są to informacje potrzebne do przejścia z (id: Int, name: String)
pliku do pliku Bar
. A unapply
jeśli zgadłeś, jest na odwrót.
Skąd się unapply
bierze? Jest to standardowa metoda Scala dostępna dla każdej zwykłej klasy przypadku - wystarczy zdefiniować Bar
, Bar.unapply
który jest ekstraktorem, którego można użyć do odzyskania id
i name
który
Bar
został zbudowany z:
val bar1 = Bar(1, "one")
val Bar(id, name) = bar1
val bars: List[Bar] =
val barNames = bars.map {
case Bar(_, name) => name
}
val x = Bar.unapply(bar1)
Więc domyślną projekcję można przypisać do klasy przypadku, której najbardziej spodziewasz się użyć:
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 _)
}
Lub możesz nawet mieć to na zapytanie:
case class Baz(name: String, num: Int)
val q1 =
for (b <- Bars if b.id === 42)
yield (b.name ~ 1 <> (Baz, Baz.unapply _))
Tutaj typ q1
to a Query
z odwzorowaniem odwzorowania na Baz
. Po wywołaniu zwraca a List
z Baz
obiektów:
val result: List[Baz] = q1.list
Wreszcie, na marginesie, w .?
ofercie Option Lifting - sposób Scala radzenia sobie z wartościami, które mogą nie istnieć.
(id ~ name)
(id.? ~ name)
Co podsumowując, będzie dobrze współgrało z Twoją pierwotną definicją Bar
:
case class Bar(id: Option[Int] = None, name: String)
val q0 =
for (b <- Bars if b.id === 42)
yield (b.id.? ~ b.name <> (Bar, Bar.unapply _))
q0.list
W odpowiedzi na komentarz dotyczący sposobu, w jaki Slick używa for
rozumień:
W jakiś sposób monadom zawsze udaje się pojawić i domagać się udziału w wyjaśnieniu ...
Do zrozumienia nie są specyficzne tylko dla zbiorów. Mogą być stosowane na wszelkiego rodzaju Monady , a zbiory są tylko jednym z wielu rodzajów typów monada dostępnych w Scala.
Ale ponieważ kolekcje są znane, stanowią dobry punkt wyjścia do wyjaśnienia:
val ns = 1 to 100 toList;
val result =
for { i <- ns if i*i % 2 == 0 }
yield (i*i)
W Scali do zrozumienia jest cukier składniowy dla wywołań metod (prawdopodobnie zagnieżdżonych): Powyższy kod jest (mniej więcej) równoważny z:
ns.filter(i => i*i % 2 == 0).map(i => i*i)
W zasadzie nic z filter
, map
, flatMap
metody (innymi słowy, Monada ) może być stosowany w
for
zrozumieniu w miejscu ns
. Dobrym przykładem jest monada Option . Oto poprzedni przykład, w którym ta sama for
instrukcja działa zarówno na
monadach, List
jak i na Option
monadach:
val result =
for {
i <- ns
i2 <- Some(i*i)
if i2 % 2 == 0
} yield i2
def evenSqr(n: Int) = {
val sqr = n*n
if (sqr % 2 == 0) Some (sqr)
else None
}
result =
for {
i <- ns
i2 <- evenSqr(i)
} yield i2
W ostatnim przykładzie transformacja mogłaby wyglądać tak:
val result =
ns.flatMap(i => Some(i*i)).filter(i2 => i2 %2 ==0)
result =
ns.flatMap(i => evenSqr(i))
W Slick, zapytania są monadycznego - są tylko obiekty z map
, flatMap
i filter
metody. Zatem for
zrozumienie (pokazane w wyjaśnieniu *
metody) przekłada się po prostu na:
val q =
Query(Bars).filter(b => b.id === 42).map(b => b.name ~ 1)
val r: List[(String, Int)] = q.list
Jak widać, flatMap
, map
i filter
są wykorzystywane do wygenerowania Query
przez powtarzające się transformacji Query(Bars)
przy każdym wywołaniu filter
i map
. W przypadku kolekcji te metody faktycznie iterują i filtrują kolekcję, ale w Slick są używane do generowania kodu SQL. Więcej szczegółów tutaj: W
jaki sposób Scala Slick tłumaczy kod Scala na JDBC?