【发布时间】:2017-11-19 20:06:48
【问题描述】:
谁能告诉我CommandBehavior.CloseConnection 是什么以及在com.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
}
}
【讨论】:
reader 返回给调用者并让它处理数据。那么连接必须超出调用函数的范围。
在创建连接但返回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();
}
}
【讨论】:
如果您不这样做,那么当您在循环中一遍又一遍地使用连接时,连接将保持“打开”状态,直到垃圾收集器拾取它,然后才会将其释放回 ADO。 net 要重用的连接池。这意味着每次通过循环,“打开”连接的代码将无法再次重复使用相同的连接(它尚未释放回池中)。
因此,对于每个连续的循环迭代,ADO 都需要从头开始创建另一个连接,最终,您可能会用完可用的连接。根据 GC 关闭它所需的时间,您可能已经经历了大量的循环迭代,为每个循环创建一个新的不必要的连接,而所有这些未关闭和未使用的连接都只是坐在那里。
如果使用 CommandBehavior.CloseConnection,那么在每个循环中,您都会将连接释放回池中,并且下一次迭代可以重新使用它。因此,您的进程将运行得更快,并且可以通过更少的连接摆脱困境。
【讨论】:
我建议阅读 CommandBehaviour Enumeration 的 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有什么好处?
当您不一定要一次性检索和具体化查询将返回的所有数据时,长寿命数据阅读器会很有用。尽管您的应用程序可以直接保留对长期连接的引用,但这可能会导致架构混乱,其中数据层依赖项(例如 ISqlConnection)“渗入”到您的应用程序的业务和呈现问题中。
延迟数据检索(即仅在需要时检索数据)的好处是调用者可以继续请求更多数据,直到满足其数据要求 - 这在需要数据的场景中很常见分页或临时延迟评估,直到满足某些令人满意的条件。
长期连接/惰性数据检索实践可以说在 Fat-Client 等传统架构中更为普遍,用户可以在保持连接打开的同时滚动浏览数据,但是,在现代代码中仍有用处。
这里有一些折衷:虽然在读取器(和连接)的持续时间内,应用程序/客户端都需要内存和网络资源开销,以及在 RDBMS 数据库中保持“状态”边(缓冲区,甚至游标,如果执行使用游标的PROC),也有好处:
防止 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.
}
备注
LazyQueryAllFoos 的代码仍然需要注意不要将 Enumerable(或其迭代器)保持的时间超过需要的时间,因为这将使底层的 Reader 和 Connection 保持打开状态。 yield return 生成器 in depth here - 要点是,一旦可枚举运行完成或抛出异常(例如网络故障),使用块的 finally 将在 yield 迭代器块中完成,或者即使调用者没有完成迭代迭代器并且它超出了范围。【讨论】: