数据库连接和F#

6
我编写了以下代码来在F#中执行SQLServer存储过程:
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. 最后应该有一个cleanupfunction,我应该能够关闭+释放reader、command和connection。但目前,在管道的末尾,我只有reader。

  3. 添加参数的函数...看起来像是修改了命令参数的“状态”,因为返回类型仍然是发送的相同命令...带有一些添加的状态。我想知道一个更有经验的函数式程序员将如何完成这个任务。

  4. Visual Studio在每个处理异常的地方都给我一个警告。这有什么问题?它说

这种类型测试或向下转换始终有效

我希望代码看起来像这样:

let x:MyRecord序列= GetConnection“con”|> GetCommand“cmd”|> AddParameter“@name”SqlDbType.NVarchar 50 |> AddParameter“@policyname”SqlDbType.NVarchar 50 |> ExecuteReader |> FunctionToReadAndGenerateSeq |> CleanEverything

您能推荐我如何将代码提升到所需的水平,以及任何其他改进吗?

1个回答

8
我认为使用选项来表示失败的计算更适合于纯函数式语言。在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系列:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接