Нужно ли здесь что-то вроде вложенной памяти? - PullRequest
1 голос
/ 06 мая 2010

System.Transaction заведомо увеличивает количество транзакций, связанных с несколькими подключениями к одной и той же базе данных с 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)

Ответы [ 2 ]

1 голос
/ 06 мая 2010

Да, это похоже на запоминание - запоминание всегда должно быть реализовано с использованием мутации в F #, поэтому тот факт, что вы используете изменяемые коллекции, в принципе не является проблемой.

Я думаю, вы могли бы попытаться упростить это, ища повторяющиеся шаблоны в коде. Если я понимаю, ваш код фактически реализует двухуровневый кеш, если первый ключ - это идентификатор транзакции, а второй - строка подключения. Вы можете попытаться упростить его, создав тип, который реализует одноуровневое кэширование, а затем составьте свой менеджер транзакций, вложив кэш два раза.

Я не пытался повторно реализовать его во всех деталях, но одноуровневый кеш может выглядеть так:

// 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 немного более сложным (чтобы он вызывал функцию, указанную вами в различных других ситуациях, например, при удалении значения), но в принципе вы могли бы начать с попытки реализовать свой менеджер используя вышеуказанный класс.

0 голосов
/ 06 мая 2010

Мне неясно, какой критерий вы используете, чтобы объявить "улучшение" в этом.

Мне показалось, что это не так. если я выполняю вызовы getConnection в двух разных потоках (ни один из них не имеет Transaction.Current) с одинаковой строкой соединения, я получаю два соединения, верно? Или, может быть, это не совсем так, и вы просто пытаетесь «повторно использовать» соединения, когда в TLS уже есть Transaction.Current? В таком случае кажется, что ваш словарь также может быть ThreadStatic и удалить все локальные блокировки?

Полагаю, мне хотелось бы увидеть код клиента и желаемое поведение клиента (действительное или идеализированное).

...