Pregunta

Recientemente comencé a usar Apuesto, todo parece bonito y fácil pero hay una cosa que me sigue confundiendo:Gestión de conexiones.

Según el documentación:

Dapper no gestiona el ciclo de vida de su conexión, asume el la conexión que consigue está abierta Y no tiene data readers existentes enumerando (a menos que MARS esté habilitado)

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

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

Luego me encontré con una tabla con una gran cantidad de registros, así que pensé en devolver un IEnumerable<T> pasando buffered: false hacia Query<> método, y cuando comencé a enumerar los enumerables en la interfaz, surgió una excepción que decía que la conexión se cerró y se eliminó, lo cual se esperaba ya que estoy envolviendo mis llamadas con el bloque de uso anterior.

Pregunta: ¿La mejor manera de resolver esto?
Pregunta paralela: ¿La forma en que administro la conexión es la forma preferida de hacerlo?

¿Fue útil?

Solución

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 consultas almacenadas en búfer, desea utilizar la primera sobrecarga de GetConnection método, para no almacenado en búfer se utiliza 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 en este caso la conexión puede permanecer abierta durante demasiado tiempo...

Otros consejos

@Sergio, IMPRESIONANTE!Gracias por un patrón tan genial.Lo modifiqué ligeramente para que sea asíncrono para poder usarlo con los métodos asíncronos de Dapper.¡Hace que toda mi cadena de solicitudes sea asíncrona, desde los controladores hasta la base de datos!¡Espléndido!

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 de esta manera:

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();
        });
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top