6

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

コードを目的のレベルにする方法と、その他の改善点をお勧めできますか?

4

1 に答える 1

8

オプションを使用して失敗した計算を表現することは、純粋に関数型の言語により適していると思います。F# では、計算が失敗したことを示すために例外を使用してもまったく問題ありません。

あなたのコードは単純に例外をNone値に変換しますが、実際にはこの状況を処理しません。これはコードの呼び出し元に任されています (呼び出し元は をどうするかを決定する必要がありますNone)。それらに例外を処理させることもできます。例外にさらに情報を追加したい場合は、独自の例外タイプを定義して、標準の例外を残す代わりにそれをスローできます。

以下は、新しい例外タイプとそれをスローする単純な関数を定義しています。

exception SqlUtilException of string

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

F# 機能を使用して単純化した単純な .NET スタイルを使用すると、コードははるかに単純に見えます。

// 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 シリーズを参照してください。

于 2012-09-23T23:20:21.170 に答える