在高流量场景中使用Dapper.Net时打开DataReader问题 - 现在要解决?

时间:2012-06-20 16:20:19

标签: asp.net c#-4.0 datareader dapper dbdatareader

以下是使用Dapper.Net在我们的DAL中进行数据调用的示例:

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

我们看到的一致错误如下:

  

2012-06-20 11:42:44.8903 |致命|已经有一个开放的DataReader   与此命令关联,必须先关闭在   System.Data.SqlClient.SqlInternalConnectionTds.ValidateConnectionForExecute(SqlCommand的   命令)at   System.Data.SqlClient.SqlConnection.ValidateConnectionForExecute(字符串   方法,SqlCommand命令)at   System.Data.SqlClient.SqlCommand.ValidateCommand(String方法,   布尔异步)   System.Data.SqlClient.SqlCommand.RunExecuteReader(的CommandBehavior   cmdBehavior,RunBehavior runBehavior,Boolean returnStream,String   方法,DbAsyncResult结果)at   System.Data.SqlClient.SqlCommand.RunExecuteReader(的CommandBehavior   cmdBehavior,RunBehavior runBehavior,Boolean returnStream,String   方法)at   System.Data.SqlClient.SqlCommand.ExecuteReader(的CommandBehavior   行为,String方法)at   System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(的CommandBehavior   行为)   System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader()
  at 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(IEnumerable 1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable 1   来自MyApp.DAL.DapperORM.SqlMapper.Query [T](IDbConnection   cnn,String sql,Object param,IDbTransaction transaction,Boolean   缓冲,Nullable 1 commandTimeout, Nullable 1 commandType)in   C:\项目\ GIT中\ MyApp的\ MyApp.DAL \ MyApp.DAL.MyAppPrimary.Repositories \小巧玲珑\ SqlMapper.cs:线   538在   MyApp.DAL.Repositories.MemberRepository.AddNotificationEntry(NewsfeedNotification   notificationEntry)in   C:\项目\ GIT中\ MyApp的\ MyApp.DAL \ MyApp.DAL.MyAppPrimary.Repositories \ MemberRepositories \ MemberRepository.cs:线   465 2012-06-20 11:42:45.2491 |致命|无效尝试调用何时读取   读者已关闭在   System.Data.SqlClient.SqlDataReader.ReadInternal(Boolean setTimeout)
  在System.Data.SqlClient.SqlDataReader.Read()at   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(IEnumerable的1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable 1   来自MyApp.DAL.DapperORM.SqlMapper.Query [T](IDbConnection   cnn,String sql,Object param,IDbTransaction transaction,Boolean   缓冲,Nullable 1 commandTimeout, Nullable 1 commandType)in   C:\项目\ GIT中\ MyApp的\ MyApp.DAL \ MyApp.DAL.MyAppPrimary.Repositories \小巧玲珑\ SqlMapper.cs:线   538 at MyApp.DAL.DapperORM.SqlMapper.Query(IDbConnection cnn,   String sql,Object param,IDbTransaction事务,布尔值   缓冲,Nullable 1 commandTimeout, Nullable 1 commandType)in   C:\项目\ GIT中\ MyApp的\ MyApp.DAL \ MyApp.DAL.MyAppPrimary.Repositories \小巧玲珑\ SqlMapper.cs:线   518 at MyApp.DAL.Repositories.MemberRepository.GetBuddies(Int32   profileID)in   C:\项目\ GIT中\ MyApp的\ MyApp.DAL \ MyApp.DAL.MyAppPrimary.Repositories \ MemberRepositories \ MemberRepository.cs:线   271 2012-06-20 11:43:01.2392 |致命|序列不包含元素|在   System.Linq.Enumerable.First [TSource](IEnumerable`1 source)at   MyApp.DAL.Repositories.MemberRepository.GetNickname(Int32 profileID)   在   C:\项目\ GIT中\ MyApp的\ MyApp.DAL \ MyApp.DAL.MyAppPrimary.Repositories \ MemberRepositories \ MemberRepository.cs:线   337

最初我在using {...}内有回复,并将它们移到using区域之外,但仍遇到同样的问题。

这是一个高流量的应用程序,所以在测试这个问题并没有真正出现,直到我们上线。

使用Dapper进行DataReader管理是否还需要做其他事情?

-----更新-----

我之前应该发布这个,但现在就添加它。

Dapper.Net的第581行包含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;
                }

            }
        }

...在嵌套的using代码中看到它?我想知道是否由于嵌套yield return (T)next;内的using代码,如果这导致了问题。

事情是,在适度的交通量下,Dapper似乎运作得很好。然而,在每秒大约1000个请求的系统中,它似乎绊倒了。

我想这对于Dapper dev来说更像是一个FYI,并且想知道他们是否可以解决这个问题。

(我意识到我在代码中错过了名为DapperORM的东西 - 它不是ORM)

4 个答案:

答案 0 :(得分:1)

您只读取了datareader的第一行,因此如果有多行,它永远不会被关闭。

答案 1 :(得分:0)

我使用Entity Framework生成我的类,因此为DAL访问创建了一个不同的存储库 - 而不是使用Dapper,我只是重写了访问代码以使用Entity Framework。与EF连接字符串没有什么不同,并且在我的using语句中使用EF数据库上下文。

一切正常。

从我读到的内容来看,Dapper非常快,这就是为什么我最初选择这个作为我的DAL。但是,它似乎在高频交易环境中有其局限性。也许Dapper团队可以澄清这一点,以防我错过了某些内容或者错误地实现了某些内容。

答案 2 :(得分:0)

Ladislav Mrnka对类似问题的回答更有意义:

  

如果在迭代其他查询的结果时执行查询,则会发生这种情况...   导致这种情况的一件事是在迭代某些查询的结果时触发了延迟加载。   通过在连接字符串中允许MARS可以轻松解决这个问题。将MultipleActiveResultSets = true添加到连接字符串的提供者部分(指定数据源,初始目录等)。

https://stackoverflow.com/a/6064422/1681490

有关MARS的更多信息:http://msdn.microsoft.com/en-us/library/h32h3abf(v=vs.100).aspx

答案 3 :(得分:0)

聚会晚了一点,但它可能会帮助某个像我一样被困住的人。

问题在于Dapper的“ Query”方法返回了IEnumerable,而该IEnumerable实际上是使用“ yield return”语句来生成结果集的迭代器:

// From Dapper SqlMapper.cs QueryImpl function:
            while (reader.Read())
            {
                object val = func(reader);
                if (val == null || val is T)
                {
                    yield return (T)val;
                }
                else
                {
                    yield return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture);
                }
            }

稍后将关闭DataReader的部分。 因此,如果您不会遍历整个结果集并尝试查询另一个请求,则会收到“与此命令相关联的打开的DataReader,必须先关闭它”错误。