Abra los problemas de DataReader mientras usa Dapper.Net en un escenario de alto tráfico, ¿ahora para resolverlo?

asp.net c#-4.0 dapper datareader dbdatareader

Pregunta

Aquí hay un ejemplo de una de nuestras llamadas de datos en nuestro DAL usando Dapper.Net:

    /// <summary>
    /// Handles db connectivity as Dapper assumes an existing connection for all functions
    /// Since the app uses three databases, pass in the connection string for the required db.
    /// </summary>
    /// <returns></returns>
    protected static IDbConnection OpenConnection(string connectionStringName)
    {
        try
        {
            connection = new SqlConnection(WebConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString);
            //connection = SqlMapperUtil.GetOpenConnection(connectionStringName);       // if we want to use the Dapper utility methods
            //connection = new SqlConnection(connectionString);
            connection.Open();
            return connection;
        }
        catch (Exception ex)
        {
            ErrorLogging.Instance.Fatal(ex);        // uses singleton for logging
            return null;
        }
    }


    public string GetNickname(int profileID)
    {
        string nickname = string.Empty;

        using (IDbConnection connection = OpenConnection("PrimaryDBConnectionString"))
        {
            try
            {
                var sp_nickname = connection.Query<string>("sq_mobile_nickname_get_by_profileid", new { profileID = profileID }, commandType: CommandType.StoredProcedure);
                nickname = sp_nickname.First<string>();
            }
            catch (Exception ex)
            {
                ErrorLogging.Instance.Fatal(ex);
                return null;
            }
        }

        return nickname;
    }

Los errores consistentes que estamos viendo son los siguientes:

