1

System.Transactionsは、同じデータベースへの複数の接続を含むトランザクションをDTCにエスカレートすることで有名です。以下のモジュールとヘルパークラスはConnectionContext、同じデータベースに対する複数の接続要求が同じ接続オブジェクトを返すようにすることで、これを防ぐことを目的としています。これは、ある意味でメモ化ですが、メモ化されるものは複数あり、2番目は最初のものに依存しています。このモジュールで同期および/または可変状態(おそらくメモ化を使用)を非表示にする方法、またはより機能的なスタイルで書き直す方法はありますか?

(Transaction.Currentはであるため、接続文字列で接続を取得するときにロックがないことは何の価値もありませんThreadStatic。)

type ConnectionContext(connection:IDbConnection, ownsConnection) =
    member x.Connection = connection
    member x.OwnsConnection = ownsConnection
    interface IDisposable with
        member x.Dispose() = if ownsConnection then connection.Dispose()

module ConnectionManager =
    let private _connections = new Dictionary<string, Dictionary<string, IDbConnection>>()

    let private getTid (t:Transaction) = t.TransactionInformation.LocalIdentifier

    let private removeConnection tid =
        let cl = _connections.[tid]
        for (KeyValue(_, con)) in cl do
            con.Close()
        lock _connections (fun () -> _connections.Remove(tid) |> ignore)

    let getConnection connectionString (openConnection:(unit -> IDbConnection)) =
        match Transaction.Current with
        | null -> new ConnectionContext(openConnection(), true)
        | current ->
            let tid = getTid current

            // get connections for the current transaction
            let connections = 
                match _connections.TryGetValue(tid) with
                | true, cl -> cl
                | false, _ -> 
                    let cl = Dictionary<_,_>()
                    lock _connections (fun () -> _connections.Add(tid, cl))
                    cl

            // find connection for this connection string
            let connection =
                match connections.TryGetValue(connectionString) with
                | true, con -> con
                | false, _ ->
                    let initial = (connections.Count = 0)
                    let con = openConnection()
                    connections.Add(connectionString, con)
                    // if this is the first connection for this transaction, register connections for cleanup
                    if initial then 
                        current.TransactionCompleted.Add 
                            (fun args -> 
                                let id = getTid args.Transaction
                                removeConnection id)
                    con

            new ConnectionContext(connection, false)
4

2 に答える 2

1

はい、メモ化に少し似ています。メモ化は常にF#のミューテーションを使用して実装する必要があるため、変更可能なコレクションを使用しているという事実は、原則として問題にはなりません。

コード内で繰り返されるパターンを探すことで、単純化してみることができると思います。私が理解している場合、コードは実際には2レベルのキャッシュを実装しています。最初のキーはトランザクションIDで、2番目のキーは接続文字列です。シングルレベルのキャッシュを実装するタイプを作成し、キャッシュを2回ネストしてトランザクションマネージャーを構成することで、単純化を試みることができます。

すべての詳細で再実装しようとはしませんでしたが、単一レベルのキャッシュは次のようになります。

// Takes a function that calculates a value for a given 'Key
// when it is not available (the function also gets a flag indicating
// whether it is the first one, so that you can register it with transaction0
type Cache<´Key, ´Value when ´Key : equality>(createFunc) =
  let dict = new Dictionary<´Key, ´Value>()
  // Utility function that implements global lock for the object
  let locked = 
    let locker = new obj()
    (fun f -> lock locker f)

  member x.Remove(key) = 
    locked (fun () -> dict.Remove(key))

  // Get item from the cache using the cache.Item[key] syntax
  member x.Item
    with get(key) = 
      match dict.TryGetValue(key) with
      | true, res -> res
      | false, _ ->
          // Calculate the value if it is not already available
          let res = createFunc (dict.Count = 0) key
          locked (fun () -> dict.Add(key, res))
          res

さて、私はあなたTransactionManagerがタイプを使って実装されるかもしれないと思います:

Cache<string, Cache<string, Connection>>

これは、関数型プログラミングに不可欠な構成性原理の優れた使用法です。Cache型をもう少し複雑にする必要があるかもしれませんが(値を削除するときなど、他のさまざまな状況で指定する関数を呼び出すため)、原則として、上記を使用してマネージャーを実装することから始めることができますクラス。

于 2010-05-05T22:44:36.003 に答える
0

これに対する「改善」を宣言するためにどの基準を使用しているかはわかりません。

手に負えないように見えるかもしれません-私にはバギーです。同じ接続文字列を使用して2つの異なるスレッド(どちらにもTransaction.Currentがない)でを呼び出すとgetConnection、2つの接続が得られますよね?または、それは設計によるものであり、TLSにTransaction.Currentがすでに存在する場合に、接続を「再利用」しようとしているだけですか?その場合、あなたの辞書も可能でThreadStaticあり、すべてのローカルロックを削除できるように思われますか?

クライアントコードと望ましいクライアントの動作(実際または理想化)を見たいと思います。

于 2010-05-05T22:26:32.497 に答える