跳转至

连接数据源⚓︎

任何数据库应用程序的一项主要功能是连接数据源并检索数据源中包含的数据。 ADO.NET 的 .NET Framework 数据提供程序充当应用程序和数据源之间的桥梁,使你可以执行命令以及使用 DataReader 或 DataAdapter 检索数据。 任何数据库应用程序的一项关键功能是更新数据库中存储的数据的能力。 在 ADO.NET 中,更新数据时会使用 DataAdapter、DataSet 和 Command 对象;此外,还可能会使用事务。

一、连接数据源⚓︎

01 建立连接⚓︎

在 ADO.NET 中,通过在连接字符串中提供必要的身份验证信息,使用 Connection 对象来连接到某个特定数据源, 使用的 Connection 对象类型取决于数据源的类型。

随 .NET Framework 提供的每个数据提供程序都具有一个 DbConnection 对象:

  • 适用于 OLE DB 的 .NET Framework 数据提供程序包括一个 OleDbConnection 对象;
  • 适用于 SQL Server 的 .NET Framework 数据提供程序包括一个 SqlConnection 对象;
  • 适用于 ODBC 的 .NET Framework 数据提供程序包括一个 OdbcConnection 对象
  • 适用于 Oracle 的 .NET Framework 数据提供程序包括一个 OracleConnection 对象。

连接数据源的代码段示例如下:

// Assumes connectionString is a valid connection string.  
using (SqlConnection connection = new SqlConnection(connectionString))  
{  
    connection.Open();  
    // Do work here.  
}
// Assumes connectionString is a valid connection string.  
using (OleDbConnection connection =
  new OleDbConnection(connectionString))  
{  
    connection.Open();  
    // Do work here.  
}

csharp // Assumes connectionString is a valid connection string. using (OdbcConnection connection = new OdbcConnection(connectionString)) { connection.Open(); // Do work here. }

// Assumes connectionString is a valid connection string.  
using (OracleConnection connection =
  new OracleConnection(connectionString))  
{  
    connection.Open();  
    // Do work here.  
}  
OracleConnection nwindConn = new OracleConnection("Data Source=MyOracleServer;Integrated Security=yes;");  
nwindConn.Open();

02 关闭连接⚓︎

建议在使用完连接时一定要关闭连接,以便连接可以返回池。

  • 使用 using 语句,using 语句可确保正确使用 IDisposable 实例;
  • 调用连接对象的 Close 或 Dispose 方法

不是显式关闭的连接可能不会添加或返回到池中。例如,如果连接已超出范围但没有显式关闭,则仅当达到最大池大小而该连接仍然有效时,该连接才会返回到连接池中。

二、连接事件⚓︎

01 Connection连接事件⚓︎

所有 .NET Framework 数据提供程序中的 Connection 对象有两个事件,可用于从数据源中检索信息性消息或确定 Connection 的状态是否已被更改。

事件 说明
InfoMessage 当从数据源中返回信息性消息时发生。 信息性消息是数据源中不会引发异常的消息。
StateChange 当 Connection 的状态更改时发生。

02 使用InfoMessage事件⚓︎

SqlConnection 对象的 InfoMessage 事件从 SQL Server 数据源中检索警告和信息性消息。

从数据源返回的严重程度为 11 到 16 的错误将引发异常。 但是,InfoMessage 事件可用于从数据源中获取与错误无关联的消息。 对于 Microsoft SQL Server,任何严重程度等于或小于 10 的错误都将被视为信息性消息,将使用 InfoMessage 事件来捕获。

InfoMessage 事件接收 SqlInfoMessageEventArgs 对象,该对象在其 Errors 属性中包含来自数据源的消息的集合。

示例:

// Assumes that connection represents a SqlConnection object.  
connection.InfoMessage += new SqlInfoMessageEventHandler(OnInfoMessage);  

protected static void OnInfoMessage(object sender, SqlInfoMessageEventArgs args)  
{  
    foreach (SqlError err in args.Errors)  
    {  
        Console.WriteLine(  
            "The {0} has received a severity {1}, state {2} error number {3}\n" +  
            "on line {4} of procedure {5} on server {6}:\n{7}",  
            err.Source, 
            err.Class, 
            err.State, 
            err.Number, 
            err.LineNumber,
            err.Procedure, 
            err.Server, 
            err.Message);  
    }  
}

