连接到ADO.NET中的数据源
在 ADO.NET 中,通过在连接字符串中提供必要的身份验证信息,使用 Connection 对象连接到特定的数据源。使用的 Connection 对象取决于数据源的类型。随 .NET Framework 提供的每个 .NET Framework 数据提供程序都具有一个 DbConnection 对象,适用于 SQL Server的.NET Framework 数据提供程序包括一个 SqlConnection 对象。
建立连接
要连接到 Microsoft SQL Server , 请使用 SQL Server .NET Framework 数据提供程序的 SqlConnection 对象。
关闭连接
在使用完连接时一定要关闭连接,以便连接可以返回池。如果在 C# 代码中存在 Using 块,将自动断开连接,即使发生无法处理的异常。也可以使用适合所使用的提供程序的连接对象的 Close 或 Dispose 方法。不是显式关闭的连接可能不会添加或返回到池中。例如,如果连接已超出范围但没有显式关闭,则仅当达到最大池大小 而该连接仍然有效时,该连接才会返回到连接池中。
注意
不要在类的Finalize方法中对 Connection、DataReader 或任何其他托管对象调用 Close 或 Dispose 。在终结器中,仅释放类直接拥有的非托管资源。如果类不拥有任何非托管资源,则不要在类定义中包含 Finalize 方法。
注意
从连接池中获取连接或将连接返回到连接池中时,服务器上不会引发登录和注销事件,这是因为在将连接返回到连接池时实际上并没有将其关闭。
连接到SQL Server
以下代码示例演示如何创建并打开与SQL Server数据库的连接。
using (var connection = new SqlConnection(ConnectionStr)) { connection.Open(); }
连接事件
使用 StateChange 事件
StateChange 事件在 Connection 的状态改变时发生。 StateChange 事件接收 StateChangeEventArgs,使能够使用 OriginalState 和 CurrentState 属性来确定 Connection 状态的改变。
以下代码示例在 Connection 的状态改变时使用 StateChange 事件将消息写入控制台。
connection.StateChange += connection_StateChange; static void connection_StateChange(object sender, StateChangeEventArgs e) { Console.WriteLine("The current Connection state has changed from {0} to {1}.", e.OriginalState, e.CurrentState); }
在 ADO.NET 中的连接字符串
连接字符串包含作为参数从数据提供程序传递到数据源的初始化信息。其语法取决于数据提供程序,并且会在试图打开连接的过程中对连接字符串进行分析。语法错误会生成运行时异常,但其他错误只有在数据源收到信息后才会发生。经过验证后,数据源将应用连接字符串中指定的选项并打开连接。
连接字符串的格式是使用分号分隔的键/值参数对列表:
keyword1=value; keyword2=value;
关键字不区分大小写,并将忽略键/值对之间的空格。不过,根据数据源的不同,值可能是区分大小写的。任何包含分号、单引号或双引号的值必须用双引号引起来。
连接字符串生成器
连接字符串生成器旨在排除推测,防止出现语法错误和安全漏洞。
下面的示例演示SqlConnectionBuilder如何处理为Initial Catalog设置插入的额外值。
var builder = new SqlConnectionStringBuilder(); builder.DataSource = "(local)"; builder.IntegratedSecurity = true; builder.InitialCatalog = "AdventureWorks;NewValue=Bad"; Console.WriteLine(builder.ConnectionString);
输出结果表明,通过用双引号转义该额外值而不作为新的键/值对将其追加到连接字符串,SqlConnectionStringBuilder可以正确处理此额外值。
Data Source=(local);Initial Catalog="AdventureWorks;NewValue=Bad";Integrated Security=True
使用外部配置文件
外部配置文件是单独的文件,此类文件包含由一部分组成的配置文件的片段。外部配置文件由主配置文件引用。如果在部署完应用程序后连接字符串可能会被编辑,那么将connectionStrings节存储在物理上独立的文件中会很有用。
若要将连接字符串存储在外部配置文件中,请创建一个只包含connectionStrings节的单独的文件。不要包含任何其他的元素、节或属性。此示例演示外部配置文件的语法。
<connectionStrings> <add name="SQLServerConnectionString" connectionString="server=DTSZOPAULHUANG\SQLEXPRESS;initial catalog=TSQLFundamentals2008;integrated security=true" providerName="System.Data.SqlClient"/> </connectionStrings>
在主应用程序配置文件中,可以使用configSource属性指定外部文件的完全限定名和位置,此示例引用名为connections.config的外部配置文件。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <connectionStrings configSource="connections.config"></connectionStrings> </configuration>
连接字符串语法
Windows身份验证
建议使用Windows身份验证(有时也称为"集成安全性")连接到支持其的数据源。连接字符串中使用的语法根据提供程序的不同而不同。下面演示 SQL Server .NET Framework 数据提供程序的 Windows 身份验证语法。
Integrated Security=true; //or Integrated Security=SSPI;
下列语法使用 Windows 身份验证连接到特定的数据库。
connectionString="server=DTSZOPAULHUANG\SQLEXPRESS;initial catalog=TSQLFundamentals2008;integrated security=true"
SQL Server 登录
Windows 身份验证是用于连接到 SQL Server 的首选方法。但是,如果需要 SQL Server 身份验证,请使用下列语法来指定用户名和密码。
connectionString="server=DTSZOPAULHUANG\SQLEXPRESS;initial catalog=TSQLFundamentals2008;User ID=****;Password=****;Persist Security Info=False"
安全性注意
Persist Security Info 关键字的默认设置为 false .如果将其设置为 true ,则允许在打开连接后通过连接获取安全敏感信息(包括用户 ID 和密码)。保持将 Persist Securiy Info 设置为 false ,以确保不受信任的来源不能访问敏感的连接字符串信息。
注意
Windows 身份验证优先于 SQL Server 登录。如果同时指定 Integrated Securiy=true 以及用户名和密码,将忽略用户名和密码,而使用 Windows 身份验证。
SQL Sever 连接池
概述
连接到数据源可能需要很长时间。为了最大程度地降低打开连接的成本,ADO.NET 使用一种称为连接池的优化技术,这种技术可最大程度地降低重复打开和关闭连接所造成的成本。
连接到数据库服务器通常由几个需要很长时间的步骤组成。必须建立物理通道,必须与服务器进行初次握手,必须分析连接字符串信息,必须由服务器对连接进行身份验证等等。
实际上,大多数应用程序仅使用一个或几个不同的连接配置。这意味着在执行应用程序期间,许多相同的连接将反复地打开和关闭。为了使打开连接花费的系统开销最小,ADO.NET 使用称为连接池的优化方法。
连接池使新连接必须打开的次数得以减少。池进程保持物理连接的所有权。通过为每个给定的连接配置保留一组活动连接来管理连接。每当用户在连接上调用 Open 时,池进程就会查找池中可用的连接。如果某个池连接可用,会将该连接返回给调用者,而不是打开新连接。应用程序在该连接上调用 Close 时,池进程会将连接返回到活动连接池中,而不是关闭连接。连接返回到池中之后,即可在下一个 Open 调用中重复使用。
只有配置相同的连接可以建立池连接。ADO.NET 同时保留多个池,每种配置各一个。在使用集成的安全性时,连接按照连接字符串以及 Windows 标识分到多个池中。还根据连接是否已在事务中登记来建立池连接。池连接可以显著提高应用程序的性能和可缩放性。默认情况下,在 ADO.NET 中启用连接池,除非显式禁用。
为了比较形象的比较在打开及关闭连接池时的效果,下面进行示例演示。示例1是默认打开连接池进行多次数据库操作情况下的耗时情况,示例2是主动关闭连接池后对应的耗时情况。
示例1:
<connectionStrings> <add name="SQLServerConnectionString" connectionString="server=DTSZOPAULHUANG\SQLEXPRESS;initial catalog=TSQLFundamentals2008;Integrated Security=true;" providerName="System.Data.SqlClient"/> </connectionStrings>
public static class DataAccess { private static string _connectionString = null; public static int ExecuteNonQuerySql(string sql, SqlParameter[] sqlParameters = null) { _connectionString = ConfigurationManager.ConnectionStrings["SQLServerConnectionString"].ConnectionString; using (var connection = new SqlConnection(_connectionString)) { //connection.StateChange += connection_StateChange; var commmand = connection.CreateCommand(); commmand.CommandType = CommandType.Text; commmand.CommandText = sql; if (sqlParameters != null) { commmand.Parameters.AddRange(sqlParameters); } Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); connection.Open(); Console.WriteLine(stopwatch.ElapsedMilliseconds); return commmand.ExecuteNonQuery(); } } }
private void btnUpdate_Click(object sender, EventArgs e) { string sql = "UPDATE Sales.Shippers SET CompanyName = @CompanyName, Phone = @Phone WHERE ShipperId = @ShipperId"; var sqlParameters = new List<SqlParameter>(); sqlParameters.Add(new SqlParameter("CompanyName", "SML")); sqlParameters.Add(new SqlParameter("Phone", "13266779613")); sqlParameters.Add(new SqlParameter("ShipperId", 1)); int result = DataAccess.ExecuteNonQuerySql(sql, sqlParameters.ToArray()); MessageBox.Show(result.ToString()); }
//测试结果
示例2:修改连接字符串,使之禁用连接池,其他部分均不变动。
<connectionStrings> <add name="SQLServerConnectionString" connectionString="server=DTSZOPAULHUANG\SQLEXPRESS;initial catalog=TSQLFundamentals2008;Integrated Security=true;Pooling=False" providerName="System.Data.SqlClient"/> </connectionStrings>
//测试结果
池的创建和分配
在初次打开连接时,将根据完全匹配算法创建连接池,该算法将池与连接中的连接字符串关联。按进程、应用程序域、连接字符串以及 Windows 标识(在使用集成的安全性时)来建立池连接。连接字符串还必须是完全匹配的;按不同顺序为同一连接提供的关键字将分到单独的池中。
在以下的示例中创建了三个新的 SqlConnection 对象,但是管理时只需要两个连接池。注意连接字符串的不同,可以通过查看 connection 对象的 ClientConnectionId 属性查看连接的 ID。
public class Testing { public void Test() { using (SqlConnection connection = new SqlConnection( @"server=DTSZOPAULHUANG\SQLEXPRESS;initial catalog=TSQLFundamentals2008;Integrated Security=true;")) { connection.Open(); // Pool A is created. Console.WriteLine(connection.ClientConnectionId); } using (SqlConnection connection = new SqlConnection( @"server=DTSZOPAULHUANG\SQLEXPRESS;initial catalog=TSQLFundamentals2008;Integrated Security=SSPI;")) { connection.Open(); // Pool B is created because the connection strings differ. Console.WriteLine(connection.ClientConnectionId); } using (SqlConnection connection = new SqlConnection( @"server=DTSZOPAULHUANG\SQLEXPRESS;initial catalog=TSQLFundamentals2008;Integrated Security=true;")) { connection.Open(); // The connection string matches pool A. Console.WriteLine(connection.ClientConnectionId); } } }
//测试结果
从结果可以清楚的看到第一和第三个连接对象使用的是同一个连接。第二个连接对象使用得是新的连接。
如果MinPoolSize在连接字符串中未指定或指定为零,池中的连接将在一段时间不活动后关闭。但是,如果指定的MinPoolSize 大于零,在AppDomain被卸载并且进程结束之前,连接池不会被破坏。非活动或空池的维护只需要最少的系统开销。
添加连接
当创建一个池后,将创建多个连接对象并将其添加到该池中,以满足最小池大小的要求。连接根据需要添加到池中,但是不能超过指定的最大池大小(默认值为100)。连接在关闭或断开时释放回池中。在请求 SqlConnection 对象时,如果存在可用的连接,将从池中获取该对象。连接要可用,必须未使用,具有匹配的事务上下文或未与任何事务上下文关联,并且具有与服务器的有效链接。
连接池进程通过在连接释放回池中时重新分配连接,来满足这些连接请求。如果已达到最大池大小且不存在可用的连接,则该请求将会排队。然后,池进程尝试重新建立任何连接,直至到达超时时间。如果池进程在连接超时之前无法满足请求,将引发异常。
池碎片
因为集成安全性产生的池碎片
连接根据连接字符串以及用户标识来建立池连接。因此,如果使用网站上的基本身份验证或 Windows 身份验证以及集成的安全登录,每个用户将获得一个池。尽管这样可以提高单个用户的后续数据库请求的性能,但是该用户无法利用其他用户建立的连接。这样还使每个用户至少产生一个与数据库服务器的连接。这对特定的 Web 应用程序结构会产生副作用。
因为许多数据库产生的池碎片
许多 Internet 服务提供商在一台服务器上托管多个网站。他们可能使用单个数据库确认窗体身份验证登录,然后为该用户或用户组打开与特定数据库的连接。与身份验证数据库的连接将建立连接池,供每个用户使用。但是,每个数据库的连接存在一个独立的池,这会增加与服务的连接数。
这也会对应用程序设计产生副作用。可通过一种相对简单的方法来避免此副作用,而不会影响连接到 SQL Server 时的安全性。连接到服务器上的相同数据库而不是为每个用户或组连接到单独的数据库,然后执行 T-SQL USE 语句来切换所需数据库。