【问题标题】:Database Connections and F#数据库连接和 F#
【发布时间】:2012-09-23 22:29:47
【问题描述】:

我编写了以下代码在 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

这段代码有多个问题

  1. 首先,重复使用 Option.bind 非常烦人……而且会增加噪音。我需要一种更清晰的方法来检查输出是否为 None,如果不是,则继续。

  2. 最后应该有一个清理功能,我应该能够关闭 + 处置阅读器、命令和连接。但目前在管道的最后,我所拥有的只是阅读器。

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

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

这种类型测试或向下转换将始终保持

我希望这段代码看起来是这样的

let x : MyRecord seq = GetConnection "con" |> GetCommand "cmd" |> AddParameter "@name" SqlDbType.NVarchar 50 |> AddParameter "@policyname" SqlDbType.NVarchar 50 |> ExecuteReader |> FunctionToReadAndGenerateSeq |> CleanEverything

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

【问题讨论】:

    标签: f#


    【解决方案1】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-10-01
      • 2015-02-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-01
      • 1970-01-01
      相关资源
      最近更新 更多