03 使用StateChange事件⚓︎

StateChange 事件在 Connection 的状态更改时发生。

StateChange 事件接收 StateChangeEventArgs,使你能够使用 OriginalStateCurrentState 属性来确定 Connection 状态的更改。

  • OriginalState 属性是一个 ConnectionState 枚举,指示更改前的 Connection 状态;
  • CurrentState 属性是一个 ConnectionState 枚举,指示更改后的 Connection 状态。

示例:

// Assumes connection represents a SqlConnection object.  
connection.StateChange  += new StateChangeEventHandler(OnStateChange);  

protected static void OnStateChange(object sender, StateChangeEventArgs args)  
{  
    Console.WriteLine(  
        "The current Connection state has changed from {0} to {1}.",  
        args.OriginalState, 
        args.CurrentState);  
}

三、连接字符串⚓︎

01 连接字符串⚓︎

连接字符串包含初始化信息,该信息作为参数从数据提供程序传递给数据源。

在前述示例代码段中,例如 new SqlConnection(connectionString),Data Provider 接收连接字符串作为 DbConnection.ConnectionString 属性的值,数据提供程序解析连接字符串,并确保语法正确且支持关键字, 然后 DbConnection.Open() 方法将已解析的连接参数传递到数据源,数据源执行进一步验证并建立连接。

连接字符串是使用分号分隔的键/值参数对字符串列表

String connectionString = "keyword1=value; keyword2=value";

连接字符串具有以下语法规则:

  • 关键字不区分大小写, 但是,值可能会区分大小写,具体取决于数据源;
  • 关键字和值都可以包含空白字符;
  • 关键字和不带引号的值中忽略前导空格和尾随空格;
  • 包含分号、Unicode 控制字符、前导空格或尾随空格值必须用单引号或双引号将其括起;
  • 包含单引号的值只能用双引号括起,反之亦然;
  • 引号本身以及等号不需要转义;
  • 关键字集取决于提供程序,键入错误会导致错误。

Warning

在运行时从未验证的用户手动输入构造的连接字符串易受字符串注入式攻击,从而危害数据源的安全。 为了解决这些问题,ADO.NET 2.0 为每个 .NET Framework 数据提供程序引入了连接字符串生成器。 这些连接字符串生成器将参数公开为强类型属性,并使得在将连接字符串发送到数据源之前可以验证该连接字符串,关于连接字符串生成器的介绍请参考后续内容。

02 连接字符串生成器⚓︎

在 ADO.NET 的早期版本中,不会对具有串联字符串值的连接字符串进行编译时检查,因此在运行时会产生 ArgumentException。 并且,不同的 Data Provider 支持的连接字符串关键字的语法不同,这使得手动构造有效连接字符串变得很困难。

Warning

当使用动态字符串串联根据用户输入生成连接字符串时,可能发生连接字符串 注入式攻击。 如果未验证字符串并且未转义恶意文本或字符,则攻击者可能会访问服务器上的敏感数据或其他资源。 例如,攻击者可以通过提供分号并追加其他值来发起攻击。

为解决这个问题,ADO.NET 2.0 为每个 Data Provider 引入了连接字符串生成器,每个 Data Provider 包括一个从 DbConnectionStringBuilder 继承的强类型连接字符串生成器类, 每个提供程序的特定连接字符串语法记录在其 ConnectionString 属性中。

Data Provider ConnectionStringBuilder
System.Data.SqlClient System.Data.SqlClient.SqlConnectionStringBuilder
System.Data.OleDb System.Data.OleDb.OleDbConnectionStringBuilder
System.Data.Odbc System.Data.Odbc.OdbcConnectionStringBuilder
System.Data.OracleClient System.Data.OracleClient.OracleConnectionStringBuilder

连接字符串生成器类旨在排除推测,防止出现语法错误和安全漏洞。 它们提供与每个数据提供程序允许的已知键/值对相对应的方法和属性。 每个类都保持一个固定的同义词集合,可以将同义词转换为相应的已知键名。 将执行键/值对的有效性检查,无效对会引发异常。 此外,还会以一种安全方式处理插入的值。

示例:

SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();  
builder["Data Source"] = "(local)";  
builder["integrated Security"] = true;  
builder["Initial Catalog"] = "AdventureWorks;NewValue=Bad";  
Console.WriteLine(builder.ConnectionString);

// Output:
// data source=(local);Integrated Security=True;
// initial catalog="AdventureWorks;NewValue=Bad"

03 连接字符串与配置文件⚓︎

在应用程序代码中嵌入连接字符串可能导致安全漏洞和维护问题。 使用 Ildasm.exe(IL 反汇编程序)工具可以查看编译到应用程序源代码中的未加密连接字符串。 此外,如果连接字符串发生更改,则必须重新编译应用程序。 因此,微软建议将连接字符串存储在应用程序配置文件中。

下面的代码展示了配置文件中添加连接字符串的语法:

<?xml version='1.0' encoding='utf-8'?>  
<configuration>  
<connectionStrings>  
  <clear />  
  <add name="Name" providerName="System.Data.ProviderName" 
      connectionString="Valid Connection String;" />  
</connectionStrings>  
</configuration>

About

应用程序配置文件包含特定应用程序特有的设置。 例如,ASP.NET 应用程序能包含一个或多个 web.config 文件,Windows 应用程序可能包含一个可选 app.config 文件。 虽然配置文件的名称和位置会因应用程序宿主的不同而有所不同,但配置文件可以有相同的元素。

另外,你可以进一步将连接字符串抽离到单独的外部文件,然后在应用程序配置文件中重新引用它们:

外部配置文件 connections.config
<connectionStrings>  
  <add name="Name"
   providerName="System.Data.ProviderName"
   connectionString="Valid Connection String;" />  
</connectionStrings>

应用程序配置文件
<?xml version='1.0' encoding='utf-8'?>  
<configuration>  
    <connectionStrings configSource="connections.config"/>  
</configuration>

Tip

1⃣ 您可以将连接字符串的一部分保存在配置文件中,并使用 DbConnectionStringBuilder 类在运行时将该字符串补充完整。 如果您预先不知道连接字符串的元素,或者不希望将敏感信息保存到配置文件中,这一方法会很有用。
2⃣ .NET Framework 支持运行时检索配置文件中的连接字符串,您可以以编程方式按名称或提供程序名称检索连接字符串。更多参考:https://learn.microsoft.com/zh-cn/dotnet/framework/data/adonet/connection-strings-and-configuration-files

04 常见数据库连接字符串⚓︎

{
    Server=localhost;Initial Catalog=DBName;User ID=username;Password=12356
}

四、连接池⚓︎

01 连接池原理⚓︎

连接到数据源可能需要很长时间。 为了最大程度地降低打开连接的成本,ADO.NET 使用一种称为“连接池”的优化技术,这种技术可最大程度地降低重复打开和关闭连接所造成的成本。不同的数据提供程序处理连接池的方式有所不同。

连接池使新连接必须打开的次数得以减少。 池程序维持物理连接的所有权。 通过为每个给定的连接配置保留一组活动连接来管理连接。 每当用户在连接上调用 Open 时,池进程就会查找池中可用的连接。 如果某个池连接可用,会将该连接返回给调用者,而不是打开新连接。 应用程序在该连接上调用 Close 时,池进程会将连接返回到活动连接池集中,而不是关闭连接。 连接返回到池中之后,即可在下一个 Open 调用中重复使用。

只有配置相同的连接可以建立池连接。 ADO.NET 会同时保留多个池,每个池对应一种配置。 在使用集成的安全性时,连接按照连接字符串以及 Windows 标识分到多个池中。 还根据连接是否已在事务中登记来建立池连接。 在使用 ChangePassword 时,SqlCredential 实例影响连接池。 SqlCredential 的不同实例将使用不同的连接池,即使用户 ID 和密码相,也是如此。

