Нужно ли здесь что-то вроде вложенной памяти?
Известно, что 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)
2 ответа
Да, это немного похоже на запоминание - запоминание всегда должно быть реализовано с использованием мутации в 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
введите немного более сложный (чтобы он вызывал функцию, указанную вами в различных других ситуациях, например, при удалении значения), но в принципе вы могли бы начать с попытки реализовать свой менеджер, используя приведенный выше класс.
Мне неясно, какой критерий вы используете, чтобы объявить "улучшение" в этом.
Вне руки это выглядит, может быть, глючным для меня; если я позвоню getConnection
в двух разных потоках (ни один из них не имеет Transaction.Current) с одной и той же строкой подключения, я получаю два подключения, верно? Или, может быть, это не совсем так, и вы просто пытаетесь "повторно использовать" соединения, когда в TLS уже есть Transaction.Current? В этом случае кажется, что ваш словарь также может быть ThreadStatic
и удалить все локальные блокировки?
Я предполагаю, что хотел бы видеть код клиента и желаемое поведение клиента (актуальное или идеализированное).