2012-06-20 11: 42: 44.8903 | Fatal | Ya hay un DataReader abierto asociado a este comando que debe cerrarse primero. en System.Data.SqlClient.SqlInternalConnectionTds.ValidateConnectionForExecute (comando SqlCommand) en System.Data.SqlClient.SqlConnection.ValidateConnectionForExecute (método String, comando SqlCommand) en System.Data.SqlClient.SqlCommand.ValidateCommand (método String, Boolean async) en System .Data.SqlClient.SqlCommand.RunExecuteReader (CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result) en System.Data.SqlClient.SqlCommand.RunExecuteReader (CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method) en el sistema. Data.SqlClient.SqlCommand.ExecuteReader (comportamiento CommandBehavior, método String) en System.Data.SqlClient.SqlCommand.ExecuteDbDataReader (comportamiento CommandBehavior) en System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader ()
en MyApp.DAL.DapperORM.SqlMapper.d_ 13 1.MoveNext() in C:\Projects\Git\MyApp\MyApp.DAL\MyApp.DAL.MyAppPrimary.Repositories\Dapper\SqlMapper.cs:line 581 at System.Collections.Generic.List 1..ctor ( 1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable IEnumerable 1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable fuente 1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable 1) en MyApp.DAL.DapperORM.SqlMapper.Query [T] (IDbConnection cnn, String sql, Object param, transacción IDbTransaction, Boolean buffer, Nullable 1 commandTimeout, Nullable 1 commandType) en C: \ Projects \ Git \ MyApp \ MyApp.DAL \ MyApp.DAL.MyAppPrimary.Repositories \ Dapper \ SqlMapper.cs: línea 538 en MyApp.DAL .Repositories.MemberRepository.AddNotificationEntry (NewsfeedNotification notificationEntry) en C: \ Projects \ Git \ MyApp \ MyApp.DAL \ MyApp.DAL.MyAppPrimary.Repositories \ MemberRepositories \ MemberRepository.cs: línea 465 2012-06-20 11: 42: 45.2491 | Fatal | Intento no válido de llamar a Read cuando se cierra el lector. | en System.Data.SqlClient.SqlDataReader.ReadInternal (Boolean setTimeout)
en System.Data.SqlClient.SqlDataReader.Read () en MyApp.DAL.DapperORM.SqlMapper.d
_13 1.MoveNext() in C:\Projects\Git\MyApp\MyApp.DAL\MyApp.DAL.MyAppPrimary.Repositories\Dapper\SqlMapper.cs:line 597 at System.Collections.Generic.List 1..ctor ( 1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable IEnumerable 1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable fuente 1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable 1) en MyApp.DAL.DapperORM.SqlMapper. Consulta [T] (IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Boolean buffer, Nullable 1 commandTimeout, Nullable 1 commandType) en C: \ Projects \ Git \ MyApp \ MyApp.DAL \ MyApp.DAL.MyAppPrimary.Repositories \ Dapper \ SqlMapper.cs: línea 538 en MyApp.DAL.DapperORM.SqlMapper.Query (IDbConnection cnn, String sql, Object param, transacción IDbTransaction, Boolean buffer, Nullable 1 commandTimeout, Nullable 1 commandType) en C: \ Projects \ Git \ MyApp \ MyApp.DAL \ MyApp.DAL.MyAppPrimary.Repositories \ Dapper \ SqlMapper.cs: línea 518 en MyApp.DAL.Repositories.MemberRepository.GetBuddies (Int32 profileID) en C: \ Projects \ Git \ MyApp \ MyApp.DA L \ MyApp.DAL.MyAppPrimary.Repositories \ MemberRepositories \ MemberRepository.cs: línea 271 2012-06-20 11: 43: 01.2392 | Fatal | La secuencia no contiene elementos | en System.Linq.Enumerable.First [TSource] (fuente IEnumerable`1) en MyApp.DAL.Repositories.MemberRepository.GetNickname (Int32 profileID) en C: \ Projects \ Git \ MyApp \ MyApp.DAL \ MyApp.DAL.MyAppPrimary .Repositories \ MemberRepositories \ MemberRepository.cs: línea 337

Inicialmente tuve los retornos dentro del using {...} y los moví fuera del bloque de using , pero aún tengo los mismos problemas.

Esta es una aplicación de alto tráfico, por lo que en las pruebas esta cuestión no surgió hasta que comenzamos a funcionar.

¿Hay algo más que deba hacerse aquí para la administración de DataReader con Dapper?

----- ACTUALIZAR -----

Debería haber publicado esto antes, pero solo agregué esto ahora.

La línea 581 de Dapper.Net contiene el código ExecuteReader() :

   private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType)
    {
        var identity = new Identity(sql, commandType, cnn, typeof(T), param == null ? null : param.GetType(), null);
        var info = GetCacheInfo(identity);

        using (var cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, param, commandTimeout, commandType))
        {
            using (var reader = cmd.ExecuteReader())
            {
                Func<Func<IDataReader, object>> cacheDeserializer =  () =>
                {
                    info.Deserializer = GetDeserializer(typeof(T), reader, 0, -1, false);
                    SetQueryCache(identity, info);
                    return info.Deserializer;
                };

                if (info.Deserializer == null)
                {
                    cacheDeserializer();
                }

                var deserializer = info.Deserializer;

                while (reader.Read())
                {
                    object next;
                    try
                    {
                        next = deserializer(reader);
                    }
                    catch (DataException)
                    {
                        // give it another shot, in case the underlying schema changed
                        deserializer = cacheDeserializer();
                        next = deserializer(reader);
                    }
                    yield return (T)next;
                }

            }
        }

... verlo allí en el código de using anidado? Me pregunto si debido al yield return (T)next; código dentro del tiempo, dentro del using anidado, si eso está causando un problema.

El problema es que con una cantidad moderada de tráfico, Dapper parece funcionar bien. Sin embargo, en un sistema con aproximadamente 1000 solicitudes por segundo, parece tropezar.

Supongo que esto es más que un FYI para los desarrolladores de Dapper, y me pregunto si podrían resolver esto.

(y me doy cuenta de que pierdo el nombre de DapperORM en el código, no es un ORM)

Respuesta aceptada

Usé Entity Framework para generar mis clases, así que creé un repositorio diferente para el acceso DAL, en lugar de utilizar Dapper, simplemente reescribí el código de acceso para usar Entity Framework. Nada diferente de las cadenas de conexión EF y el uso del contexto de la base de datos EF en mis instrucciones de using .

Todo funciona bien.

Por lo que leí, Dapper es bastante rápido, por lo que inicialmente elegí esto para mi DAL. Sin embargo, parece que tiene sus limitaciones en un entorno de transacción de alta frecuencia. Tal vez el equipo de Dapper puede aclarar esto en caso de que haya olvidado algo o implementado algo incorrectamente.


Respuesta popular

Solo lee la primera fila del lector de datos, por lo que nunca se cierra si hay más de una fila.



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow