在高流量情况下使用 Dapper.Net 时打开 DataReader 问题 - 现在解决



下面是使用 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|致命|已经有一个开放的数据阅读器 与此命令相关联,必须先关闭。| 在 System.Data.SqlClient.SqlInternalConnectionTds.ValidateConnectionForExecute(SqlCommand 命令) at System.Data.SqlClient.SqlConnection.ValidateConnectionForExecute(String 方法,SqlCommand 命令) at System.Data.SqlClient.SqlCommand.ValidateCommand(String method, 布尔异步) System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String 方法,DbAsyncResult result) at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String 方法)在 System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior 行为,字符串方法)在 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:ProjectsGitMyAppMyApp.DALMyApp.DAL.MyAppPrimary.RepositoriesDapperSqlMapper.cs:line 581 at System.Collections.Generic.List 1..ctor(IEnumerable 1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable 1 source) at MyApp.DAL.DapperORM.SqlMapper.Query[T](IDbConnection cnn, 字符串 sql, 对象参数, IDbTransaction transaction, 布尔值 缓冲,可为空1 commandTimeout, Nullable 1 个命令类型)在 C:\Projects\Git\MyApp\MyApp.DAL\MyApp.DAL.MyAppPrimary.Repositories\Dapper\SqlMapper.cs:line 538 在 MyApp.DAL.Repository.MemberRepository.AddNotificationEntry(NewsfeedNotification 通知条目)在 C:\Projects\Git\MyApp\MyApp.DAL\MyApp.DAL.MyAppPrimary.Repository\MemberRepository\MemberRepository.cs:line 465 2012-06-20 11:42:45.2491|致命|尝试在以下情况下调用读取无效 阅读器已关闭。| 在 System.Data.SqlClient.SqlDataReader.ReadInternal(Boolean setTimeout)
at System.Data.SqlClient.SqlDataReader.Read() at MyApp.DAL.DapperORM.SqlMapper.d
_13 1.MoveNext() in C:ProjectsGitMyAppMyApp.DALMyApp.DAL.MyAppPrimary.RepositoriesDapperSqlMapper.cs:line 597 at System.Collections.Generic.List 1..ctor(IEnumerable 1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable 1 source) at MyApp.DAL.DapperORM.SqlMapper.Query[T](IDbConnection cnn, 字符串 sql, 对象参数, IDbTransaction transaction, 布尔值 缓冲,可为空1 commandTimeout, Nullable 1 个命令类型) C:\Projects\Git\MyApp\MyApp.DAL\MyApp.DAL.MyAppPrimary.Repositories\Dapper\SqlMapper.cs:line 538 at MyApp.DAL.DapperORM.SqlMapper.Query(IDbConnection cnn, 字符串 sql, 对象参数, IDb事务事务, 布尔值 缓冲,可为空1 commandTimeout, Nullable 1 个命令类型)在 C:\Projects\Git\MyApp\MyApp.DAL\MyApp.DAL.MyAppPrimary.Repositories\Dapper\SqlMapper.cs:line 518 at MyApp.DAL.Repositories.MemberRepository.GetBuddies(Int32 配置文件 ID) 中 C:\Projects\Git\MyApp\MyApp.DAL\MyApp.DAL.MyAppPrimary.Repository\MemberRepository\MemberRepository.cs:line 271 2012-06-20 11:43:01.2392|致命|序列不包含元素| 在 System.Linq.Enumerable.First[TSource](IEnumerable'1 source) at MyApp.DAL.Repository.MemberRepository.GetNickname(Int32 profileID) 在 C:\Projects\Git\MyApp\MyApp.DAL\MyApp.DAL.MyAppPrimary.Repository\MemberRepository\MemberRepository.cs:line 337

最初,我将退货放在using {...}内,并将它们移到using块外,但仍然遇到同样的问题。

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

对于使用 Dapper 进行数据读取器管理,这里还需要做其他事情吗?

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

我应该早点发布这个,但现在只是添加这个。

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代码中看到它吗? 我想知道是否由于嵌套using内部的yield return (T)next;代码,是否会导致问题。

问题是,在流量适中的情况下,Dapper 似乎运行良好。 然而,在一个每秒大约 1000 个请求的系统中,它似乎跳闸了。

我想这更像是 Dapper 开发人员的仅供参考,并想知道他们是否可以解决这个问题。

(我意识到我在代码中错误地命名了 DapperORM - 它不是 ORM)

您只读取数据读取器的第一行,因此如果有多个行,它永远不会关闭。

派对有点晚了,但它可能会帮助那些像我一样陷入困境的人。

问题是 Dapper "Query" 方法返回了 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,必须先关闭"错误。

Ladislav Mrnka 在类似问题上的答案更有意义:

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

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

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

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

一切都很好。

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

相关内容

  • 没有找到相关文章

最新更新