我编写了以下代码来在F#中执行SQLServer StoredProc
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
Run Code Online (Sandbox Code Playgroud)
我有这个代码的多个问题
首先,重复使用Option.bind是非常恼人的......并且正在增加噪音.我需要一种更清晰的方法来检查输出是否为None,如果不是则继续.
最后应该有一个清理功能,我应该能够关闭+处理读卡器,命令和连接.但目前在管道的最后我只有读者.
添加参数的函数......看起来它正在修改命令参数的"状态",因为返回类型仍然是发送它的相同命令...带有一些添加状态.我想知道一个更有经验的功能程序员会如何做到这一点.
Visual Studio在我执行异常处理的每个地方都给出了警告.这有什么不对"它说
此类型测试或向下转换将始终保持
我想要这个代码的方式是这样的
let x:MyRecord seq = GetConnection"con"|> GetCommand"cmd"|> AddParameter"@ name"SqlDbType.NVarchar 50 |> AddParameter"@policyname"SqlDbType.NVarchar 50 |> ExecuteReader |> FunctionToReadAndGenerateSeq |> CleanEverything
您能否建议我如何将我的代码提升到所需级别以及其他任何改进?
我认为使用选项来表示失败的计算更适合纯粹的功能语言.在F#中,使用异常来表示计算失败是完全正确的.
您的代码只是将异常转换为None值,但它并没有真正处理这种情况 - 这是由代码的调用者(谁将需要决定如何处理None).您也可以让他们处理异常.如果要向异常添加更多信息,可以定义自己的异常类型并抛出该异常类型,而不是保留标准异常.
下面定义了一个新的异常类型和一个抛出它的简单函数:
exception SqlUtilException of string
// This supports the 'printf' formatting style
let raiseSql fmt =
Printf.kprintf (SqlUtilException >> raise) fmt
Run Code Online (Sandbox Code Playgroud)
使用简单的.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
()
Run Code Online (Sandbox Code Playgroud)
它看起来更像.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 ]
Run Code Online (Sandbox Code Playgroud)
有关此内容的详细信息,请参阅以下MSDN系列: