【问题标题】:Haskell HDBC Elegance in F#?F# 中的 Haskell HDBC 优雅?
【发布时间】:2013-04-25 15:42:53
【问题描述】:

Haskell 的简洁和优雅让我印象深刻。但是我在 .Net 公司工作,所以我会在可以摆脱的情况下使用 F#——我可能是全国数百个使用它的唯一一个。

ADO.NET 或 F# 是否提供与 HDBC 的 executeMany 一样简洁和优雅的东西?我正在通过Real World Haskell。在chapter 21 它提供了这个例子:

ghci> conn <- connectSqlite3 "test1.db"
ghci> stmt <- prepare conn "INSERT INTO test VALUES (?, ?)"
ghci> executeMany stmt [[toSql 5, toSql "five's nice"], [toSql 6, SqlNull]]
ghci> commit conn
ghci> disconnect conn

我想在我的 F# 中获得这种优雅和简洁。我已经看到很多关于使用参数化查询来避免 SQL 注入攻击的炒作。在这种情况下,我没有使用它们有以下三个原因:

  1. 我发现 .Net 中的参数化查询既丑陋又繁琐。
  2. 我的数据来自公司办公室,所以它(大部分)是干净的。
  3. 我的表有 34 列。我鄙视用 34 列参数化查询的想法。

这是我的 F# 代码:

module Data

open System
open System.Data
open System.Data.OleDb
open System.Text.RegularExpressions

type Period = Prior | Current

let Import period records db =
    use conn = new OleDbConnection(@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + db + ";Persist Security Info=False;")

    let execNonQuery s =
        let comm = new OleDbCommand(s, conn) in
        comm.ExecuteNonQuery() |> ignore

    let enquote = sprintf "\"%s\""
    let escapeQuotes s = Regex.Replace(s, "\"", "\"\"")
    let join (ss:string[]) = String.Join(",", ss)

    let table = match period with
                | Prior   -> "tblPrior"
                | Current -> "tblCurrent"
    let statements =
        [| for r in records do
               let vs = r |> Array.map (escapeQuotes >> enquote) |> join
               let vs' = vs + sprintf ",\"14\",#%s#" (DateTime.Now.ToString "yyyy-MM-dd") in
               yield sprintf "INSERT INTO %s ( [Field01], [Field02], [Field03] [Field04], [Field05], [Field06], [Field07], [Field08], [Field09], [Field10], [Field11], [Field12], [Field13], [Field14], [Field15], [Field16], [Field17], [Field18], [Field19], [Field20], [Field21], [Field22], [Field23], [Field24], [Field25], [Field26], [Field27], [Field28], [Field29], [Field30], [Field31], [Field32], [Field33], [Field34] ) VALUES (%s)" table vs' |] in

    do conn.Open()
    execNonQuery (sprintf "DELETE FROM %s" table)
    statements |> Array.iter execNonQuery

出于安全原因,我已重命名表的字段。

由于表格上的所有字段都是文本,我可以轻松地将它们 Array.map 以转义和引用值。

每天要导入两个表中的每一个的记录在 9,000 到 10,000 条之间,我希望尽可能高效地执行此操作。因此我对 Haskell 的executeMany 很感兴趣。不过,我也喜欢参数化查询背后的想法,也喜欢 Hasekll 实现它们的方式。在 F# 中是否存在与简洁和优雅相当的东西?

【问题讨论】:

  • 我不知道它是否和 Hasekll 一样优雅,但 F# 3.0 中的类型提供程序比构建插入语句要优雅得多。
  • "我可能是全国数百个使用它的唯一一个。"呃,不。
  • @ildjarn 我认为您误解了他所指的内容。我很确定“数百人”是他公司里的人。
  • 没错,我的意思是我公司的数百人。 :)
  • 啊,我确实误会了,抱歉。 :-]

标签: ado.net f# haskell hdbc


【解决方案1】:

我同意 @JonnyBoats 的评论,即通常使用像 SqlDataConnection(LINQ-to-SQL)或 SqlEntityConnection(实体框架)这样的 F# SQL 类型提供程序将比任何涉及构建插入语句的解决方案优雅得多手动字符串。

