Session par requête avec SqlConnection / System.Transactions

dapper sqlconnection sql-server system.transactions

Question

Je viens de commencer à utiliser Dapper pour un projet, ayant surtout utilisé des ORM comme NHibernate et EF ces dernières années.

Généralement, dans nos applications Web, nous implémentons la session par requête, en commençant une transaction au début de la requête et en la validant à la fin.

Devrions-nous faire quelque chose de similaire en travaillant directement avec SqlConnection / System.Transactions?

Comment StackOverflow le fait-il?

Solution

Sur les conseils de @gbn et @Sam Safron, je n'utilise pas de transactions. Dans mon cas, je ne fais que des requêtes de lecture, il semble donc qu'il n'y ait pas de véritable obligation d'utiliser des transactions (contrairement à ce que l'on m'a dit sur les transactions implicites).

Je crée une interface de session légère pour pouvoir utiliser une connexion par requête. Ceci est tout à fait bénéfique pour moi, car avec Dapper, j'ai souvent besoin de créer quelques requêtes différentes pour créer un objet et partager plutôt la même connexion.

Le travail de détermination de la connexion par requête et de son élimination est effectué par mon conteneur IoC (StructureMap):

public interface ISession : IDisposable {
    IDbConnection Connection { get; }
}

public class DbSession : ISession {

    private static readonly object @lock = new object();
    private readonly ILogger logger;
    private readonly string connectionString;
    private IDbConnection cn;

    public DbSession(string connectionString, ILogger logger) {
        this.connectionString = connectionString;
        this.logger = logger;
    }

    public IDbConnection Connection { get { return GetConnection(); } }

    private IDbConnection GetConnection() {
        if (cn == null) {
            lock (@lock) {
                if (cn == null) {
                    logger.Debug("Creating Connection");
                    cn = new SqlConnection(connectionString);
                    cn.Open();
                    logger.Debug("Opened Connection");
                }
            }
        }

        return cn;
    }

    public void Dispose() {
        if (cn != null) {
            logger.Debug("Disposing connection (current state '{0}')", cn.State);
            cn.Dispose();
        }
    }
}

Réponse acceptée

Ceci est ce que nous faisons:

Nous définissons un DB appelé statique sur un objet appelé Current

public static DBContext DB
{
    var result = GetContextItem<T>(itemKey);

    if (result == null)
    {
        result = InstantiateDB();
        SetContextItem(itemKey, result);
    }

    return result;
}

public static T GetContextItem<T>(string itemKey, bool strict = true)
{

#if DEBUG // HttpContext is null for unit test calls, which are only done in DEBUG
    if (Context == null)
    {
        var result = CallContext.GetData(itemKey);
        return result != null ? (T)result : default(T);
    }
    else
    {
#endif
        var ctx = HttpContext.Current;
        if (ctx == null)
        {
            if (strict) throw new InvalidOperationException("GetContextItem without a context");
            return default(T);
        }
        else
        {
            var result = ctx.Items[itemKey];
            return result != null ? (T)result : default(T);
        }
#if DEBUG
    }
#endif
}

public static void SetContextItem(string itemKey, object item)
{
#if DEBUG // HttpContext is null for unit test calls, which are only done in DEBUG
    if (Context == null)
    {
        CallContext.SetData(itemKey, item);
    }
    else
    {
#endif
        HttpContext.Current.Items[itemKey] = item;

#if DEBUG
    }
#endif
}

Dans notre cas InstantiateDB retourne un contexte L2S, mais dans votre cas , il pourrait être une ouverture SQLConnection ou autre.

Sur notre objet d'application, nous nous assurons que notre connexion est fermée à la fin de la demande.

   protected void Application_EndRequest(object sender, EventArgs e)
   {
        Current.DisposeDB(); // closes connection, clears context 
   }

Alors n'importe où dans votre code où vous avez besoin d'accéder à la base de données, vous appelez simplement Current.DB et tout fonctionne automatiquement. Ceci est également compatible avec les tests unitaires en raison de tous les éléments #if DEBUG .


Nous ne commençons aucune transaction par session, si nous le faisions et si nous avions des mises à jour au début de notre session, nous aurions de sérieux problèmes de verrouillage, car les verrous ne seraient pas publiés avant la fin.


Réponse populaire

Vous ne pouvez démarrer une transaction SQL Server que lorsque vous devez utiliser quelque chose comme TransactionScope lorsque vous appelez la base de données avec un appel "écriture".

Voir un exemple aléatoire dans cette question récente: Pourquoi une transaction imbriquée est-elle validée même si TransactionScope.Complete () n'est jamais appelée?

Vous ne souhaitez pas ouvrir une connexion et démarrer une transaction par requête http. Seulement sur demande. Je vais avoir du mal à comprendre pourquoi certains préconisent populaire l' ouverture d' une transaction de base de données par session: idiotie quand vous regardez ce qu'est une transaction de base de données est

Note: Je ne suis pas contre le modèle en soi. Je suis contre les transactions de base de données côté client inutiles et trop longues qui invoquent MSDTC




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi