2010-06-21 69 views
19

我刚刚开始学习F#。我昨晚写了这个F#/ ADO.NET代码。 你会以何种方式改进语法 - 让它感觉像惯用的F#?F#和ADO.NET - 惯用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"] 

注:我发现语法检查不喜欢 rowCol [I] [ “名字”] 什么是处理双索引的正确方法。?我不得不通过两行分解代码。

另外如果我没有关闭DataSet路由并使用将其数据加载到F#记录的SqlDataReader。我应该使用什么样的结构来包含记录? 标准.NET列表<>?

+0

不知道我明白这个问题。 – BuddyJoe 2010-06-21 13:34:55

+1

他意味着手头的任务(使用.NET库的数据库操作)必然以命令式代码结束,所以F#不会在那里发光。但是,一旦将数据从数据库中取出,它可能非常适合处理数据。 – Mau 2010-06-21 13:38:43

+0

@tyndall Mau是100%正确的。你的代码是100%势在必行的。您可以使用FindReplace将其转换为C#。我认为F#可能不是这里最好的工具。 – Andrey 2010-06-21 13:42:13

回答

30

与.NET API代码交易的关键部分不能正常工作,所以没有办法让这部分代码特别是更地道或更好。然而,函数式编程中的关键是抽象,所以你可以将这个(丑陋的)代码隐藏到一些惯用和可重用的函数中。你可以使用标准的F#列表类型(这对功能数据处理很好)或者seq<'a>(这是标准的.NET IEnumerable<'a>),它可以很好地处理其他的.NET库。

取决于你如何在你的代码的其他地方访问数据库,下面可以工作:

// 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]) } 

现在你可以使用query功能大致写你原来的代码是这样的:

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") 

你可以编写的另一个例子是:

// 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:关于Mau的建议如果有更直接的方法使用语言结构编写代码(例如for),我不会过分使用高阶函数。上面的iter的例子很简单,有些人会发现它更具可读性,但没有一般规则...

+1

如果正确理解你的第一个例子,seq {}将导致数据库再次被查询,对吗?而你的“let ppl =”这行通过设置它来避免可变性问题。我在这条正确的道路上吗? – BuddyJoe 2010-06-21 14:49:04

+1

@tyndall:这是一个很好的观点!每次使用序列时(例如使用“Seq.count”或“for”),将重新评估序列(并再次查询数据库)。这有点不幸,“let ppl = ..'不能避免。但是,您可以编写'let ppl = ... |> Seq.cache'或例如'List.ofSeq'来运行查询并获得列表结果。 – 2010-06-21 15:43:03

+0

我喜欢List.ofSeq的想法。 +1 – BuddyJoe 2010-06-21 16:50:57

4

那么,在第一个位没有太多的改变,但是无论何时处理最后几行的数据集时,都可以使用内置的Seq,List,Array函数。

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

我写了一个functional wrapper over ADO.NET for F#。有了这个库,你的例子看起来像这样:

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)