首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Haskell优雅在F#?

Haskell优雅在F#?
EN

Stack Overflow用户
提问于 2013-05-01 15:54:04
回答 1查看 525关注 0票数 3

我被哈斯克尔的简洁和优雅所打动。但是我在.Net的房子里工作,所以当我能摆脱它的时候我就用F# --我可能是全国数百个使用它的人中的唯一一个。

ADO.NET或F#是否提供了与HDBC的executeMany一样简洁和优雅的功能?我正在通过真实世界Haskell。在第21章中,它提供了以下示例:

代码语言:javascript
复制
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#代码:

代码语言:javascript
复制
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

出于安全考虑,我重新命名了表中的字段。

因为表中的所有字段都是文本,所以我可以很容易地将它们转义并引用值。

以每天9,000至10,000条记录将数据导入到这两个表中的每个表中,我希望尽可能高效地完成这一任务。因此我对哈斯克尔的executeMany感兴趣。不过,我也喜欢参数化查询背后的想法,也喜欢Hasekll实现这些查询的方式。在F#中是否有类似于简洁和优雅的东西?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2013-05-01 16:32:55

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

但是,您的问题有一个重要的限定符:“每天输入到这两个表中的每一个表的记录在9,000至10,000条之间,我希望尽可能高效地完成这一任务。”在这样的场景中,您将希望使用SqlBulkCopy进行高效的批量插入(它利用本地数据库驱动程序特性来实现比使用HDBC的executeMany可能得到的更快的插入)。

下面是一个小示例,可以帮助您开始使用SqlBulkCopy和F#:https://stackoverflow.com/a/8942056/236255。请注意,您将使用一个DataTable来生成数据,这些数据虽然很旧,而且从F#中使用起来有些尴尬,但在我看来,它仍然优于构建insert语句字符串。

响应评论更新

下面是一种使用SqlBulkCopy的通用方法,该方法针对您的场景进行了改进(我们分别传递列规范和行数据,两者都是动态的):

代码语言:javascript
复制
//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

使用一种涉及反射的方法,你可能会变得更有幻想/更简洁。例如,创建一个类似于

代码语言:javascript
复制
type RowData = { Field1:string; Field2:string; Field3:string }

并使用一个带有bulkLoad参数的签名来创建一个list<'a>参数,这样它就可以在typeof<'a>的属性名称和类型上进行反射以构建DataTable Columns,并且类似地使用反射对一个行实例的所有属性进行迭代,以创建一个新行并向DataTable添加一个新行。实际上,这个问题展示了如何创建一个通用的ToDataTable方法(在C#中)。

票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/16321601

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档