池连接可以显著提高应用程序的性能和可缩放性。 默认情况下,在 ADO.NET 中启用连接池。 除非显式禁用,否则,在应用程序中打开和关闭连接时,池进程会对连接进行优化。 还可以提供几个连接字符串修饰符来控制连接池的行为。

Note

启用连接池后,如果发生超时错误或其他登录错误,则将引发异常,并且在接下来的五秒内进行的后续连接尝试将失败,此段时间称为“阻塞期”。 如果应用程序尝试在阻塞期内进行连接,则将再次引发第一个异常。 阻塞期结束后的后续失败将导致新的阻塞期,该阻塞期的持续时间是上一个阻塞期的两倍,最长为一分钟。

02 SQL Server 连接池⚓︎

在初次打开连接时,将根据完全匹配算法创建连接池,该算法将池与连接中的连接字符串关联。 每个连接池都与一个不同的连接字符串相关联。 打开新连接时,如果连接字符串并非与现有池完全匹配,将创建一个新池。 按进程、应用程序域、连接字符串以及 Windows 标识(在使用集成的安全性时)来建立池连接。 连接字符串还必须是完全匹配的;按不同顺序为同一连接提供的关键字将分到单独的池中。

创建连接池⚓︎

using (SqlConnection connection = new SqlConnection(  
  "Integrated Security=SSPI;Initial Catalog=Northwind"))  
{  
    connection.Open();
    // Pool A is created.  
}  

using (SqlConnection connection = new SqlConnection(  
  "Integrated Security=SSPI;Initial Catalog=pubs"))  
{  
    connection.Open();
    // Pool B is created because the connection strings differ.  
}  

using (SqlConnection connection = new SqlConnection(  
  "Integrated Security=SSPI;Initial Catalog=Northwind"))  
{  
    connection.Open();
    // The connection string matches pool A.  
}

// 示例中创建了三个新的 SqlConnection 对象,但是管理时只需要两个连接池。 注意,根据为 `Initial Catalog` 分配的值,第一个和第二个连接字符串有所不同。

添加连接⚓︎

连接池是为每个唯一的连接字符串创建的。 当创建一个池后,将创建多个连接对象并将其添加到该池中,以满足最小池大小的需求。 连接根据需要添加到池中,但是不能超过指定的最大池大小(默认值为 100)。 连接在关闭或断开时释放回池中。

在请求 SqlConnection 对象时,如果存在可用的连接,将从池中获取该对象。 连接要可用,必须未使用,具有匹配的事务上下文或未与任何事务上下文关联,并且具有与服务器的有效链接。

连接池进程通过在连接释放回池中时重新分配连接,来满足这些连接请求。 如果已达到最大池大小且不存在可用的连接,则该请求将会排队。 然后,池进程尝试重新建立任何连接,直至到达超时时间(默认值为 15 秒)。 如果池进程在连接超时之前无法满足请求,将引发异常。

Warnning

请在连接使用完毕后关闭连接,以便连接返回连接池,可以调用 Connection 对象的 Close 或 Dispose 方法,或使用 using 语句。

移除连接⚓︎

如果空闲时间达到大约 4-8 分钟,或池进程检测到与服务器的连接已断开,连接池进程会将该连接从池中移除。 注意,只有在尝试与服务器进行通信之后才能检测到断开的连接。 如果发现某连接不再连接到服务器,则会将其标记为无效。 无效连接只有在关闭或重新建立后,才会从连接池中移除。

如果存在一个与已消失的服务器的连接,即使连接池进程尚未检测到断开的连接,也可以从池中取出此连接并将连接标记为无效。 这种情况是因为检查连接是否仍有效的系统开销将造成与服务器的另一次往返,从而抵消了池进程的优势。 发生此情况时,初次尝试使用该连接将检测连接是否曾断开,并引发异常。

清除连接池⚓︎

ADO.NET 2.0 引入了两种新方法来清除池:ClearAllPools 和 ClearPool。 ClearAllPools 清除指定提供程序的所有连接池,ClearPool 清除与特定连接关联的连接池。

如果在调用时连接正在使用,将对它们进行相应的标记。 连接关闭时,将被丢弃,而不是返回池中。