2010-05-05 2 views
1

System.Transactions escalade notoirement les transactions impliquant plusieurs connexions à la même base de données vers le DTC. Le module et la classe d'assistance, ConnectionContext, ci-dessous sont destinés à empêcher cela en assurant que plusieurs demandes de connexion pour la même base de données retournent le même objet de connexion. C'est, dans un certain sens, mémo, bien qu'il y ait plusieurs choses mémoized et la seconde dépend de la première. Est-il possible de masquer la synchronisation et/ou l'état mutable (peut-être en utilisant la mémorisation) dans ce module, ou peut-être de le réécrire dans un style plus fonctionnel?Est-ce que quelque chose dans le genre de la mémoisation imbriquée est nécessaire ici?

(Il est peut-être ne vaut rien qu'il n'y a pas de blocage lors de l'obtention de la connexion par chaîne de connexion, car Transaction.Current est 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) 

Répondre

1

Oui, cela ressemble un peu à la mémorisation - la mémoisation doit toujours être implémentée en utilisant la mutation en F #, donc le fait que vous utilisiez des collections mutables n'est pas, en principe, un problème.

Je pense que vous pourriez essayer de le simplifier en recherchant des motifs répétés dans le code. Si je comprends bien, votre code implémente en réalité un cache à deux niveaux, dont la première clé est l'ID de la transaction et la deuxième clé est la chaîne de connexion. Vous pouvez essayer de le simplifier en créant un type qui implémente une mise en cache à un seul niveau, puis en composant votre gestionnaire de transactions en imbriquant deux fois le cache.

Je n'ai pas essayé réimplémenter dans tous les détails, mais un cache à un seul niveau pourrait ressembler à ceci:

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

Maintenant, je pense que votre TransactionManager peut être mis en œuvre en utilisant le type:

Cache<string, Cache<string, Connection>> 

Ce serait une bonne utilisation du principe de compositionnalité, qui est essentiel pour la programmation fonctionnelle. Je suppose que vous devrez peut-être rendre le type Cache un peu plus complexe (pour qu'il appelle une fonction que vous spécifiez dans diverses autres situations, par exemple lors de la suppression d'une valeur), mais en principe, vous pouvez commencer par implémenter votre gestionnaire. au dessus de la classe.

+0

Cela ressemble à un très bon point de départ. J'ai souvent découvert, lors de l'utilisation de l'état mutable, que je mettais en œuvre un motif déjà présent dans le cadre (p., paresseux, Seq.fold). Donc, je voulais voir s'il y avait un motif dans ce code que je néglige. – Daniel

+0

Je ne pense pas que cela soit implémenté n'importe où dans la bibliothèque, mais je pense que quelque chose de très similaire à mon code apparaît dans Expert F #. Un exemple qui montre la mémorisation dans ma programmation fonctionnelle du monde réel implémente le motif simplement comme une fonction qui prend une fonction (qui ne permet pas de supprimer des valeurs mémorisées). Je suppose que la mémorisation nécessite un réglage (par exemple, comment vous stockez les valeurs, comment vous les supprimez, ...), donc il n'y a pas de classe intégrée pour cela (car il peut ne pas répondre aux besoins dans de nombreux cas). –

0

Je suis pas clair quel critère que vous utilisez pour déclarer " amélioration "à ceci.

D'un coup, ça me semble peut-être buggé; si je fais des appels à getConnection sur deux threads différents (aucun n'a une Transaction.Current) avec la même chaîne de connexion, j'obtiens deux connexions, non? Ou peut-être est-ce un by-design, et vous essayez simplement de 'réutiliser' les connexions quand il y a déjà une Transaction.Current dans TLS? Dans ce cas, il semble que votre dictionnaire pourrait également être ThreadStatic et supprimer tous les verrous locaux?

Je suppose que j'aimerais voir le code client et le comportement client souhaité (réel ou idéal).

+0

Oui, ce que vous avez remarqué est le comportement souhaité. Je n'ai pas utilisé de dictionnaire ThreadStatic car l'événement TransactionCompleted se déclenche sur un thread différent. ConnectionManager.getConnection est appelée à partir d'une classe semblable à DataProvider qui a une chaîne de connexion associée. Le but de getConnection est de renvoyer une connexion mise en cache si elle est appelée dans une transaction, sinon renvoyer une nouvelle connexion (en supposant que l'on se fie au pool de connexions pour plus d'efficacité). Est-ce qu'il y a une meilleure approche? – Daniel

+0

Pour répondre à votre première phrase: Je suis à la recherche de modèles dans le code qui sont implémentés dans le framework, ou pour lesquels il existe des idiomes fonctionnels. – Daniel

Questions connexes