2010-06-21 22 views
19

Ich fange gerade an, F # zu lernen. Ich habe diesen F #/ADO.NET Code letzte Nacht geschrieben. In welcher Weise würden Sie die Syntax verbessern - sie würde sich wie idiomatisches F # anfühlen?F # und ADO.NET - idiomatische F #

let cn = new OleDbConnection(cnstr) 
    let sql = "SELECT * FROM People" 
    let da = new OleDbDataAdapter(new OleDbCommand(sql, cn)) 
    let ds = new DataSet() 
    cn.Open() 
    let i = da.Fill(ds) 
    let rowCol = ds.Tables.[0].Rows 
    let rowCount = rowCol.Count 
    printfn "%A" rowCount 

    for i in 0 .. (rowCount - 1) do 
     let row:DataRow = rowCol.[i] 
     printfn "%A" row.["LastName"] 

Hinweis: habe ich die Syntaxprüfung fanden ROWCOL nicht wie [i] [ "Name"] Was ist der richtige Weg, Dual-Indexer zu behandeln..? Ich musste den Code über zwei Zeilen aufteilen.

Auch Wenn ich nicht die DataSet-Route gegangen wäre und einen SqlDataReader verwendet, der seine Daten in F # -Datensätze geladen hat. Welche Sammlungsstruktur sollte ich zum Speichern der Datensätze verwenden? Die Standard-.NET-Liste <>?

+0

Nicht sicher, verstehe ich die Frage. – BuddyJoe

+1

Er meinte, dass die Aufgabe (DB-Operationen mit .NET-Bibliotheken) notwendigerweise in imperativem Code endet, deshalb scheint F # nicht dort. Es könnte jedoch sehr gut geeignet sein, um die Daten zu verarbeiten, sobald Sie sie aus der Datenbank erhalten haben. – Mau

+0

@tyndall Mau ist zu 100% korrekt. Ihr Code ist 100% zwingend erforderlich. Sie können es mit FindReplace in C# konvertieren. Ich denke F # könnte hier nicht das beste Werkzeug sein. – Andrey

Antwort

30

Der wichtigste Teil des Codes beschäftigt sich mit .NET-API, die nicht funktionsfähig ist, so gibt es keine Möglichkeit, diesen Teil des Codes zu machen vor allem mehr idiomatische oder schöner. Der Schlüssel in der funktionalen Programmierung ist jedoch Abstraktion, so dass Sie diesen (hässlichen) Code in eine idiomatische und wiederverwendbare Funktion verstecken können.

Für Sammlungen von Daten in F # darstellt, entweder Sie Standard-F # Listentyp verwenden können (die für die Funktionsdatenverarbeitung gut ist) oder seq<'a> (die Standard-NET ist IEnumerable<'a> unter der Abdeckung), die gut funktioniert, wenn sie mit anderen Arbeits .NET-Bibliotheken

Je nachdem, wie Sie Datenbank an anderer Stelle im Code zugreifen, könnte folgende Arbeiten:

// Runs the specified query 'sql' and formats rows using function 'f' 
let query sql f = 
    // Return a sequence of values formatted using function 'f' 
    seq { use cn = new OleDbConnection(cnstr) // will be disposed 
     let da = new OleDbDataAdapter(new OleDbCommand(sql, cn)) 
     let ds = new DataSet() 
     cn.Open() 
     let i = da.Fill(ds) 
     // Iterate over rows and format each row 
     let rowCol = ds.Tables.[0].Rows 
     for i in 0 .. (rowCount - 1) do 
      yield f (rowCol.[i]) } 

Jetzt können Sie die query Funktion benutzen, um Ihre ursprünglichen Code zu schreiben, etwa wie folgt aus:

let names = query "SELECT * FROM People" (fun row -> row.["LastName"]) 
printfn "count = %d" (Seq.count names) 
for name in names do printfn "%A" name 

// Using 'Seq.iter' makes the code maybe nicer 
// (but that's a personal preference): 
names |> Seq.iter (printfn "%A") 

Ein weiteres Beispiel könnten Sie schreiben ist:

// Using records to store the data 
type Person { LastName : string; FirstName : string } 
let ppl = query "SELECT * FROM People" (fun row -> 
    { FirstName = row.["FirstName"]; LastName = row.["LastName"]; }) 

let johns = ppl |> Seq.filter (fun p -> p.FirstName = "John") 

BTW: In Bezug auf den Vorschlag von Mau würde ich Funktionen höherer Ordnung nicht übermäßig verwenden, wenn es eine direktere Möglichkeit gibt, den Code mithilfe von Sprachkonstrukten wie for zu schreiben. Das Beispiel mit iter oben ist einfach genug und einige Leute finden es besser lesbar, aber es gibt keine allgemeine Regel ...

+1

Wenn Sie Ihr erstes Beispiel richtig verstehen, wird die seq {} dazu führen, dass die Datenbank erneut abgefragt wird, richtig? und Ihre "let ppl =" Zeile vermeidet das Veränderungsproblem, indem sie es auf eine Sequenz setzt. Bin ich hier auf dem richtigen Weg? – BuddyJoe

+1

@tyndall: Das ist ein guter Punkt! Die Sequenz wird bei jeder Verwendung der Sequenz erneut ausgewertet (und die Datenbank erneut abgefragt) (z. B. mit 'Seq.count' oder' for'). Das ist ein bisschen unglücklich und "let ppl = .." vermeidet das nicht. Sie könnten jedoch 'let ppl = ... |> Seq.cache' oder zum Beispiel' List.ofSeq' schreiben, um die Abfrage auszuführen und das Ergebnis als Liste zu erhalten. –

+0

Ich mag die List.ofSeq Idee. +1 – BuddyJoe

4

Nun, es gibt nicht viel, was Sie im ersten Bit ändern können, aber wenn Sie Datensammlungen wie in den letzten Zeilen verarbeiten, können Sie die integrierten Funktionen Seq, List, Array verwenden.

for i in 0 .. (rowCount - 1) do 
    let row:DataRow = rowCol.[i] 
    printfn "%A" row.["LastName"] 

=

rowCol |> Seq.cast<DataRow> 
     |> Seq.iter (fun row -> printfn "%A" row.["LastName"]) 
7

Ich schrieb eine functional wrapper over ADO.NET for F#. Mit dieser Bibliothek sieht Ihr Beispiel so aus:

let openConn() = 
    let cn = new OleDbConnection(cnstr) 
    cn.Open() 
    cn :> IDbConnection 

let query sql = Sql.execReader (Sql.withNewConnection openConn) sql 

let people = query "select * from people" |> List.ofDataReader 
printfn "%d" people.Length 
people |> Seq.iter (fun r -> printfn "%s" (r?LastName).Value)