每次访问数据库时停止编写连接管理代码






3.88/5 (17投票s)
2006年1月6日
8分钟阅读

180061

2347
一个简单的类库,用于数据库访问,无需普遍且常常脆弱的连接管理细节。
目录
引言
本文介绍了我的 AMS.ADO 类库,其中包含一组用于执行数据库命令的类,无需典型的连接管理代码。这些类被实现到两个独立的程序集中——一个用于 .NET 2.0(利用泛型),另一个用于 .NET 1.1——并且可用于四种主要提供程序:SQL Server、OLEDB、ODBC 和 Oracle。尽情享用!
背景
当您想要运行数据库查询或存储过程时,通常每次都会遵循相同的步骤
- 创建一个命令对象,并将 SQL 查询以及数据库连接对象传递给它。
- 如果是存储过程,将
CommandType
设置为StoredProcedure
。 - 设置查询所需的任何参数。
- 打开数据库连接。
- 执行查询并检索记录(如果需要)。
- 关闭数据库连接。
C# 中的代码通常如下所示:
using System.Data.SqlClient;
...
using (SqlConnection conn = new SqlConnection(connectionString))
{
SqlCommand command = new SqlCommand("spUpdateDescription", conn);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("@ID", id);
command.Parameters.Add("@Description", description);
conn.Open();
command.ExecuteNonQuery();
}
或在 VB.NET 1.1 中:
Imports System.Data.SqlClient
...
Dim conn As New SqlConnection(connectionString)
Dim command As New SqlCommand("spUpdateDescription", conn)
command.CommandType = CommandType.StoredProcedure
command.Parameters.Add("@ID", id)
command.Parameters.Add("@Description", description)
Try
conn.Open()
command.ExecuteNonQuery()
Finally
conn.Dispose()
End Try
这段逻辑需要几乎在每次执行查询或存储过程的地方重复。原因是需要仔细管理连接,以便仅在需要访问数据库的时间内打开和使用它。由于这是一个手动过程,可能会无意中忘记在使用后关闭连接,这可能导致连接池最终达到其限制。
我解决这些问题的方法是将四个主要的 IDbCommand
类(SqlCommand
、OleDbCommand
、OdbcCommand
和 OracleCommand
)封装在两组类中,它们会在后台为我处理连接管理细节。
类
这两个类分别称为 SQL
和 StoredProcedure
。它们将上述六个步骤减少到三个:
- 创建 SQL 对象,并将查询(或存储过程)以及数据库连接字符串或对象传递给它。
- 设置查询所需的任何参数。
- 执行查询并检索记录(如果需要)。
因此,上面的代码在 C# 中看起来是这样的:
using AMS.ADO.SqlClient;
...
StoredProcedure sp = new
StoredProcedure("spUpdateDescription", connectionString);
sp.Parameters.Add("@ID", id);
sp.Parameters.Add("@Description", description);
sp.ExecuteNonQuery();
或在 VB.NET 中:
Imports AMS.ADO.SqlClient
...
Dim sp As New StoredProcedure("spUpdateDescription", connectionString)
sp.Parameters.Add("@ID", id)
sp.Parameters.Add("@Description", description)
sp.ExecuteNonQuery()
相同的逻辑现在变成了五行清晰简洁的代码,而不是十行(或 VB.NET 1.1 是十二行),并且数据库访问和连接管理代码混合在一起。多么大的区别!
不仅仅是代码量减少了,而且还不用担心无意中留下连接打开。正如您可能猜到的,我的 ExecuteNonQuery
会创建并打开连接,调用真实的 ExecuteNonQuery
,然后在返回结果之前关闭连接。因此,所有那些重复的代码现在都应该在它应该在的地方:隐藏起来。
上面的例子是通过存储过程向数据库写入的一个简单(但真实)的案例。由于我将连接字符串传递给构造函数,因此类会在内部创建和维护连接。如果我传递的是连接 *对象*,那么它会在调用 ExecuteNonQuery
之前将其保留在调用前的相同打开/关闭状态。
StoredProcedure
类从 SQL
类继承了大部分功能。它旨在消除将 CommandType
设置为 StoredProcedure
的额外调用,并使在代码中搜索存储过程调用变得容易。SQL
类旨在针对数据库执行通用的 SQL 语句或查询(CRUD)。
让我们看另一个例子,这次使用 SQL
类在 C# 中执行 SELECT
查询:
using AMS.ADO.SqlClient;
using System.Data.SqlClient;
...
using (SQL sql = new SQL("SELECT Description FROM" +
" SomeTable WHERE ID = @ID", connectionString))
{
sql.Parameters.Add("@ID", id);
for (SqlDataReader reader = sql.ExecuteReader(); reader.Read(); )
{
string description = reader.GetString(0);
...
}
}
或在 VB.NET 1.1 中:
Imports AMS.ADO.SqlClient
Imports System.Data.SqlClient
...
Dim sql As New SQL("SELECT Description FROM" & _
" SomeTable WHERE ID = @ID", connectionString)
sql.Parameters.Add("@ID", id)
Try
Dim reader As SqlDataReader = sql.ExecuteReader()
While reader.Read()
Dim description As String = reader.GetString(0)
...
End While
Finally
sql.Dispose()
End Try
您注意到什么不见了吗?没错,连接对象不见了!您只看到您关心的:运行查询并检索结果。
在这种情况下,ExecuteReader
在调用真实的 ExecuteReader
之前创建并打开连接。然后,当我处置 SQL
对象(或关闭读取器)时,连接会自动关闭。
这两个类 SQL
和 StoredProcedure
都在可下载的帮助文件(AMS.ADO.chm,已压缩)中有详细文档,该文件是根据代码中的 XML 注释(使用 NDoc 工具)创建的。
架构
正如我之前提到的,我为 .NET 如今可用的四种主要数据提供程序创建了一套独立的类。这些类的名称相同(SQL
和 StoredProcedure
),但可以通过它们所属的 *命名空间* 来区分:
AMS.ADO.SqlClient
AMS.ADO.OleDb
AMS.ADO.Odbc
AMS.ADO.OracleClient
.NET 2.0
我最初是用 C# 2.0 编写的代码,以便能够利用 *泛型*。想法是为所有数据提供程序拥有一个通用的基类,因为代码将是相同的,除了类型名称。我将我的类命名为“Command
”(在 AMS.ADO
命名空间内),并这样声明:
public class Command<ConnectionClass, TransactionClass, CommandClass,
ParameterClass, ParameterCollectionClass, DataReaderClass,
DataAdapterClass> : ICommand
{
...
}
因此,现在同一个类为所有四种数据提供程序——SQL Server、OLE DB、ODBC 和 Oracle——充当基类,并且可以轻松地在未来添加其他提供程序(如 SQL Server CE)。泛型太棒了!
写完代码后,我想使用类似 typedef
的东西,它允许我用一行代码定义所有提供程序的相应类,有点像这样:
public typedef AMS.ADO.Command<SqlConnection, SqlTransaction,
SqlCommand, SqlParameter, SqlParameterCollection,
SqlDataReader, SqlDataAdapter> SQL;
不幸的是,C# 没有真正的 typedef
等价物——using
指令不是一样的——所以我不得不显式地定义每个类,以及所有必需的构造函数,因为它们不是继承的。总之,我不得不这样做:
public class SQL : AMS.ADO.Command<SqlConnection, SqlTransaction,
SqlCommand, SqlParameter, SqlParameterCollection,
SqlDataReader, SqlDataAdapter>
{
public SQL()
{
}
public SQL(string sql);
{
...
}
public SQL(string sql, string connectionString)
{
...
}
...
}
这不算什么大问题,但它清楚地说明了语言功能缺失如何能产生重大影响。(回想起来,我本可以用 C++ 来写,但我对 C# 太着迷了。)
写完代码后,我发现了 Visual Studio 2005 的很酷的新“查看类图”功能(通过右键单击项目),并决定为我的类生成它。下面是 ClassDiagram.cd 的样子:
正如您所见,我的 ICommand
接口派生自 System.Data.IDbCommand
。我只是添加了几个我认为有用的额外属性和方法。除此之外,Command
派生类看起来非常像它们的 .NET 对等项,所以很容易上手。
.NET 1.1
写完并记录了代码后,我决定从代码中的注释生成帮助文件。我知道的唯一工具是 NDoc,它在我写这篇文章时不支持 .NET 2.0 中的泛型类型。我决定搁置这个项目,看看是否会发布一个可用的 NDoc 版本……但它从未出现。所以,我最终决定为 .NET 1.1 创建这个类的版本。这也可以让那些还没有迁移到 .NET 2.0 的用户使用这些类。当然,缺点是我不得不创建四组新的类,并将 Command
类中的代码直接复制到每个类中。画面不美观,但有效。
我在 NET 1.1 文件夹内创建了一个单独的 Visual Studio .NET 2003 解决方案,并将文件复制了进去。我为类和命名空间保留了相同的名称。想法是,当您切换到 .NET 2.0 时,您只需要引用 .NET 2.0 程序集并重新构建您的项目。无需更改代码。
所以帮助文件基于 .NET 1.1 版本,但由于名称相同,因此适用于两个程序集。
测试
我使用流行的 NUnit 工具测试了我的代码——当时 csUnit 还没有 .NET 2.0 版本。
我创建了一个“Test”文件夹,并在其中为 SqlClient、OleDb 和 Odbc 类添加了“fixtures”。它们都使用 Windows 身份验证与本地 SQL Server 数据库一起工作,并自动创建了一个小型数据库(“testAMSADO”)以及几个表和存储过程。
由于测试源文件依赖于 nunit.framework 程序集,我决定将它们从解决方案中排除,以消除不必要的依赖。文件仍然在那里,以防有人感兴趣。下面是我为两个程序集设置的方式:
- .NET 2.0 *项目* 将测试文件直接构建到程序集中,所以我将直接把 AMS.ADO.dll 加载到 NUnit 中。
- .NET 1.1 *解决方案* 在同一个 Test 文件夹内有一个单独的项目(AMS.ADO.Test.csproj)用于测试文件,所以我将 AMS.ADO.Test.dll 加载到 NUnit 中。
这些测试在帮助我验证代码是否按设计工作方面发挥了巨大作用。我强烈建议使用 NUnit 等工具测试底层代码。
用法
可下载的 zip 文件包含 AMS.ADO.dll 的 .NET 1.1 和 2.0 版本(名称相同,位于不同文件夹下),因此请确保使用适合您项目的版本。
- 在 Visual Studio .NET 中打开您自己的项目。
- 在解决方案资源管理器工具箱中,右键单击“引用”文件夹,然后选择“添加引用”。
- 单击“浏览”按钮并选择 AMS.ADO.dll 文件。 .NET 1.1 版本位于 NET1.1 文件夹内。
- 单击“确定”。您现在可以在您的项目中使用
AMS.ADO
命名空间了。
如果您想最小化程序集的大小(尽管它只有 24K),您可以打开 Visual Studio 中的相应解决方案,并排除您不需要的源文件。例如,如果您不需要 Oracle 或 ODBC 访问,您可以右键单击 OracleClient.cs 和 Odbc.cs 并选择“从项目排除”。然后,您可以删除对 System.Data.OracleClient
的引用。作为替代,您可能希望将单个 .cs 文件复制到您自己的项目中,以避免在分发中再添加一个程序集。
历史
- 版本 1.0 - 2005 年 12 月 22 日
- 初始发布。