2012-09-23 19 views
5

yo escribimos el siguiente código para ejecutar un StoredProc SQLServer en Fa #Conexiones de base de datos y 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 

tengo varios problemas con este código

  1. En primer lugar el uso repetido de Option.bind es muy irritante ... y está agregando ruido. Necesito una forma más clara de verificar si la salida fue None y si no, proceder.

  2. Al final debería haber una función de limpieza en la que debería ser capaz de cerrar + desechar el lector, el comando y la conexión. Pero actualmente, al final de la tubería, todo lo que tengo es el lector.

  3. La función que agrega parámetros ... parece que está modificando el "estado" del parámetro de comando porque el tipo de devolución sigue siendo el mismo comando que se le envió ... con algún estado agregado. Me pregunto cómo un programador funcional más experimentado hubiera hecho esto.

  4. Visual Studio me avisa en cada uno de los lugares donde realizo el manejo de excepciones. lo que hay de malo en eso" dice

Este ensayo de tipo o abatido siempre tendrá

La forma que yo quiero este código para buscar es esta

Sea X: MyRecord ss = getConnection 'con' |> GetCommand "cmd" |> addParameter "@ name" SqlDbType.NVarchar 50 |> addParameter "@policyname" SqlDbType.NVarchar 50 |> ExecuteReader |> FunctionToReadAndGenerateSeq |> CleanEverything

me puede recomendar cómo puedo llevar a mi código al nivel deseado y también cualquier otro ¿mejora?

Respuesta

7

Creo que el uso de opciones para representar cálculos fallidos es más adecuado para lenguajes puramente funcionales. En F #, está perfectamente bien usar excepciones para indicar que un cálculo ha fallado.

Su código simplemente convierte las excepciones en valores de None, pero realmente no soluciona esta situación; esto se deja a la persona que llama de su código (quien tendrá que decidir qué hacer con None). También puede dejarles manejar la excepción. Si desea agregar más información a la excepción, puede definir su propio tipo de excepción y lanzarla en lugar de dejar las excepciones estándar.

A continuación se define un nuevo tipo de excepción y una función simple para tirarlo:

exception SqlUtilException of string 

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

Usando el estilo .NET llano con algunas simplificaciones utilizando F # características, el código es mucho más simple:

// 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 
() 

Se parece más al código .NET, pero eso está perfectamente bien. Tratar con bases de datos en F # va a usar un estilo imperativo y tratar de ocultarlo solo hará que el código sea confuso.Ahora, hay una serie de otros # interesantes características F que puede utilizar - en especial el apoyo a los operadores dinámicos ?, lo que le daría algo así como:

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 ] 

Para obtener más información al respecto, consulte la siguiente serie de MSDN:

Cuestiones relacionadas