这里需要嵌套备忘录吗?

时间:2010-05-05 22:10:55

标签: f# memoization system.transactions threadstatic

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

(通过连接字符串获取连接时没有任何锁定可能是没有价值的,因为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)

2 个答案:

答案 0 :(得分:1)

是的,它看起来有点像memoization - memoization总是必须使用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类型更复杂一些(以便它调用你在各种其他情况下指定的函数,例如删除值时),但原则上,你可以从尝试实现开始你的经理使用上述课程。

答案 1 :(得分:0)

我不清楚你用什么标准来宣布对此的“改进”。

对我而言,它看起来似乎有点儿麻烦;如果我在具有相同连接字符串的两个不同线程(没有Transaction.Current)上调用getConnection,我会得到两个连接,对吧?或者这可能是设计,当你在TLS中已经有一个Transaction.Current时,你只是试图“重用”连接?在这种情况下,您的字典似乎也可能是ThreadStatic并删除所有本地锁定?

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