2013-05-01 50 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