3

Haskell の簡潔さと優雅さに感銘を受けました。しかし、私は .Net ハウスで働いているので、F# を使いこなせるときは F# を使用しています。全国で F# を使用しているのは、おそらく私だけです。

ADO.NET または F# は、HDBC のように簡潔でエレガントなものを提供しますexecuteManyか? 私はReal World Haskellを通り抜けています。第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 インジェクション攻撃を回避するためにパラメーター化されたクエリを使用するという誇大宣伝をたくさん見てきました。この場合、次の 3 つの理由でそれらを使用していません。

  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 してエスケープし、値を引用することができます。

2 つのテーブルのそれぞれに 1 日あたり 9,000 から 10,000 レコードをインポートするので、これをできるだけ効率的に行いたいと考えています。したがって、executeManyHaskell に興味があります。とはいえ、パラメーター化されたクエリの背後にあるアイデアも好きですし、Hasell がそれらを実装する方法も気に入っています。F# の簡潔さと優雅さに相当するものはありますか?

4

1 に答える 1

7

一般に、 SqlDataConnection (LINQ-to-SQL) やSqlEntityConnection (Entity Framework) などの F# SQL 型プロバイダーを使用することは、insert ステートメント文字列を手動で作成することを含むあらゆる種類のソリューションよりもはるかにエレガントであるという @JonnyBoats のコメントに同意します。

しかし、あなたの質問には 1 つの重要な修飾子があります。「2 つのテーブルのそれぞれに 1 日あたり 9,000 から 10,000 レコードをインポートするため、これをできるだけ効率的に行いたいです。」このようなシナリオでは、効率的な一括挿入のためにSqlBulkCopyを使用することをお勧めします(ネイティブ データベース ドライバーの機能を利用して、HDBC で得られる可能性が高いよりもはるかに高速な挿入を実現しますexecuteMany)。

SqlBulkCopyF#の使用を開始するのに役立つ小さな例を次に示します: https://stackoverflow.com/a/8942056/236255DataTableを使用してデータをステージングすることに注意してください。データは古くて F# から使用するのはやや厄介ですが、私の意見では、insert ステートメント文字列を作成するよりも優れています。

コメントに応じて更新

これは、シナリオに合わせて改善された一般的なアプローチ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を取るシグネチャを使用して を作成し、同様にリフレクションを使用して行インスタンスのすべてのプロパティを反復処理し、新しい行を作成して に追加します。実際、この質問は、それを行うジェネリックメソッドを作成する方法を示しています (C# で)。list<'a>typeof<'a>DataTable ColumnsDataTableToDataTable

于 2013-05-01T16:32:55.520 に答える