2012-12-16 12 views
85

Ich versuche, einige Slick-Arbeiten zu verstehen und was es erfordert.Scala Slick-Methode kann ich bisher nicht verstehen

Hier ein Beispiel:

package models 

case class Bar(id: Option[Int] = None, name: String) 

object Bars extends Table[Bar]("bar") { 
    def id = column[Int]("id", O.PrimaryKey, O.AutoInc) 

    // This is the primary key column 
    def name = column[String]("name") 

    // Every table needs a * projection with the same type as the table's type parameter 
    def * = id.? ~ name <>(Bar, Bar.unapply _) 
} 

Könnte mir jemand erklären, was der Zweck der * Methode ist hier, was ist <>, warum unapply? und was ist Projektion - Methode ~ 'gibt die Instanz Projection2 zurück?

Antwort

189

[UPDATE]-hinzugefügt (weitere) Erklärung auf for Comprehensions

  1. Die * Methode:

    Dies gibt die Projektion Standard - das ist, wie Sie beschreiben:

    ‚Alle Spalten (oder Werte berechnet) Ich bin in der Regel interessiert‘ in

    Ihre Tabelle kann mehrere Felder haben. Sie benötigen nur eine Teilmenge für Ihre Standardprojektion. Die Standardprojektion muss mit den Parametern des Typs der Tabelle übereinstimmen.

    Lassen Sie uns eins nach dem anderen nehmen.Ohne die <> Sachen, 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) 
    

    Nur eine Tabellendefinition so lassen Sie Abfragen wie machen:

    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 einer List[(Int, String)] für einfache Abfragen wie zum 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 der Typ von q? Es ist ein Query mit der Projektion (String, Int). Bei Aufruf wird eine List(String, Int) Tupel gemäß der Projektion zurückgegeben.

    val result: List[(String, Int)] = q.list 
    

    In diesem Fall haben Sie die Projektion Sie des for Verständnis in der yield Klausel wollen definiert.

  2. Jetzt über <> und Bar.unapply.

    Dies bietet, was Mapped Projections genannt wird.

    Bisher haben wir gesehen, wie glatt Sie Abfragen in Scala auszudrücken ermöglicht , die eine Projektion von Spalten (oder berechnete Werte) zurückkehren; Also bei der Ausführung diese Abfragen müssen Sie an die Ergebniszeile einer Abfrage als ein Scala-Tupel denken. Der Typ des Tupel entspricht der Projektion, die definiert ist (durch Ihre for Verständnis wie im vorherigen Beispiel von der Standard * Projektion). Aus diesem Grund field1 ~ field2 eine Projektion von Projection2[A, B] zurück, wo A die Art der field1 ist und B die Art der field2 ist.

    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 mit Tupeln zu tun hat, was mühsam sein kann, wenn wir zu viele Spalten haben. Wir möchten uns die Ergebnisse nicht als TupleN sondern eher als ein Objekt mit benannten Feldern vorstellen.

    (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 projizierte Projektion auf den Typ Bar zurück. Die beiden Argumente Bar, Bar.unapply _ erzählen, wie diese (Int, String) Projektion einer Fallklasse zugeordnet werden muss.

    Dies ist ein Zwei-Wege-Mapping; Bar ist der Fall Klassenkonstruktor, das ist also die Informationen benötigt, um von (id: Int, name: String) zu einem Bar gehen. Und unapply wenn Sie es erraten haben, ist für das Gegenteil.

    Woher kommt unapply?Dies ist ein Standard Scala Verfahren für jeder gewöhnliche Fallklasse - nur definieren Bar gibt Ihnen eine Bar.unapply die ist ein Extraktor, die verwendet werden können, die id und name zurück zu bekommen, dass die Bar wurde mit gebaut:

    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 können Ihre Standard-Projektion auf den Fall Klasse zugeordnet werden Sie am meisten erwarten zu verwenden:

    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 die Art von q1 ist ein Query mit einem Projektion Baz abgebildet. Wenn es aufgerufen wird, gibt sie ein List von Baz Objekte:

    val result: List[Baz] = q1.list 
    
  3. schließlich als Nebenbei bemerkt, die .? bietet Option Lifting - die Scala Weg von mit Werten zu tun, die nicht sein kann.

    (id ~ name) // Projection2[Int, String] // this is just for illustration 
    (id.? ~ name) // Projection2[Option[Int], String] 
    

    Which, Einwickeln, wird mit Ihrer ursprünglichen Definition von Bar gut funktionieren:

    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] 
    
  4. Als Reaktion auf den Kommentar, wie Slick Anwendungen for Comprehensions:

    Irgendwie immer Monaden verwalten zu zeigen und zu werden Teil der Erklärung sein ...

    Für Übersichten gelten nicht nur Sammlungen. Sie können auf jeder Art von Monad verwendet werden, und Sammlungen sind nur eine der vielen Arten von Monade-Typen in Scala verfügbar.

    Aber als Sammlungen vertraut sind, machen sie einen guten Ausgang Punkt 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, ein für das Verständnis ist syntaktischer Zucker für Methode (möglicherweise verschachtelte) Methode aufruft: Der obige Code ist (mehr oder weniger) entspricht:

    ns.filter(i => i*i % 2 == 0).map(i => i*i) 
    

    Grundsätzlich etwas mit filter, map, flatMap Methoden (mit anderen Worten, ein Monad) kann in einem for Verständnis statt ns verwendet werden. Ein gutes Beispiel ist das Option monad.Hier ist das vorherige Beispiel , wo die gleiche for Anweisung funktioniert sowohl auf der List sowie Option Monaden:

    // (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 aussehen vielleicht wie folgt aus:

    // 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 sind Abfragen monadisch - sie sind nur Objekte mit den Methoden map, flatMap und filter. So ist das for Verständnis (in der Erklärung der * Methode dargestellt) übersetzt 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 erzeugen ein Query durch die wiederholte Transformation von Query(Bars) mit jedem Aufruf von filter und map. Im Fall von Sammlungen iterieren und filtern diese Methoden tatsächlich die Sammlung , aber in Slick werden sie verwendet, um SQL zu generieren. Weitere Details hier: How does Scala Slick translate Scala code into JDBC?

+11

Schöne Antwort. –

+0

In '1' Erklärung Block: Es ist nicht offensichtlich, dass 'val q =' WrappingQuery, es sieht aus wie eine Liste beim Lesen des Codes. Wie ist es möglich, dass es in Query umgewandelt wird? (Ich spiele immer noch mit deinen Erklärungen, um zu verstehen, wie es funktioniert. Danke dafür!) – ses

+0

@ses - hinzugefügt eine (etwas lang) Erklärung dazu ... Siehe auch http://StackOverflow.com/questions/13454347/monads-with-java-8/13455602 # 13455602 - Ich erkannte, dass es fast der gleiche Inhalt ist. – Faiz

6

Da niemand sonst geantwortet hat, könnte dies helfen, um loszulegen. Ich kenne Slick nicht sehr gut.

Vom Slick documentation:

Lifted Embedding:

Jede Tabelle benötigt eine * Methode eine Standardprojektion contatining. Diese beschreibt, was Sie erhalten, wenn Sie Zeilen (in Form eines Tabellenobjekts ) aus einer Abfrage zurückgeben. Slicks * Projektion muss nicht mit dem in der Datenbank übereinstimmen. Sie können neue Spalten hinzufügen (z. B. mit berechneten Werten ) oder einige Spalten weglassen, wie Sie möchten. Der nicht angehobene Typ , der der * Projektion entspricht, wird als Typparameter an Tabelle übergeben. Bei einfachen, nicht zugeordneten Tabellen ist dies eine einzelne Spalte oder ein Tupel von Spaltentypen.

Mit anderen Worten, Slick muss wissen, wie man mit einer aus der Datenbank zurückgegebenen Zeile umgeht. Die von Ihnen definierte Methode verwendet ihre Parserkombinatorfunktionen, um Ihre Spaltendefinitionen in etwas zu kombinieren, das in einer Zeile verwendet werden kann.

+0

ook. und Projektion ist nur eine Darstellung der Spalten .. wie: final class Projection2 [T1, T2] ( Überschreibung val _1: Spalte [T1], Überschreibung val _2: Spalte [T2] ) erstreckt Tuple2 (_1, _2) mit Projektion [(T1, T2)] {.. – ses

+0

Nun .. wie kommt es, dass: Bar 'unapply' Methode? – ses

+2

Aha .. - Alle Case-Klassen implementieren das Produktmerkmal und werden nicht angewendet. Zauber. – ses