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

任何人都可以告诉我什么是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 。 在创建阅读器之前,要非常小心在exception上对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(); } } 

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

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

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

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

我发现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等传统架构中,长期连接/延迟数据检索实践可以说是更常见的,用户可以在保持连接打开的同时滚动数据,但现代代码中仍然存在使用。

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

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

防止IDataReader流入您的应用程序

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

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

 public Task> 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 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生成器 – 外卖的是,一旦可枚举运行完成,或者抛出exception(例如网络故障),或者即使调用者没有,也会在yield迭代器块中完成使用块的finally结束完成迭代迭代器并且它超出了范围。
  • 与在C#6中一样,异步代码当前也不能使用yield return,因此需要将helper方法从异步查询中分离出来(但是我可以相信将helper移动到Local Function中)。 将来可能会发生变化,例如IAsyncEnumerator