Sesión por solicitud con SqlConnection / System.Transactions

dapper sqlconnection sql-server system.transactions

Pregunta

Acabo de empezar a utilizar Dapper para un proyecto, ya que en su mayoría he usado ORM como NHibernate y EF durante los últimos años.

Por lo general, en nuestras aplicaciones web implementamos sesión por solicitud, comenzamos una transacción al inicio de la solicitud y la comprometemos al final.

¿Deberíamos hacer algo similar cuando trabajamos directamente con SqlConnection / System.Transactions?

¿Cómo lo hace StackOverflow?

Solución

Tomando el consejo de @gbn y @Sam Safron, no estoy usando transacciones. En mi caso, solo realizo consultas de lectura, por lo que parece que no hay ningún requisito real para usar las transacciones (a diferencia de lo que me han dicho sobre las transacciones implícitas).

Creo una interfaz de sesión ligera para que pueda usar una conexión por solicitud. Esto es bastante beneficioso para mí, ya que con Dapper a menudo necesito crear algunas consultas diferentes para construir un objeto y preferiría compartir la misma conexión.

El trabajo de determinar el alcance de la conexión por solicitud y eliminarlo lo realiza mi contenedor 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();
        }
    }
}

Respuesta aceptada

Esto es lo que hacemos:

Definimos una DB llamada estática en un objeto llamado 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
}

En nuestro caso, InstantiateDB devuelve un contexto L2S, sin embargo, en su caso podría ser una SQLConnection abierta o lo que sea.

En nuestro objeto de aplicación, nos aseguramos de que nuestra conexión se cierre al final de la solicitud.

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

Luego, en cualquier parte de tu código donde necesites acceder a la base de datos, simplemente llama a Current.DB y todo funciona automáticamente. Esto también es #if DEBUG para pruebas unitarias debido a todas las cosas #if DEBUG .


No iniciamos ninguna transacción por sesión, si lo hiciéramos y tuviéramos actualizaciones al comienzo de nuestra sesión, tendríamos serios problemas de bloqueo, ya que los bloqueos no se liberarían hasta el final.


Respuesta popular

Solo iniciaría una transacción de SQL Server cuando lo necesite con algo como TransactionScope cuando llame a la base de datos con una llamada de "escritura".

Vea un ejemplo al azar en esta pregunta reciente: ¿Por qué se ha cometido una transacción anidada incluso si nunca se llama a TransactionScope.Complete ()?

Usted no abrir una conexión e iniciar una transacción por la petición http. Solo bajo demanda Tengo dificultades para entender por qué algunos defensores del folklore abren una transacción de base de datos por sesión: pura idiotez cuando miras qué es una transacción de base de datos

Nota: No estoy en contra del patrón per se. Estoy en contra de las transacciones innecesarias, demasiado largas, de bases de datos del lado del cliente que invocan MSDTC



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué