F#和ADO.NET - 惯用F#

时间:2010-06-21 13:26:50

标签: .net ado.net f# functional-programming

我刚开始学习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路由走下去并使用一个SqlDataReader将其数据加载到F#记录中。我应该使用什么样的集合结构来包含记录? 标准的.NET列表<>?

3 个答案:

答案 0 :(得分:31)

您的代码的关键部分涉及无法运行的.NET API,因此无法使代码的这一部分更具惯用性或更好。但是,函数式编程中的关键是抽象,因此您可以将这个(丑陋)代码隐藏到一些惯用和可重用的函数中。

为了表示F#中的数据集合,您可以使用标准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 :(得分: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)

答案 2 :(得分: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"])