Administrar la conexión con consultas sin búfer en Dapper

.net ado.net c# dapper micro-orm

Pregunta

Recientemente comencé a usar Dapper , todo parece agradable y fácil, pero hay una cosa que me sigue confundiendo: Connection Management.

Según la documentación :

Dapper no administra el ciclo de vida de su conexión, supone que la conexión que recibe está abierta Y no tiene enumeradores de datos existentes (a menos que MARS esté habilitado)

A la luz de esto comencé a hacer esto dentro de la implementación de mis métodos de repositorio:

using (var db = new SqliteConnection(connectionString)) {
    // call Dapper methods here
}

Luego encontré una tabla con una gran cantidad de registros, así que pensé en devolver un IEnumerable<T> pasando el método buffered: false al método Query<> , y cuando comencé a enumerar el enumerable en el front end, auge una excepción diciendo que la conexión se cerró y eliminó, lo que se espera, ya que estoy envolviendo mis llamadas con el bloque de uso anterior.

Pregunta: ¿La mejor manera de resolver esto?
Pregunta complementaria: ¿Es la forma en que manejo la conexión la mejor manera de hacerlo?

Respuesta aceptada

Yo ofrecería este patrón de repositorio:

public class Repository
{
    private readonly string _connectionString;

    public Repository(string connectionString)
    {
        _connectionString = connectionString;
    }

    protected T GetConnection<T>(Func<IDbConnection, T> getData)
    {
        using (var connection = new SqlConnection(_connectionString))
        {
            connection.Open();
            return getData(connection);
        }
    }

    protected TResult GetConnection<TRead, TResult>(Func<IDbConnection, TRead> getData, Func<TRead, TResult> process)
    {
        using (var connection = new SqlConnection(_connectionString))
        {
            connection.Open();
            var data = getData(connection);
            return process(data);
        }
    }
}

Para las consultas con búfer, desea utilizar la primera sobrecarga del método GetConnection ; para el búfer, utilice el segundo, especificando la devolución de llamada para procesar datos:

public class MyRepository : Repository
{
    public MyRepository(string connectionString) : base(connectionString)
    {
    }

    public IEnumerable<MyMapObject> GetData()
    {
        return GetConnection(c => c.Query<MyMapObject>(query));
    }

    public IEnumerable<ResultObject> GetLotsOfData(Func<IEnumerable<MyMapObject>, IEnumerable<ResultObject>> process)
    {
        return GetConnection(c => c.Query<MyMapObject>(query, buffered: false), process);
    }
}

Uso muy básico:

static void Main(string[] args)
{
    var repository = new MyRepository(connectionString);
    var data = repository.GetLotsOfData(ProcessData);
}

public static IEnumerable<ResultObject> ProcessData(IEnumerable<MyMapObject> data)
{
    foreach (var record in data)
    {
        var result = new ResultObject();
        //do some work...
        yield return result;
    }
}

Pero tenga en cuenta que la conexión puede abrirse demasiado tiempo en este caso ...


Respuesta popular

@Sergio, ¡INCREÍBLE! Gracias por un gran patrón. Lo modifiqué ligeramente para que sea asíncrono, de modo que pueda usarlo con los métodos asincrónicos de Dapper. ¡Hace que toda mi cadena de peticiones se vuelva asincrónica, desde los controladores hasta el DB! ¡Maravilloso!

public abstract class BaseRepository
{
    private readonly string _ConnectionString;

    protected BaseRepository(string connectionString)
    {
        _ConnectionString = connectionString;
    }

    // use for buffered queries
    protected async Task<T> WithConnection<T>(Func<IDbConnection, Task<T>> getData)
    {
        try
        {
            using (var connection = new SqlConnection(_ConnectionString))
            {
                await connection.OpenAsync();
                return await getData(connection);
            }
        }
        catch (TimeoutException ex)
        {
            throw new Exception(String.Format("{0}.WithConnection() experienced a SQL timeout", GetType().FullName), ex);
        }
        catch (SqlException ex)
        {
            throw new Exception(String.Format("{0}.WithConnection() experienced a SQL exception (not a timeout)", GetType().FullName), ex);
        }
    }

    // use for non-buffeed queries
    protected async Task<TResult> WithConnection<TRead, TResult>(Func<IDbConnection, Task<TRead>> getData, Func<TRead, Task<TResult>> process)
    {
        try
        {
            using (var connection = new SqlConnection(_ConnectionString))
            {
                await connection.OpenAsync();
                var data = await getData(connection);
                return await process(data);
            }
        }
        catch (TimeoutException ex)
        {
            throw new Exception(String.Format("{0}.WithConnection() experienced a SQL timeout", GetType().FullName), ex);
        }
        catch (SqlException ex)
        {
            throw new Exception(String.Format("{0}.WithConnection() experienced a SQL exception (not a timeout)", GetType().FullName), ex);
        }
    }
}

Úselo con Dapper así:

public class PersonRepository : BaseRepository
{
    public PersonRepository(string connectionString): base (connectionString) { }

    // Assumes you have a Person table in your DB that 
    // aligns with a Person POCO model.
    //
    // Assumes you have an existing SQL sproc in your DB 
    // with @Id UNIQUEIDENTIFIER as a parameter. The sproc 
    // returns rows from the Person table.
    public async Task<Person> GetPersonById(Guid Id)
    {
        return await WithConnection(async c =>
        {
            var p = new DynamicParameters();
            p.Add("Id", Id, DbType.Guid);
            var people = await c.QueryAsync<Person>(sql: "sp_Person_GetById", param: p, commandType: CommandType.StoredProcedure);
            return people.FirstOrDefault();
        });
    }
}


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é