繁体   English   中英

在ExecuteReader()中使用CommandBehavior.CloseConnection的用途/优点是什么

[英]What is the use/advantage of using CommandBehavior.CloseConnection in ExecuteReader()

任何人都可以告诉我什么是CommandBehavior.CloseConnection以及将此作为com.ExecuteReader(CommandBehavior.CloseConnection)的参数传递的用途/好处是什么?

在读取数据读取器时需要打开连接,并且希望尽快关闭连接。 通过在调用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
    }
}

在创建连接但返回IDataReader函数中使用CommandBehavior.CloseConnection 要非常小心Dispose()的中IDbConnection例外创建读者前:

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();
    }
}

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

我发现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);
}

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

Re: CommandBehavior.CloseConnection什么优势?

当您不一定想要检索和实现查询本来会返回的所有数据时,长期存在的数据读取器非常有用。 虽然您的应用程序可以直接保留对长期连接的引用,但这可能会导致混乱的体系结构,其中数据层依赖性(如ISqlConnectionISqlConnection ”到应用程序的业务和表示问题中。

延迟数据检索 (即仅在需要时检索数据)具有以下好处:调用者可以在满足其数据要求之前继续请求更多数据 - 这在需要数据分页或延迟评估的情况下是常见的,直到某些满足条件。

长期连接/延迟数据检索实践在Fat-Client等遗留架构中更为常见,用户可以在保持连接打开的同时滚动数据,但现代代码中仍然存在使用。

这里有一些权衡:虽然在读取器(和连接)期间需要内存和App / Client端的网络资源开销,并且在RDBMS数据库端保持“状态”(缓冲区) ,或者甚至是游标,如果执行使用游标的PROC),也有以下好处:

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

防止IDataReader流入您的应用程序

如今,大多数应用程序将数据访问问题包含在Repository模式中,或者使用ORM来抽象数据访问 - 这通常会导致数据检索返回Entity对象,而不是在整个应用程序中使用低级IDataReader API本机工作。

幸运的是,仍然可以使用延迟生成器(即返回IEnumerable<Entity>方法仍然保持对DataReader(以及连接)生命周期的控制,使用这样的模式(这是一个async版本,但显然是同步的)代码也可以工作,虽然更加线程饥饿)

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和DataReader一样,调用LazyQueryAllFoos的代码仍然需要注意不要将Enumerable(或它的Iterator)保持的时间超过需要的时间,因为这将使底层的Reader和Connection保持打开状态。
  • Jon Skeet 在这里深入分析了yield return生成器 - 外卖的是,一旦可枚举运行完成,或者抛出异常(例如网络故障),或者即使调用者没有,也会在yield迭代器块中完成使用块的finally结束不完全迭代迭代器,它超出了范围。
  • 与在C#6一样,异步代码当前也不能使用yield return,因此需要将辅助方法从异步查询中分离出来(但是我可以相信帮助器可以移动到本地函数中)。 将来可能会发生变化,例如IAsyncEnumerator

我建议阅读有关CommandBehaviour枚举的MSDN文档:

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM