【问题标题】:What is the use/advantage of using CommandBehavior.CloseConnection in ExecuteReader()在 ExecuteReader() 中使用 CommandBehavior.CloseConnection 的用途/优势是什么
【发布时间】:2017-11-19 20:06:48
【问题描述】:

谁能告诉我CommandBehavior.CloseConnection 是什么以及在com.ExecuteReader(CommandBehavior.CloseConnection) 中将其作为参数传递的用途/好处是什么?

【问题讨论】:

    标签: c# ado.net


    【解决方案1】:

    您在读取数据读取器时需要一个打开的连接,并且您希望尽快关闭连接。通过在调用ExecuteReader 时指定CommandBehavior.CloseConnection,您可以确保您的代码在关闭数据读取器时关闭连接。

    但是您应该已经立即处理您的连接(不仅仅是关闭它们),在这种情况下,这样做最多只能获得边际(几乎肯定无法衡量)的好处.

    例如,此代码立即关闭它的连接(并且执行任何其他需要处理它的工作),而不指定命令行为:

    using (SqlConnection connection = new SqlConnection(connectionString))
    using (SqlCommand command = new SqlCommand(commandText, connection))
    {
        connection.Open();
        using (SqlDataReader reader = command.ExecuteReader()) 
        {
            while (reader.Read())
               // Do something with the rows
        }
    }
    

    【讨论】:

    • 在发送最后一个右花括号'}'之前假设我们使用connection.Close();那么你认为在 using 块尝试释放连接对象时会导致错误吗?
    • “但是您应该已经立即处理您的连接”。有时这样做并不方便。例如,您想将reader 返回给调用者并让它处理数据。那么连接必须超出调用函数的范围。
    • 这个答案有点短视,假设你永远不会将检索数据的代码与处理它的代码解耦......这正是这个选项存在的原因。
    【解决方案2】:

    在创建连接但返回IDataReader 的函数中使用CommandBehavior.CloseConnection。在创建阅读器之前要非常小心IDbConnection 中的Dispose() 异常

    IDataReader ReadAll()
    {
        var connection = new SqlConnection(connectionString);
        try
        {
            connection.Open();
            var command = connection.CreateCommand();
            command.CommandText = "SELECT * FROM mytable";
            var result = command.ExecuteReader(CommandBehavior.CloseConnection);
            connection = null; // Don't dispose the connection.
            return result;
        }
        finally
        {
            if (connection != null)
                connection.Dispose();
        }
    }
    

    【讨论】:

      【解决方案3】:

      如果您不这样做,那么当您在循环中一遍又一遍地使用连接时,连接将保持“打开”状态,直到垃圾收集器拾取它,然后才会将其释放回 ADO。 net 要重用的连接池。这意味着每次通过循环,“打开”连接的代码将无法再次重复使用相同的连接(它尚未释放回池中)。
      因此,对于每个连续的循环迭代,ADO 都需要从头开始创建另一个连接,最终,您可能会用完可用的连接。根据 GC 关闭它所需的时间,您可能已经经历了大量的循环迭代,为每个循环创建一个新的不必要的连接,而所有这些未关闭和未使用的连接都只是坐在那里。 如果使用 CommandBehavior.CloseConnection,那么在每个循环中,您都会将连接释放回池中,并且下一次迭代可以重新使用它。因此,您的进程将运行得更快,并且可以通过更少的连接摆脱困境。

      【讨论】:

        【解决方案4】:

        我建议阅读 CommandBehaviour Enumeration 的 MSDN 文档:

        CloseConnection - 执行命令时,关闭关联的DataReader对象时,关闭关联的Connection对象。

        将此与其他枚举项进行比较。

        【讨论】:

          【解决方案5】:

          我发现CommandBehavior.CloseConnection 的最佳用途是当您想要编写足够灵活的代码以在事务中使用时(这意味着所有查询的共享连接)。考虑:

          public DbDataReader GetReader(DbCommand cmd, bool close = true)
          {
              if(close)
                  return cmd.ExecuteReader(CommandBehavior.CloseConnection);
              return cmd.ExecuteReader();
          }
          

          如果您将读取操作作为更大事务的一部分运行,您可能希望将false 传递给此方法。无论哪种情况,您仍应使用using 语句来进行实际读取。

          不在交易中:

          using(var reader = GetReader(cmd, true))
          {
              while(reader.Read())
                  ...
          }
          

          在事务中,可能会检查是否存在记录:

          bool exists = false;
          using(var reader = GetReader(cmd, false))
          {
              if(reader.Read())
                  exists = reader.GetBoolean(0);
          }
          

          第一个示例将关闭阅读器和连接。第二个(事务性)仍将关闭阅读器,但不会关闭连接。

          【讨论】:

            【解决方案6】:

            Re:CommandBehavior.CloseConnection有什么好处?

            当您不一定要一次性检索和具体化查询将返回的所有数据时,长寿命数据阅读器会很有用。尽管您的应用程序可以直接保留对长期连接的引用,但这可能会导致架构混乱,其中数据层依赖项(例如 ISqlConnection)“渗入”到您的应用程序的业务和呈现问题中。

            延迟数据检索(即仅在需要时检索数据)的好处是调用者可以继续请求更多数据,直到满足其数据要求 - 这在需要数据的场景中很常见分页或临时延迟评估,直到满足某些令人满意的条件。

            长期连接/惰性数据检索实践可以说在 Fat-Client 等传统架构中更为普遍,用户可以在保持连接打开的同时滚动浏览数据,但是,在现代代码中仍有用处。

            这里有一些折衷:虽然在读取器(和连接)的持续时间内,应用程序/客户端都需要内存和网络资源开销,以及在 RDBMS 数据库中保持“状态”边(缓冲区,甚至游标,如果执行使用游标的PROC),也有好处:

            • 使用应用程序和数据库之间的网络 IO 减少,因为只检索需要的数据
            • 应用程序内存开销减少,因为不需要的数据不会被检索(并且如果在 DataReader 之上使用对象抽象,这也可能将不必要的反序列化/物化保存到实体/DTO/ POCO)
            • 通过不“一次全部”具体化查询中的所有数据,它允许应用在通过阅读器时释放不再需要的内存(例如 DTO)

            防止 IDataReader 渗入您的应用

            如今,大多数应用程序将数据访问问题封装到存储库模式中或使用 ORM 来抽象数据访问 - 这通常会导致数据检索返回实体对象,而不是在整个应用程序中使用低级别的 IDataReader API 原生工作应用程序。

            幸运的是,仍然可以使用惰性生成器(即返回 IEnumerable<Entity> 的方法,并且仍然可以通过使用这样的模式(这是 async 版本)保持对 DataReader(以及连接)生命周期的控制,但显然同步代码也可以工作,尽管更需要线程)

            public Task<IEnumerable<Foo>> LazyQueryAllFoos()
            {
               var sqlConn = new SqlConnection(_connectionString);
               using (var cmd = new SqlCommand(
                    "SELECT Id, Col2, ... FROM LargeFoos", sqlConn))
               {
                  await mySqlConn.OpenAsync();
                  var reader = cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection);
                  // Return the IEnumerable, without actually materializing Foos yet
                  // Reader and Connection remain open until caller is done with the enumerable
                  return LazyGenerateFoos(reader);
               }
            }
            
            // Helper method to manage lifespan of foos
            private static IEnumerable<Foo> GenerateFoos(IDataReader reader)
            {
                // Lifespan of the reader is scoped to the IEnumerable
                using(reader)
                {
                   while (reader.Read())
                   {
                      yield return new Foo
                      {
                          Id = Convert.ToInt32(reader["Id"]),
                          ...
                      };
                   }
                } // Reader is Closed + Disposed here => Connection also Closed.
            }
            

            备注

            • 与 SqlConnections 和 DataReaders 一样,调用 LazyQueryAllFoos 的代码仍然需要注意不要将 Enumerable(或其迭代器)保持的时间超过需要的时间,因为这将使底层的 Reader 和 Connection 保持打开状态。
            • Jon Skeet 分析 yield return 生成器 in depth here - 要点是,一旦可枚举运行完成或抛出异常(例如网络故障),使用块的 finally 将在 yield 迭代器块中完成,或者即使调用者没有完成迭代迭代器并且它超出了范围。
            • 在 C#6 中,异步代码目前还不能使用 yield return,因此需要将辅助方法从异步查询中分离出来(不过,我相信辅助方法可以移动到本地函数中)。未来可能会发生变化,例如IAsyncEnumerator

            【讨论】:

            • 需要强调一点,懒惰的数据读取器会占用服务器资源(连接和缓冲区)。这种架构只有在客户端相对较少且服务器有空闲空间时才有用 - 即,对于面向互联网的可扩展应用程序来说不是这样,但对于传统的胖客户端风格的应用程序可能有一些用例。
            猜你喜欢
            • 1970-01-01
            • 2016-04-16
            • 2019-01-24
            • 1970-01-01
            • 2020-02-07
            • 2014-06-03
            • 1970-01-01
            • 1970-01-01
            • 2019-01-08
            相关资源
            最近更新 更多