【问题标题】:F# and ADO.NET - idiomatic F#F# 和 ADO.NET - 惯用的 F#
【发布时间】:2010-06-21 13:26:50
【问题描述】:

我刚刚开始学习 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].["LastName"] 处理双索引器的正确方法是什么?我不得不将代码分成两行。

另外如果我没有走 DataSet 路线并使用 SqlDataReader 将其数据加载到 F# 记录中。我应该使用什么集合结构来包含记录? 标准 .NET 列表?

【问题讨论】:

  • 不确定我是否理解这个问题。
  • 他的意思是手头的任务(使用 .NET 库进行 DB 操作)必然以命令式代码结束,因此 F# 并不适用。但是,一旦您将数据从数据库中取出,它可能非常适合处理数据。
  • @tyndall Mau 是 100% 正确的。您的代码是 100% 必要的。您可以使用 FindReplace 将其转换为 C#。我认为 F# 可能不是这里最好的工具。
  • @Andrey:这里的 F# 与 C#完全相同。问题是,如果你想稍后编写一些优雅的 F# 代码,你需要先获取数据:-)。
  • 我正在尝试获取数据,然后利用 F# 的优势。但想避免用 C# 和 F# 编写应用程序。

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


【解决方案1】:

代码的关键部分处理不起作用的 .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")

顺便说一句:关于 Mau 的建议,如果有更直接的方法来使用语言结构(例如 for)编写代码,我不会过度使用高阶函数。上面iter的例子很简单,有些人会觉得它更具可读性,但没有通用的规则……

【讨论】:

  • 如果正确理解你的第一个例子,seq { } 将导致数据库再次查询,对吗?并且您的“let ppl =”行通过将其设置为序列来避免可变性问题。我走在正确的道路上吗?
  • @tyndall:说得好!每次您使用该序列(例如使用Seq.countfor)时,都会重新评估该序列(并再次查询数据库)。这有点不幸,let ppl = .. 并没有避免这种情况。但是,您可以编写 let ppl = ... |> Seq.cache 或例如 List.ofSeq 来运行查询并以列表形式获取结果。
  • 我喜欢 List.ofSeq 的想法。 +1
  • 产生序列的“for”循环可以简化:for row in rowCol do yield f row。那么我们就不需要变量i了,可以有“da.Fill(ds)|>ignore”
  • 很好的答案。有点吹毛求疵,但我认为更好的选择是避免通过.Fill() 急切地加载结果并通过IDbReader 懒惰地使用它。
【解决方案2】:

我写了一个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)

【讨论】:

    【解决方案3】:

    嗯,你可以在第一位改变的不多,但是每当你处理像最后几行这样的数据集合时,你可以使用内置的 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"])
    

    【讨论】:

      猜你喜欢
      • 2019-04-20
      • 2019-08-10
      • 1970-01-01
      • 2019-03-20
      • 2014-07-03
      • 2015-12-31
      • 2019-08-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多