Gestion de la connexion avec des requêtes non mises en mémoire tampon dans Dapper

.net ado.net c# dapper micro-orm

Question

J'ai récemment commencé à utiliser Dapper , tout semble agréable et facile, mais il y a une chose qui me déroute toujours: la gestion des connexions.

Selon la documentation :

Dapper ne gère pas le cycle de vie de votre connexion, il suppose que la connexion obtenue est ouverte ET ne contient aucun lecteur de données existant (sauf si MARS est activé)

À la lumière de cela, j'ai commencé à le faire dans le cadre de l'implémentation de mes méthodes de référentiel:

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

Puis je suis tombé sur une table avec un grand nombre d'enregistrements, donc j'ai renvoyé un IEnumerable<T> en transmettant buffered: false à la méthode Query<> , et lorsque j'ai commencé à énumérer l'énumérable dans le front, déclenchez une exception disant que la connexion a été fermée et supprimée, ce qui est attendu depuis que j'achève mes appels avec le bloc précédent.

Question: Le meilleur moyen de résoudre ce problème?
Question subsidiaire: La manière dont je gère la connexion est-elle la meilleure façon de procéder?

Réponse acceptée

Je proposerais ce modèle de dépôt:

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);
        }
    }
}

Pour les requêtes mises en mémoire tampon, vous souhaitez utiliser la première surcharge de la méthode GetConnection , pour les secondes non mises en mémoire tampon, vous devez spécifier un rappel pour le traitement des données:

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);
    }
}

Utilisation très basique:

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;
    }
}

Mais gardez à l'esprit que la connexion peut être ouverte trop longtemps dans ce cas ...


Réponse populaire

@Sergio, génial! Merci pour un si bon motif. Je l'ai légèrement modifié pour être asynchrone afin de pouvoir l'utiliser avec les méthodes asynchrones de Dapper. Rend toute ma chaîne de requêtes asynchrone, des contrôleurs à la base de données! Magnifique!

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);
        }
    }
}

Utiliser avec Dapper comme ceci:

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();
        });
    }
}


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