2012-09-23 83 views
5

我写了下面的代码在F#中执行一个SQLServer StoredProc数据库连接和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 

我有这个代码

  1. 首先重复使用Option.bind的多重问题非常刺激......并且增加了噪音。我需要一个更清晰的方法来检查输出是否为None,如果不是则继续。

  2. 最后应该有一个清理功能,我应该能够关闭+配置阅读器,命令和连接。但是现在在管道的尽头,我只有读者。

  3. 正在添加参数的函数...它看起来像修改了命令参数的“状态”,因为返回类型仍然是发送给它的同一个命令......并带有一些添加状态。我不知道一个更有经验的功能程序员是如何做到这一点的。

  4. Visual Studio在我做异常处理的每个地方给我一个警告。这有什么错,”它说

这种类型的测试或沮丧的总是拥有

我想这段代码的方式看是这样的

设X:MyRecord序列=的getConnection‘CON’ |> GetCommand “CMD” |> AddParameter “@name” SqlDbType.NVarchar 50 |> AddParameter “@policyname” SqlDbType.NVarchar 50 |>的ExecuteReader |> FunctionToReadAndGenerateSeq |> CleanEverything

你能推荐我怎么才能把我的代码达到所需的水平,也是其他任何改进?

回答

7

我认为使用选项来表示失败的计算更适合纯粹的功能语言。在F#中,使用异常来表示计算失败是完全正确的。

您的代码只是将异常变成None的值,但它并不能真正处理这种情况 - 这由您的代码调用者决定(需要决定如何处理None)。你可能只是让他们处理这个异常。如果您想向异常添加更多信息,您可以定义自己的异常类型并抛出异常,而不是保留标准异常。

下面定义了一个新的异常类型和一个简单的功能,把它扔到:

exception SqlUtilException of string 

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

使用普通的.NET风格,使用F#特性的一些简化,代码看起来简单得多:

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

它看起来更像是.NET代码,但这非常好。处理F#中的数据库将使用命令式的风格,并试图隐藏它,只会使代码混淆。现在,有一些你可以使用其他整齐的F#特性 - 尤其是对于动态运营商?支持,这将使你是这样的:

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 ] 

有关更多信息,请参阅以下MSDN系列: