1

众所周知,System.Transactions 将涉及到同一数据库的多个连接的事务升级到 DTC。下面的模块和帮助程序类ConnectionContext旨在通过确保对同一数据库的多个连接请求返回相同的连接对象来防止这种情况。从某种意义上说,这就是记忆,尽管有很多东西被记忆,第二个依赖于第一个。有没有办法在这个模块中隐藏同步和/或可变状态(可能使用记忆),或者可能以更实用的风格重写它?

(通过连接字符串获取连接时没有锁定可能一文不值,因为 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# 中使用突变来实现,所以原则上你使用可变集合这一事实不是问题。

我认为您可以尝试通过在代码中查找重复模式来简化它。如果我理解的话,您的代码实际上实现了两级缓存,其中第一个键是事务 ID,第二个键是连接字符串。您可以尝试通过创建一个实现单级缓存的类型来简化它,然后通过两次嵌套缓存来组成您的事务管理器。

我没有尝试在所有细节中重新实现它,但单级缓存可能如下所示:

// 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

我不清楚您使用什么标准来声明对此进行“改进”。

对我来说,它看起来可能是错误的;getConnection如果我用相同的连接字符串调用两个不同的线程(都没有 Transaction.Current),我得到两个连接,对吗?或者也许这是设计使然,当 TLS 中已经存在 Transaction.Current 时,您只是试图“重用”连接?在那种情况下,您的字典似乎也可以ThreadStatic删除所有本地锁定?

我想我想看看客户端代码和期望的客户端行为(实际的或理想化的)。

于 2010-05-05T22:26:32.497 回答