这里需要类似嵌套记忆的东西吗?

发布于 2024-08-31 15:05:41 字数 2472 浏览 6 评论 0原文

众所周知,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)

System.Transactions notoriously escalates transactions involving multiple connections to the same database to the DTC. The module and helper class, ConnectionContext, below are meant to prevent this by ensuring multiple connection requests for the same database return the same connection object. This is, in some sense, memoization, although there are multiple things being memoized and the second is dependent on the first. Is there some way to hide the synchronization and/or mutable state (perhaps using memoization) in this module, or perhaps rewrite it in a more functional style?

(It may be worth nothing that there's no locking when getting the connection by connection string because Transaction.Current is 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)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

恏ㄋ傷疤忘ㄋ疼 2024-09-07 15:05:44

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

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

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

I'm unclear what criterion you're using to declare an "improvement" to this.

Offhand it looks maybe-buggy to me; if I make calls to getConnection on two different threads (neither has a Transaction.Current) with the same connection string, I get two connections, right? Or maybe that's by-design, and you're just trying to 'reuse' connections when there's already a Transaction.Current in TLS? In that case seems like your dictionary could also be ThreadStatic and remove all the local locking?

I guess I would like to see the client code and desired client behavior (actual or idealized).

错々过的事 2024-09-07 15:05:43

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

Yes, it looks a bit like memoization - memoization always has to be implemented using mutation in F#, so the fact that you're using mutable collections isn't, in principle, a problem.

I think you could try simplifying it by looking for repeated patterns in the code. If I understand it, your code actually implements a two-level cache, wheree the first key is the transaction ID and the second key is the connection string. You could try simplifying it by creating a type that implements a single-level caching and then composing your transaction manager by nesting the cache two times.

I didn't try re-implementing it in all the details, but a single-level cache might look like this:

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

Now, I think that your TransactionManager may be implemented using the type:

Cache<string, Cache<string, Connection>>

This would be a nice use of compositionality principle, which is essential for functional programming. I guess you may need to make the Cache type a bit more complex (so that it calls a function you specify in various other situations, e.g. when removing a value), but in principle, you could start by trying to implement your manager using the above class.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文