2012-09-23 11 views
5

ho scritto il seguente codice per eseguire uno StoredProc SQLServer in F #Connessioni di database e F #

module SqlUtility = 
    open System 
    open System.Data 
    open System.Data.SqlClient 

    SqlUtility.GetSqlConnection "MyDB" 
    |> Option.bind (fun con -> SqlUtility.GetSqlCommand "dbo.usp_MyStordProc" con) 
    |> Option.bind (fun cmd -> 
     let param1 = new SqlParameter("@User", SqlDbType.NVarChar, 50) 
     param1.Value <- user 
     cmd.Parameters.Add(param1) |> ignore 
     let param2 = new SqlParameter("@PolicyName", SqlDbType.NVarChar, 10) 
     param2.Value <- policyName 
     cmd.Parameters.Add(param2) |> ignore 
     Some(cmd) 
    ) 
    |> Option.bind (fun cmd -> SqlUtility.ExecuteReader cmd) 
    |> Option.bind (fun rdr -> ExtractValue rdr)   

    let GetSqlConnection (conName : string) = 
    let conStr = ConfigHandler.GetConnectionString conName 
    try 
     let con = new SqlConnection(conStr) 
     con.Open() 
     Some(con) 
    with 
    | :? System.Exception as ex -> printfn "Failed to connect to DB %s with Error %s " conName ex.Message; None 
    | _ -> printfn "Failed to connect to DB %s" conName; None 

    let GetSqlCommand (spName : string) (con : SqlConnection) =  
    let cmd = new SqlCommand() 
    cmd.Connection <- con 
    cmd.CommandText <- spName 
    cmd.CommandType <- CommandType.StoredProcedure 
    Some(cmd) 

    let AddParameters (cmd : SqlCommand) (paramList : SqlParameter list) = 
    paramList |> List.iter (fun p -> cmd.Parameters.Add p |> ignore) 

    let ExecuteReader (cmd : SqlCommand) = 
    try 
     Some(cmd.ExecuteReader()) 
    with 
    | :? System.Exception as ex -> printfn "Failed to execute reader with error %s" ex.Message; None 

ho più problemi con questo codice

  1. In primo luogo l'uso ripetuto di Option.bind è molto irritante ... e aggiunge rumore Ho bisogno di un modo più chiaro per verificare se l'output fosse None e, in caso contrario, procedere.

  2. Alla fine dovrebbe esserci una funzione di pulizia in cui dovrei essere in grado di chiudere + disporre il lettore, il comando e la connessione. Ma attualmente alla fine della pipeline tutto quello che ho è il lettore.

  3. La funzione che aggiunge parametri ... sembra che stia modificando lo "stato" del parametro di comando perché il tipo di ritorno è sempre lo stesso comando che è stato inviato ... con qualche stato aggiunto. Mi chiedo come avrebbe fatto un programmatore funzionale più esperto.

  4. Visual Studio mi invia un avviso in ognuno dei punti in cui gestisco le eccezioni. cosa c'è di sbagliato in questo" dice

Questo test tipo o abbattuti saranno sempre tenere

Il modo in cui voglio questo codice a guardare è questo

Sia X: MyRecord seq = GetConnection 'con' |> GetCommand "cmd" |> addParameter "@name" SqlDbType.NVarchar 50 |> addParameter "@policyname" SqlDbType.NVarchar 50 |> ExecuteReader |> FunctionToReadAndGenerateSeq |> CleanEverything

Potete consigliarmi come posso prendere il mio codice al livello desiderato e anche a qualsiasi altro miglioramento?

risposta

7

Penso che l'utilizzo delle opzioni per rappresentare i calcoli falliti sia più adatto alle lingue puramente funzionali. In F #, è perfettamente bene usare le eccezioni per indicare che un calcolo ha fallito.

Il codice si trasforma semplicemente eccezioni in None valori, ma in realtà non gestire questa situazione - questo è lasciato al chiamante del codice (che avrà bisogno di decidere cosa fare con None). Puoi anche lasciargli gestire l'eccezione. Se si desidera aggiungere ulteriori informazioni all'eccezione, è possibile definire il proprio tipo di eccezione e lanciarlo anziché lasciare le eccezioni standard.

Quanto segue definisce un nuovo tipo di eccezione e una funzione semplice da buttare:

exception SqlUtilException of string 

// This supports the 'printf' formatting style  
let raiseSql fmt = 
    Printf.kprintf (SqlUtilException >> raise) fmt 

Usando stile semplice .NET con alcune semplificazioni utilizzando F # caratteristiche, il codice è molto più semplice:

// Using 'use' the 'Dispose' method is called automatically 
let connName = ConfigHandler.GetConnectionString "MyDB" 
use conn = new SqlConnection(connName) 

// Handle exceptions that happen when opening the connection 
try conn.Open() 
with ex -> raiseSql "Failed to connect to DB %s with Error %s " connName ex.Message 

// Using object initializer, we can nicely set the properties 
use cmd = 
    new SqlCommand(Connection = conn, CommandText = "dbo.usp_MyStordProc", 
        CommandType = CommandType.StoredProcedure) 

// Add parameters 
// (BTW: I do not think you need to set the type - this will be infered) 
let param1 = new SqlParameter("@User", SqlDbType.NVarChar, 50, Value = user) 
let param2 = new SqlParameter("@PolicyName", SqlDbType.NVarChar, 10, Value = policyName) 
cmd.Parameters.AddRange [| param1; param2 |] 

use reader = 
    try cmd.ExecuteReader() 
    with ex -> raiseSql "Failed to execute reader with error %s" ex.Message 

// Do more with the reader 
() 

Sembra più simile al codice .NET, ma va benissimo. Trattare con i database in F # sta per usare lo stile imperativo e cercare di nascondere il che renderà solo il codice confuso.Ora, c'è un certo numero di altre # caratteristiche pulito F è possibile utilizzare - in particolare il supporto per gli operatori dinamici ?, che ti dia qualcosa come:

let connName = ConfigHandler.GetConnectionString "MyDB" 

// A wrapper that provides dynamic access to database 
use db = new DynamicDatabase(connName) 

// You can call stored procedures using method call syntax 
// and pass SQL parameters as standard arguments 
let rows = db.Query?usp_MyStordProc(user, policy) 

// You can access columns using the '?' syntax again 
[ for row in rows -> row?Column1, row?Column2 ] 

Per ulteriori informazioni su questo, vedere la seguente serie di MSDN:

Problemi correlati