Generalice la consulta de Dapper dentro de un delegado, Func o Action C #

c#-4.0 dapper generics

Pregunta

Tengo varias consultas tipo Dapper como esta a continuación, con varios tipos diferentes como resultado. Este es uno específico de estos, que producen, por ejemplo, List <ClassA>:

string anSql = GetSqlQueryText("query_name");
SqlConnection connection = GetSqlConnection();

List<ClassA> result = null;
try
{
    connection.Open();
    result = connection.Query<ClassA>(anSql, new    //want to move this part from here
    {
        param1 = "value1",
        param2 = "value2"
    }).ToList();                                    //to here out, to an outer call
}
catch //more error handling and retry logic omitted and this is a simplified version
{
    result = new List<ClassA>();       //this good to be filled up by the generic type
}
finally
{
    connection.Close();
}

Deseo unir este tipo de consultas a un método GenDapperQuery <T> genérico, que se puede llamar con la ayuda de un delegado (o Func / Action o cualquier otra cosa) algo así (T será ClassA o ClassB, etc., en el código final):

List<T> result = GenDapperQuery<T>(() =>
{
    result = connection.Query<T>(anSql, new
    {
        param1 = "value1",
        param2 = "value2"
    }).ToList();
}
//and I want to use the result then as a specific type e.g. ClassA
//either immediately or after a cast
result[0].Id = 3; //or
(result as List<ClassA>)[0].Id = 3;

Así que mi propósito es usar la conexión, mi lógica de manejo / reintento de errores y, por supuesto, la consulta Dapper en general, varias veces (porque no quiero escribir tantas como la consulta y el tipo que tengo), pero quiero para decir de alguna manera a este método genérico (deseado), qué preguntar con aplomo y qué tipo de lista (genérica) crear y completar.

(Este método genérico (deseado) estará en la misma clase donde puedo crear conexión una vez. La parte de manejo de errores será más complicada, pero siempre igual en todo tipo, es por eso que no quiero escribirlos varias veces . Los parámetros pueden variar libremente solo como cadena sql como entradas.)

Mi problema ahora es que no puedo escribir una consulta Dapper genérica que rodee mi propio código, pero con una función inyectada específica desde fuera de este método (deseado).

¿Es esto posible en C #? Cualquier sugerencia sería muy apreciada.

Respuesta aceptada

Hay muchas formas de lograr esto. Un mecanismo es crear un método para Execute / ErrorHandler:

public TResult ExecuteWrapper<TResult>(SqlConnection connection, Func<TResult, SqlConnection> func)
{
    TResult result;
    try
    {
        connection.Open();
        // Query will be wrapped in a function or lambda
        result = func(connection);
    }
    catch //more error handling and retry logic omitted and this is a simplified version
    {
        // Specifying a new TResult may be more difficult. You could either:
        // 1. Pass a default value in the method parameter
        //    result = defaultValue; // defaultValue is method parameter
        // 2. Use System.Activator to create a default instance
        //    result = (TResult)System.Activator(typeof(TResult));

        // Original: result = new List<ClassA>(); // this good to be filled up by the generic type
    }
    finally
    {
        connection.Close();
    }
    return result;
}

Entonces lo usarías así:

List<ClassA> result = ExecuteWrapper(connection, (cn) =>
    {
        string anSql = GetSqlQueryText("query_name");
        return cn.Query<ClassA>(anSql, new
            {
                param1 = "value1",
                param2 = "value2"
            }).ToList();        
    });

Respuesta popular

Estoy publicando mi mejora, basada en la buena respuesta de Ryan, también para mantener unidas estas soluciones. Esta función de contenedor no solo solicita la conexión SQL sino también el nombre de la consulta. Entonces esta solución es más corta con una línea en el lado de la persona que llama y tal vez un poco más elegante.
La función de envoltura modificada:

public TResult ExecuteWrapper<TResult>(SqlConnection connection, string queryName, Func<SqlConnection, string, TResult> func)
{
   string anSql = GetSqlText(queryName);
   TResult result;
   try
   {
      connection.Open();
      result = func(connection, anSql);
   }
   catch
   {
      result = System.Activator.CreateInstance<TResult>(); //this is working in .NET 4.5 environment
   }
   finally
   {
      connection.Close();
   }
   return result;
}

Y el lado llamante de esto:

List<ClassA> result = ExecuteWrapper(connection, "query_name", (conn, sql) =>
    {
        return conn.Query<ClassA>(sql, new
            {
                param1 = "value1",
                param2 = "value2"
            }).ToList();        
    });

Gracias de nuevo a Ryan por su respuesta.



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é