2017-03-15 53 views
2

我的F#程序需要與SQL Server交談。在一個部分,我有這樣的事情:沒有在這樣的時間設置參數一個F#使用參數設置SQLCommand的最佳方式

let workFlowDetailRuncommand = new SqlCommand(query, econnection) 
    workFlowDetailRuncommand.CommandTimeout <- 100000 
    workFlowDetailRuncommand.Parameters.Add("@1", SqlDbType.Int).Value <- 42 
    workFlowDetailRuncommand.Parameters.Add("@2", SqlDbType.VarChar).Value <- "answer" 
    workFlowDetailRuncommand.Parameters.Add("@3", SqlDbType.VarChar).Value <- mydate.ToString("yyyy.MM.dd") 
    workFlowDetailRuncommand.Parameters.Add("@4", SqlDbType.VarChar).Value <- "D. Adams" 
    workFlowDetailRuncommand.Parameters.Add("@5", SqlDbType.DateTime).Value <- DateTime.Now 
    workFlowDetailRuncommand.Parameters.Add("@6", SqlDbType.Text).Value <- filename 

有沒有更idomatic辦法做到這一點(用更少的打字!)。

+2

請看這裏的SqlCommandProvider。可能不完全適合你的用例(特別是在查詢是動態的情況下),但很好地處理參數:http://fsprojects.github.io/FSharp.Data.SqlClient/ –

回答

2

我還沒有測試過這個。

創建一些幫助函數。

let createSqlCommand query connection = 
    new SqlCommand(query, connection) 

let setTimeout timeout (sqlCommand: SqlCommand) = 
    sqlCommand.CommandTimeout <- timeout 
    sqlCommand 

let addInt name (value: int) (sqlCommand: SqlCommand) = 
    sqlCommand.Parameters.Add(name, SqlDbType.Int).Value <- value 
    sqlCommand 

像這樣使用它們。

let mySqlCommand = 
    createSqlCommand someQuery someConnection 
    |> setTimeout 100000 
    |> addInt "@1" 41 
    |> addString "@s" "Hello" 
    |> addDateTime "@dt1" DateTime.Now 
    |> addFloat "@f1" 5.1 

如果你有一個int類型,那麼總是使用SqlDbType.Int是有意義的。

但是,如果你有一個字符串,有幾個明顯的候選字段類型。出於這個原因,讓addXxx函數的名稱反映字段類型而不是F#/ .NET類型可能是一個好主意。所以,你會創造addVarChar,addNVarChar,addChar等

|> addInt "@i1" myInt 
|> addDateTime "@dt1" myDateTime 
|> addText "@tagText" myTagText 
|> addNVarChar "@letter" myLetterBody 
4

我覺得從彎曲的答案給你構建標準SqlCommand對象非常好的DSL。這可能就是你所需要的 - 如果你只是想創建更好的語法來創建一些命令,它將會很好地工作。

如果你想用你的SQL命令做更多的事情,那麼DSL有一個限制,那就是它仍然基於潛在的可變SqlCommand類型 - 它看起來很實用,但它會改變封面下的對象,可能會讓你陷入麻煩。

一個更全面的選擇是定義你自己的功能類型來捕捉域 - 也就是說,該類型的查詢要運行:

type Parameter = 
    | Int of int 
    | VarChar of string 
    | Text of string 
    | DateTime of System.DateTime 

type Command = 
    { Query : string 
    Timeout : int 
    Parameters : (string * Parameter) list } 

然後你就可以使用正常的F#類型構造查詢(你甚至可以實現像一個彎曲在此之上提出了DSL,同時仍保持的東西是不變的):

let cmd = 
    { Query = query 
    Timeout = 100000 
    Parameters = 
     [ "@1", Int 42 
     "@2", VarChar "answer" 
     "@3", VarChar (mydate.ToString("yyyy.MM.dd")) 
     "@4", VarChar "D. Adams" 
     "@5", DateTime DateTime.Now 
     "@6", Text filename ] } 

最後一位是寫一個函數,命令和連接,並把它變成SqlCommand

let createSqlCommand cmd connection = 
    let sql = new SqlCommand(cmd.Query, connection) 
    sql.CommandTimeout <- cmd.Timeout 
    for name, par in cmd.Parameters do 
    let sqlTyp, value = 
     match par with 
     | Int n -> SqlDbType.Int, box n 
     | VarChar s -> SqlDbType.VarChar, box s 
     | Text s -> SqlDbType.Text, box s 
     | DateTime dt -> SqlDbType.DateTime, box dt 
    sql.Parameters.Add(name, sqlTyp).Value <- value 
    sql 

什麼是最好的方法將取決於你的使用情況 - 和與數據庫交互本質上是不純的,所以也許讓事情分離,不純是完全正常的。儘管如果您希望更加實用並專注於域(使用F#的強大的域驅動的建模一側),我想將其展示爲可能的選項。

+1

我使用這種技術來處理巨大基於F#類型的XML數量,它的工作非常出色。 –

+1

這太棒了!我開始使用它。一個跟進問題。由於SQLConnection支持Dispose,因此我一直在使用connection = new SqlConnection(connString)'''我怎麼符合這個想法? – user1443098

+1

@ user1443098這就是爲什麼我沒有在'Command'類型中包含'SqlConnection'的原因 - 這樣,您可以獨立配置該命令,然後使用'use'打開連接,調用'createSqlCommand'並運行SQL命令 - 然後關閉連接。仍然是必要的部分,但它被推到最後。 –