但是,您的问题有一个重要的限定词:“每天要导入两个表中的每一个的记录在 9,000 到 10,000 条之间,我希望尽可能高效地执行此操作。”在这种情况下,您需要使用 SqlBulkCopy 进行高效的批量插入(它利用原生数据库驱动程序功能实现比 HDBC 的 executeMany 更快的插入)。

下面是一个小示例,可以帮助您开始使用带有 F# 的 SqlBulkCopyhttps://stackoverflow.com/a/8942056/236255。请注意,您将使用DataTable 来暂存数据,虽然这些数据在 F# 中使用起来有些陈旧且有些尴尬,但在我看来仍然优于构建插入语句字符串。

根据评论更新

这是使用SqlBulkCopy 的通用方法,该方法针对您的场景进行了改进(我们将列规范与行数据分开传递,并且两者都是动态的):

//you must reference System.Data and System.Xml
open System
open System.Data
open System.Data.SqlClient

let bulkLoad (conn:SqlConnection) tableName (columns:list<string * Type>) (rows: list<list<obj>>) =
    use sbc = new SqlBulkCopy(conn, SqlBulkCopyOptions.TableLock, null, BatchSize=500, BulkCopyTimeout=1200, DestinationTableName=tableName)
    sbc.WriteToServer(
        let dt = new DataTable()
        columns
        |> List.iter (dt.Columns.Add>>ignore)

        for row in rows do
            let dr = dt.NewRow()
            row |> Seq.iteri(fun i value -> dr.[i] <- value)
            dt.Rows.Add(dr)
        dt)

//example usage:

//note: since you know all your columns are of type string, you could define columns like
//let columns = ["Field1", "Field2", "Field3"] |> List.map (fun name -> name, typeof<String>)
let columns = [
    "Field1", typeof<String>
    "Field2", typeof<String>
    "Field3", typeof<String>
]

let rows = [
    ["a"; "b"; "c"]
    ["d"; "e"; "f"]
    ["g"; "h"; "i"]
    ["j"; "k"; "l"]
    ["m"; "n"; "o"]
]

//a little funkiness to transform our list<list<string>> to list<list<obj>>, 
//probably not needed in practice because you won't be constructing your lists literally
let rows = rows |> List.map (fun row -> row |> List.map (fun value -> value :> obj))

bulkLoad conn "tblPrior" columns rows

使用涉及反射的方法,您可以变得更花哨/更简洁。例如创建一个类似

的类型
type RowData = { Field1:string; Field2:string; Field3:string }

并使用带有list&lt;'a&gt; 参数的签名创建bulkLoad,以便它反映typeof&lt;'a&gt; 的属性名称和类型以构建DataTable Columns,并且类似地使用反射来迭代创建行实例的所有属性并将新行添加到DataTable。事实上,this question 展示了如何创建一个通用的 ToDataTable 方法(在 C# 中)。

【讨论】:

  • 感谢您告诉我有关 SqlBulkCopy 的信息,以及指向您示例的指针。不过,我想知道,如果 F# 不能做得更好。关于您的示例,最让我感到震惊的是它需要两次引用列名。在 34 列中,有 68 个重复点(我自己的、临时的、统计的——不严格解释)。打完所有这些之后,我会觉得需要洗澡,并且讨厌看我的工作。
  • @JeffManer 是的,我听到了。 F# 语言当然可以像你的 Haskell 一样富有表现力,但是 API 还没有(据我所知)。但是,我更新后可能会使用针对您的场景进行通用和改进的SqlBulkCopy 方法来回答(列名和类型只需要指定一次)。
  • @StephenSwenson,非常感谢!不幸的是,SqlBulkCopy 只能用于写入 SQL Server 表。在这种情况下,我正在写入 Access 表。但是我有另一个项目,我在其中写入 SQL Server 表。我会尝试在那里使用你的新的和改进的例子! :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-24
  • 2013-06-21
  • 1970-01-01
  • 2021-01-07
相关资源
最近更新 更多