2013-05-01 45 views
3

哈斯克尔的简洁和优雅让我印象深刻。但是我在一个.Net的房子里工作,所以我用F#的时候,我可以逃避它 - 我可能是全国数百人中唯一使用它的人。F#中的Haskell HDBC优雅?

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#中的简洁和优雅有什么相同之处?

+7

我不知道它是否像Hasekll一样优雅,但F#3.0中的类型提供程序比构建插入语句更优雅。 – JonnyBoats 2013-05-01 16:04:28

+3

“*我可能是全国数百人中唯一使用它的人。*”呃,不。 – ildjarn 2013-05-01 16:53:07

+1

@ildjarn让你误解他所指的是什么。我很确定这些“数百人”是他公司里的人。 – mydogisbox 2013-05-01 16:57:55

回答

7

我同意@JonnyBoats评论说,通常使用像SqlDataConnection(LINQ到SQL)或SqlEntityConnection(实体框架)的F#SQL类型的供应商将远远超过任何一种涉及手工建筑INSERT语句字符串的解决方案更优雅。

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

下面是一个小例子,可以帮助您开始使用SqlBulkCopy和F#:https://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 } 

,并作出bulkLoad与需要list<'a>参数,使得它反映了的typeof<'a>的属性名称和类型来构建DataTableColumns,同样使用反射来遍历所有的签名行实例的属性创建并向DataTable添加新行。实际上,this question显示了如何制作通用的ToDataTable方法(在C#中)。

+0

感谢您告诉我关于SqlBulkCopy以及指向您示例的指针。不过,我想知道,如果F#无法做得更好。最让我担心的是,它需要两次引用列名。在34列,这是68个重复点(我自己的,特设的统计 - 不要被严格解释)。在输入完所有内容后,我会觉得需要洗澡,并且会讨厌看我的工作。 – 2013-05-01 19:20:55

+0

@JeffManer是的,我听到你。 F#的语言当然可以像你的Haskell一样具有表现力,但是这些API还不存在(据我所知)。不过,我已经更新过,可能会针对您的场景进行一般化和改进的SqlBulkCopy方法(列名和类型只需指定一次)。 – 2013-05-01 20:13:22

+0

@StephenSwenson,非常感谢!不幸的是,'SqlBulkCopy'只能用于写入SQL Server表。在这种情况下,我正在写入Access表。但我有另一个项目,我写入SQL Server表。我会尝试使用你的新的和改进的例子! :) – 2013-05-02 14:28:43