<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>Eric's Zone...</title><link>http://www.mscenter.edu.cn/blog/eric_dyx/</link><description>..........常记一二.........</description><managingEditor>Ericdyx</managingEditor><dc:language>zh-CHS</dc:language><generator>.Text Version 0.958.2004.214</generator><item><dc:creator>Ericdyx</dc:creator><title>SqlDataReader和DataSet的概述</title><link>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/02/04/804.html</link><pubDate>Thu, 03 Feb 2005 18:21:00 GMT</pubDate><guid>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/02/04/804.html</guid><wfw:comment>http://www.mscenter.edu.cn/blog/eric_dyx/comments/804.html</wfw:comment><comments>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/02/04/804.html#Feedback</comments><slash:comments>90</slash:comments><wfw:commentRss>http://www.mscenter.edu.cn/blog/eric_dyx/comments/commentRss/804.html</wfw:commentRss><trackback:ping>http://www.mscenter.edu.cn/blog/eric_dyx/services/trackbacks/804.html</trackback:ping><description>&lt;H2 class=dtH1&gt;&lt;A name=daag_performance&gt;&lt;/A&gt;Performance&lt;/H2&gt;
&lt;P&gt;This section introduces a number of common data access scenarios, and for each one, provides details about the most high-performance and scalable solution in terms of ADO.NET data access code. Where appropriate, performance, functionality, and development effort are compared. This section considers the following functional scenarios: 
&lt;UL type=disc&gt;
&lt;LI&gt;. Retrieving a result set and iterating through the retrieved rows. 
&lt;LI&gt;. Retrieving a single row with a specified primary key. 
&lt;LI&gt;. Retrieving a single item from a specified row. 
&lt;LI&gt;. Checking to see whether or not a row with a particular primary key exists. This is a variation of the single item lookup scenario in which a simple Boolean return is sufficient. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Retrieving Multiple Rows&lt;/H3&gt;
&lt;P&gt;In this scenario, you want to retrieve a tabulated set of data and iterate through the retrieved rows to perform an operation. For example, you might want to retrieve a set of data, work with it in disconnected fashion, and pass it to a client application as an XML document (perhaps through a Web service). Alternatively, you might want to display the data in the form of a HTML table.&lt;/P&gt;
&lt;P&gt;To help determine the most appropriate data access approach, consider whether you require the added flexibility of the (disconnected) &lt;B&gt;DataSet &lt;/B&gt;object, or the raw performance offered by the &lt;B&gt;SqlDataReader &lt;/B&gt;object, which is ideally suited to data presentation in business-to consumer (B2C) Web applications. &lt;BR&gt;&lt;/P&gt;
&lt;H4 class=dtH1&gt;Data Binding&lt;/H4&gt;
&lt;P&gt;All three of these objects can act as data sources for data-bound controls, although the &lt;B&gt;DataSet&lt;/B&gt; and &lt;B&gt;DataTable&lt;/B&gt; can act as data sources for a wider variety of controls than the &lt;B&gt;SqlDataReader&lt;/B&gt;. This is because the &lt;B&gt;DataSet&lt;/B&gt; and &lt;B&gt;DataTable&lt;/B&gt; implement &lt;B&gt;IListSource&lt;/B&gt; (yielding &lt;B&gt;IList&lt;/B&gt;), whereas the &lt;B&gt;SqlDataReader&lt;/B&gt; implements &lt;B&gt;IEnumerable&lt;/B&gt;. A number of WinForm controls capable of data binding require a data source that implements &lt;B&gt;IList&lt;/B&gt;. &lt;/P&gt;
&lt;P&gt;This difference is due to the type of scenario for which each object type is designed. The &lt;B&gt;DataSet&lt;/B&gt; (which includes the &lt;B&gt;DataTable&lt;/B&gt;) is a rich, disconnected structure suited to both Web and desktop (WinForm) scenarios. The data reader, on the other hand, is optimized for Web applications that require optimized forward-only data access.&lt;/P&gt;&lt;img src ="http://www.mscenter.edu.cn/blog/eric_dyx/aggbug/804.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>Ericdyx</dc:creator><title>关于ConnectionString的理解总结</title><link>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/02/04/803.html</link><pubDate>Thu, 03 Feb 2005 16:53:00 GMT</pubDate><guid>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/02/04/803.html</guid><wfw:comment>http://www.mscenter.edu.cn/blog/eric_dyx/comments/803.html</wfw:comment><comments>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/02/04/803.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.mscenter.edu.cn/blog/eric_dyx/comments/commentRss/803.html</wfw:commentRss><trackback:ping>http://www.mscenter.edu.cn/blog/eric_dyx/services/trackbacks/803.html</trackback:ping><description>1）使用Trusted_Connection=true的情况&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 在Sql Server中的&amp;#8220;安全性&amp;#8221;那里必须建立网络访问的系统用户的帐号，该帐号的权限应该是包括对对应数据库实例的操作权限，然后在web.config中的authentication结点的值应该改为windows，这样网络访问就可以通过windows帐号访问数据库，而无需在web.config里明文写上uid和pwd。&lt;BR&gt;&lt;BR&gt;2）使用uid和pwd&lt;BR&gt;sqlserver中一定要有该用户，并有相应的权限，但这个帐号不是系统的帐号，然后在sqlserver的验证那里一定要设置&amp;#8220;sqlserver和windows验证&amp;#8221;，在web.config中设置authentication结点的值为forms&lt;img src ="http://www.mscenter.edu.cn/blog/eric_dyx/aggbug/803.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>Ericdyx</dc:creator><title>Managing Database Connections</title><link>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/02/02/798.html</link><pubDate>Wed, 02 Feb 2005 04:09:00 GMT</pubDate><guid>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/02/02/798.html</guid><wfw:comment>http://www.mscenter.edu.cn/blog/eric_dyx/comments/798.html</wfw:comment><comments>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/02/02/798.html#Feedback</comments><slash:comments>59</slash:comments><wfw:commentRss>http://www.mscenter.edu.cn/blog/eric_dyx/comments/commentRss/798.html</wfw:commentRss><trackback:ping>http://www.mscenter.edu.cn/blog/eric_dyx/services/trackbacks/798.html</trackback:ping><description>&lt;H2 class=dtH1&gt;&lt;A name=daag_managingdatabaseconnections&gt;&lt;/A&gt;Managing Database Connections&lt;/H2&gt;
&lt;P&gt;Database connections represent a critical, expensive, and limited resource, particularly in a multitier Web application. It is imperative that you manage your connections correctly because your approach can significantly affect the overall scalability of your application. Also, think carefully about where to store connection strings. You need a configurable and secure location.&lt;/P&gt;
&lt;P&gt;When managing database connections and connection strings, you should strive to: 
&lt;UL type=disc&gt;
&lt;LI&gt;Help realize application scalability by multiplexing a pool of database connections across multiple clients. 
&lt;LI&gt;Adopt a configurable and high performance connection pooling strategy. 
&lt;LI&gt;Use Windows authentication when accessing SQL&amp;nbsp;Server. 
&lt;LI&gt;Avoid impersonation in the middle tier. 
&lt;LI&gt;Store connection strings securely. 
&lt;LI&gt;Open database connections late and close them early. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Using Connection Pooling&lt;/H3&gt;
&lt;P&gt;Database connection pooling allows an application to reuse an existing connection from a pool instead of repeatedly establishing a new connection with the database. This technique can significantly increase the scalability of an application, because a limited number of database connections can serve a much larger number of clients. This technique also improves performance, because the significant time required to establish a new connection can be avoided.&lt;BR&gt;&lt;/P&gt;
&lt;H4 class=dtH1&gt;Configuring SQL Server .NET Data Provider Connection Pooling&lt;/H4&gt;
&lt;P&gt;You can configure connection pooling by using a set of name-value pairs, supplied by means of the connection string. For example, you can configure whether or not pooling is enabled (it is enabled by default), the maximum and minimum pool sizes, and the amount of time that a queued request to open a connection can block. The following is an example connection string that configures the maximum and minimum pool sizes.&lt;/P&gt;&lt;PRE class=code&gt;"Server=(local); Integrated Security=SSPI; Database=Northwind; 
Max Pool Size=75; Min Pool Size=5"
  &lt;/PRE&gt;
&lt;P&gt;When a connection is opened and a pool is created, multiple connections are added to the pool to bring the connection count to the configured minimum level. Connections can be subsequently added to the pool up to the configured maximum pool count. When the maximum count is reached, new requests to open a connection are queued for a configurable duration. &lt;BR&gt;&lt;/P&gt;
&lt;H3 class=dtH1&gt;Storing Connection Strings&lt;/H3&gt;
&lt;P&gt;To store database connection strings, you have a variety of options with different degrees of flexibility and security. Although hard coding a connection string within source code offers the best performance, file system caching ensures that the performance degradation associated with storing the string externally in the file system is negligible. The extra flexibility provided by an external connection string, which supports administrator configuration, is preferred in virtually all cases.&lt;BR&gt;&lt;/P&gt;
&lt;H4 class=dtH1&gt;&lt;A name=daag_usingxmlappconfigfiles&gt;&lt;/A&gt;Using XML Application Configuration Files&lt;/H4&gt;
&lt;P&gt;You can use the &amp;lt;&lt;B&gt;appSettings&lt;/B&gt;&amp;gt; element to store a database connection string in the custom settings section of an application configuration file. This element supports arbitrary key-value pairs, as illustrated in the following fragment:&lt;/P&gt;&lt;PRE class=code&gt;&amp;lt;configuration&amp;gt;
 &amp;lt;appSettings&amp;gt;
  &amp;lt;add key="DBConnStr"
     value="server=(local);Integrated Security=SSPI;database=northwind"/&amp;gt;
 &amp;lt;/appSettings&amp;gt;
&amp;lt;/configuration&amp;gt;
  &lt;/PRE&gt;
&lt;BLOCKQUOTE class=dtBlock&gt;&lt;B class=le&gt;Note&lt;/B&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;The &amp;lt;&lt;B&gt;appSettings&lt;/B&gt;&amp;gt; element appears under the &amp;lt;&lt;B&gt;configuration&lt;/B&gt;&amp;gt; element and not directly under &amp;lt;&lt;B&gt;system.web&lt;/B&gt;&amp;gt;.&lt;/BLOCKQUOTE&gt;
&lt;H4 class=dtH1&gt;Advantages&lt;/H4&gt;
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Ease of deployment&lt;/B&gt;. The connection string is deployed along with the configuration file through regular .NET &lt;B&gt;xcopy &lt;/B&gt;deployment. 
&lt;LI&gt;&lt;B&gt;Ease of programmatic access&lt;/B&gt;. The &lt;B&gt;AppSettings&lt;/B&gt; property of the &lt;B&gt;ConfigurationSettings&lt;/B&gt; class makes reading the configured database connection string an easy task at run time. 
&lt;LI&gt;&lt;B&gt;Support of dynamic update&lt;/B&gt; &lt;B&gt;(ASP.NET only)&lt;/B&gt;. If an administrator updates the connection string in a Web.config file, the change will be picked up the next time the string is accessed, which for a stateless component is likely to be the next time a client uses the component to make a data access request. &lt;/LI&gt;&lt;/UL&gt;
&lt;H4 class=dtH1&gt;Disadvantages&lt;/H4&gt;
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Security&lt;/B&gt;. Although the ASP.NET Internet Server Application Programming Interface (ISAPI) dynamic-link library (DLL) prevents clients from directly accessing files with a .config file extension and NTFS permissions can be used to further restrict access, you might still want to avoid storing these details in clear text on a front-end Web server. For added security, store the connection string in encrypted format in the configuration file. &lt;/LI&gt;&lt;/UL&gt;
&lt;H4 class=dtH1&gt;More Information&lt;/H4&gt;
&lt;UL type=disc&gt;
&lt;LI&gt;You can retrieve custom application settings by using the static &lt;B&gt;AppSettings&lt;/B&gt; property of the &lt;B&gt;System.Configuration.ConfigurationSettings&lt;/B&gt; class. This is shown in the following code fragment, which assumes the previously illustrated custom key called &lt;B&gt;DBConnStr&lt;/B&gt;: &lt;PRE class=code&gt;using System.Configuration;
private string GetDBaseConnectionString()
{
  return ConfigurationSettings.AppSettings["DBConnStr"];
}
  &lt;H3 class=dtH1&gt;Connection Usage Patterns&lt;/H3&gt;&lt;P&gt;Irrespective of the .NET data provider you use, you must always: &lt;UL type=disc&gt;&lt;LI&gt;Open a database connection as late as possible. &lt;LI&gt;Use the connection for as short a period as possible. &lt;LI&gt;Close the connection as soon as possible. The connection is not returned to the pool until it is closed through either the &lt;B&gt;Close&lt;/B&gt; or &lt;B&gt;Dispose&lt;/B&gt; method. You should also close a connection even if you detect that it has entered the broken state. This ensures that it is returned to the pool and marked as invalid. The object pooler periodically scans the pool, looking for objects that have been marked as invalid. &lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;To guarantee that the connection is closed before a method returns, consider one of the approaches illustrated in the two code samples that follow. The first uses a &lt;B&gt;finally&lt;/B&gt; block. The second uses a C# &lt;B&gt;using&lt;/B&gt; statement, which ensures that an object's &lt;B&gt;Dispose&lt;/B&gt; method is called.&lt;/P&gt;&lt;P&gt;The following code ensures that a &lt;B&gt;finally&lt;/B&gt; block closes the connection. Note that this approach works for both Visual Basic .NET and C# because Visual Basic .NET supports structured exception handling.&lt;/P&gt;&lt;PRE class=code&gt;public void DoSomeWork()
{
  SqlConnection conn = new SqlConnection(connectionString);
  SqlCommand cmd = new SqlCommand("CommandProc", conn );
  cmd.CommandType = CommandType.StoredProcedure;

  try
  {
    conn.Open();
    cmd.ExecuteNonQuery();
  }
  catch (Exception e)
  {
    // Handle and log error
  }
  finally
  {
    conn.Close();
  }
}
  &lt;/PRE&gt;&lt;P&gt;The following code shows an alternate approach that uses a C# &lt;B&gt;using&lt;/B&gt; statement. Note that Visual Basic .NET does not provide a &lt;B&gt;using&lt;/B&gt; statement or any equivalent functionality.&lt;/P&gt;&lt;PRE class=code&gt;public void DoSomeWork()
{
  // using guarantees that Dispose is called on conn, which will
  // close the connection.
  using (SqlConnection conn = new SqlConnection(connectionString))
  {
    SqlCommand cmd = new SqlCommand("CommandProc", conn);
    fcmd.CommandType = CommandType.StoredProcedure;
    conn.Open();
    cmd.ExecuteQuery();
  }
}
&lt;/PRE&gt;&lt;/PRE&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;img src ="http://www.mscenter.edu.cn/blog/eric_dyx/aggbug/798.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>Ericdyx</dc:creator><title>打包中把.net framework一起打包的方法</title><link>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/31/790.html</link><pubDate>Sun, 30 Jan 2005 17:54:00 GMT</pubDate><guid>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/31/790.html</guid><wfw:comment>http://www.mscenter.edu.cn/blog/eric_dyx/comments/790.html</wfw:comment><comments>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/31/790.html#Feedback</comments><slash:comments>19</slash:comments><wfw:commentRss>http://www.mscenter.edu.cn/blog/eric_dyx/comments/commentRss/790.html</wfw:commentRss><trackback:ping>http://www.mscenter.edu.cn/blog/eric_dyx/services/trackbacks/790.html</trackback:ping><description>&lt;H4 class=dtH1&gt;Creating the Settings.ini file&lt;/H4&gt;
&lt;P&gt;The Setup.exe Bootstrapper sample uses the external file, &lt;I&gt;settings.ini,&lt;/I&gt; to determine the following: 
&lt;UL type=disc&gt;
&lt;LI&gt;The location of Dotnetfx.exe and the host application. 
&lt;LI&gt;The language version of the .NET Framework for which it is checking. 
&lt;LI&gt;Custom strings to use for Setup.exe dialog boxes. &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;The default location for &lt;I&gt;settings.ini &lt;/I&gt;is in the same folder as Setup.exe. You can copy the &lt;I&gt;settings.ini&lt;/I&gt; file provided with the Setup.exe Bootstrapper sample and then edit the file as necessary to specify the correct values for your application.&lt;/P&gt;
&lt;P class=label&gt;&lt;B&gt;Specify the location of Dotnetfx.exe and the host application&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;The Setup.exe Bootstrapper sample requires Dotnetfx.exe and the host .NET Framework application to be in a known location relative to Setup.exe. The Setup.exe Bootstrapper sample uses the external file, &lt;I&gt;settings.ini,&lt;/I&gt; to determine the location of Dotnetfx.exe and the host application. You can copy the &lt;I&gt;settings.ini&lt;/I&gt; file provided with the sample and then edit the file to point to the locations of your .NET Framework host application and Dotnetfx.exe. &lt;/P&gt;
&lt;P&gt;The following examples of &lt;I&gt;settings.ini&lt;/I&gt; files illustrate how to point to a Windows Installer package (.msi file) for a host .NET Framework application and Dotnetfx.exe in various locations relative to the location of Setup.exe.&lt;/P&gt;
&lt;P&gt;The following example &lt;I&gt;settings.ini&lt;/I&gt; file points to the Windows Installer package for a .NET Framework application, &lt;CODE class=ce&gt;mySetup.msi,&lt;/CODE&gt; and &lt;CODE class=ce&gt;Dotnetfx.exe&lt;/CODE&gt; located in the same folder as Setup.exe.&lt;/P&gt;&lt;PRE class=code&gt;[Bootstrap]
Msi=mySetup.msi
'LanguageDirectory=
ProductName= My Application 
DialogText=
CaptionText=My Application
ErrorCaptionText= My Application Error
FxInstallerPath=c:\myProjectFolder\
&lt;/PRE&gt;
&lt;P&gt;The following example &lt;I&gt;settings.ini&lt;/I&gt; file points to the Windows Installer package for a .NET Framework application, &lt;CODE class=ce&gt;mySetup.msi&lt;/CODE&gt;, and &lt;CODE class=ce&gt;Dotnetfx.exe&lt;/CODE&gt; located on a network share, separate from Setup.exe.&lt;/P&gt;&lt;PRE class=code&gt;[Bootstrap]
Msi=\\myNetworkShare\myProjectFolder\mySetup.msi
'LanguageDirectory=
ProductName=My Application
'DialogText=
CaptionText=My Application
ErrorCaptionText= My Application Error
FxInstallerPath=\\myNetworkShare\myProjectFolder\
&lt;/PRE&gt;
&lt;P&gt;The following example &lt;I&gt;settings.ini&lt;/I&gt; file points to the Windows Installer package for a .NET Framework application, &lt;CODE class=ce&gt;mySetup.msi&lt;/CODE&gt;, and &lt;CODE class=ce&gt;Dotnetfx.exe&lt;/CODE&gt; located on the same computer as Setup.exe, but in different folders.&lt;/P&gt;&lt;PRE class=code&gt;[Bootstrap]
Msi=c:\myInstallerFiles\mySetup.msi
'LanguageDirectory=
ProductName= My Application
'DialogText=
CaptionText=My Application
ErrorCaptionText= My Application Error
FxInstallerPath=c:\myExecutables\
&lt;/PRE&gt;
&lt;P&gt;The following example &lt;I&gt;settings.ini&lt;/I&gt; file points to the Windows Installer package for a .NET Framework application, &lt;CODE class=ce&gt;mySetup.msi&lt;/CODE&gt;, and &lt;CODE class=ce&gt;Dotnetfx.exe&lt;/CODE&gt; located on the same CD as Setup.exe, but in different folders.&lt;/P&gt;&lt;PRE class=code&gt;[Bootstrap]
Msi=myInstallerFiles/mySetup.msi
'LanguageDirectory=
ProductName= My Application
'DialogText=
CaptionText=My Application
ErrorCaptionText= My Application Error
FxInstallerPath=myExecutables/
&lt;/PRE&gt;
&lt;P class=label&gt;&lt;B&gt;Specify the language version&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;If you want Setup.exe to check for a build number and a language version of the .NET Framework, you must specify the culture name of the language to check for in the &lt;I&gt;settings.ini&lt;/I&gt; file. If you do not specify a culture name, Setup.exe checks for the English language version by default.&lt;/P&gt;
&lt;P&gt;The following table lists the available language versions and the culture name to specify in the &lt;I&gt;settings.ini&lt;/I&gt; file.&lt;/P&gt;
&lt;TABLE class=data&gt;
&lt;TBODY&gt;
&lt;TR vAlign=top&gt;
&lt;TH class=data align=left width="45%"&gt;Language&lt;/TH&gt;
&lt;TH class=data align=left width="55%"&gt;Culture name&lt;/TH&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="45%"&gt;Chinese (Simplified)&lt;/TD&gt;
&lt;TD class=data width="55%"&gt;CHS&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="45%"&gt;Chinese (Traditional)&lt;/TD&gt;
&lt;TD class=data width="55%"&gt;CHT&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="45%"&gt;French&lt;/TD&gt;
&lt;TD class=data width="55%"&gt;fr&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="45%"&gt;German&lt;/TD&gt;
&lt;TD class=data width="55%"&gt;de&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="45%"&gt;Italian&lt;/TD&gt;
&lt;TD class=data width="55%"&gt;it&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="45%"&gt;Japanese&lt;/TD&gt;
&lt;TD class=data width="55%"&gt;ja&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="45%"&gt;Korean&lt;/TD&gt;
&lt;TD class=data width="55%"&gt;ko&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="45%"&gt;Spanish&lt;/TD&gt;
&lt;TD class=data width="55%"&gt;es&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;
&lt;P&gt;The following example &lt;I&gt;settings.ini&lt;/I&gt; file instructs Setup.exe to check for the French language version of Dotnetfx.exe.&lt;/P&gt;&lt;PRE class=code&gt;[Bootstrap]
Msi=mySetup.msi
LanguageDirectory=fr
ProductName= My Application 
'DialogText=
CaptionText=My Application
ErrorCaptionText= My Application Error
FxInstallerPath=c:\myProjectFolder\
&lt;/PRE&gt;
&lt;H4 class=dtH1&gt;Customize strings for Setup.exe dialog boxes&lt;/H4&gt;
&lt;P&gt;You can edit the following variables in the &lt;I&gt;settings.ini&lt;/I&gt; file to customize strings displayed in Setup.exe dialog boxes. 
&lt;DL&gt;
&lt;DT&gt;&lt;B&gt;ProductName&lt;/B&gt; 
&lt;DD&gt;Specifies the name of the .NET Framework application Setup.exe will install. The product name that you specify is used to customize the text in the dialog box that appears after launching Setup.exe. For example, if you specify My Application as ProductName, the dialog box text reads, "To start My Application Setup, click OK. To quit without installing, click Cancel." 
&lt;P&gt;If you do not customize the CaptionText variable, ProductName is also used to customize the title bar for the dialog box that appears after launching Setup.exe. For example, if you specify My Application as the ProductName, the title bar displays "My Application Setup." If you customize both ProductName and CaptionText, CaptionText is used to customize the title bar text in this dialog box. 
&lt;P&gt;ProductName is also used to customize the title bar text in the success dialog box displayed when the application is finished installing. If you do not customize ProductName, the default text "Application Setup" is displayed. &lt;/P&gt;
&lt;DT&gt;&lt;B&gt;DialogText&lt;/B&gt; 
&lt;DD&gt;Specifies the custom text to display in the dialog box that is displayed after launching Setup.exe. If you do not customize DialogText, the default text "To start Application Setup, click OK. To quit without installing, click Cancel" is displayed. 
&lt;DT&gt;&lt;B&gt;CaptionText&lt;/B&gt; 
&lt;DD&gt;Specifies the custom text to display in the title bar of the dialog box that appears after launching Setup.exe. If you do not customize CaptionText, ProductName is displayed. If ProductName is not customized, the default text "Application Setup" is displayed. 
&lt;DT&gt;&lt;B&gt;ErrorCaptionText&lt;/B&gt; 
&lt;DD&gt;Specifies the custom text to display in the title bar of Setup.exe error dialog boxes. If you do not customize ErrorCaptionText, the default text "Application Setup Error" is displayed. &lt;/DD&gt;&lt;/DL&gt;&lt;img src ="http://www.mscenter.edu.cn/blog/eric_dyx/aggbug/790.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>Ericdyx</dc:creator><title>第四章   ARP地址解析协议</title><link>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/27/787.html</link><pubDate>Thu, 27 Jan 2005 02:44:00 GMT</pubDate><guid>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/27/787.html</guid><wfw:comment>http://www.mscenter.edu.cn/blog/eric_dyx/comments/787.html</wfw:comment><comments>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/27/787.html#Feedback</comments><slash:comments>7</slash:comments><wfw:commentRss>http://www.mscenter.edu.cn/blog/eric_dyx/comments/commentRss/787.html</wfw:commentRss><trackback:ping>http://www.mscenter.edu.cn/blog/eric_dyx/services/trackbacks/787.html</trackback:ping><description>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;地址解析为两种不同的地址形式提供映射：32bit的IP地址和数据链路层使用的任何类型的地址。在A R P背后有一个基本概念，那就是网络接口有一个硬件地址（一个48 bit的值，标识不同的以太网或令牌环网络接口）。在硬件层次上进行的数据帧交换必须有正确的接口地址。但是，T C P / I P有自己的地址： 32 bit的I P地址。知道主机的I P地址并不能让内核发送一帧数据给主机。内核（如以太网驱动程序）必须知道目的端的硬件地址才能发送数据。A R P的功能是在32 bit的I P地址和采用不同网络技术的硬件地址之间提供动态映射。点对点链路不使用A R P，必须告知内核链路每一端的I P地址。&lt;BR&gt;&lt;BR&gt;4.3&amp;nbsp;&amp;nbsp; ARP高速缓存&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;A R P高效运行的关键是由于每个主机上都有一个A R P高速缓存。这个高速缓存存放了最近I n t e r n e t地址到硬件地址之间的映射记录。命令行环境下键入arp －a显示高速缓存中的所有内容。&lt;BR&gt;&lt;BR&gt;&lt;BR&gt;4.4&amp;nbsp;&amp;nbsp; ARP的分组格式&lt;BR&gt;&lt;IMG height=118 src="/blog/images/mscenter_edu_cn/eric_dyx/3.JPG" width=650 border=0&gt;&lt;BR&gt;&lt;img src ="http://www.mscenter.edu.cn/blog/eric_dyx/aggbug/787.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>Ericdyx</dc:creator><title>第三章 IP：网际协议</title><link>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/27/786.html</link><pubDate>Wed, 26 Jan 2005 17:31:00 GMT</pubDate><guid>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/27/786.html</guid><wfw:comment>http://www.mscenter.edu.cn/blog/eric_dyx/comments/786.html</wfw:comment><comments>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/27/786.html#Feedback</comments><slash:comments>8</slash:comments><wfw:commentRss>http://www.mscenter.edu.cn/blog/eric_dyx/comments/commentRss/786.html</wfw:commentRss><trackback:ping>http://www.mscenter.edu.cn/blog/eric_dyx/services/trackbacks/786.html</trackback:ping><description>3.1&amp;nbsp;&amp;nbsp;&amp;nbsp; IP首部&lt;BR&gt;IP提供不可靠、无连接的数据报传送服务。&lt;BR&gt;不可靠（ u n r e l i a b l e）的意思是它不能保证I P数据报能成功地到达目的地。I P仅提供最好的传输服务。如果发生某种错误时，如某个路由器暂时用完了缓冲区， I P有一个简单的错误处理算法：丢弃该数据报，然后发送I C M P消息报给信源端。任何要求的可靠性必须由上层来&lt;BR&gt;提供（如T C P）。&lt;BR&gt;无连接（ c o n n e c t i o n l e s s）这个术语的意思是I P并不维护任何关于后续数据报的状态信息。每个数据报的处理是相互独立的。这也说明， I P数据报可以不按发送顺序接收。如果一信源向相同的信宿发送两个连续的数据报（先是A，然后是B），每个数据报都是独立地进行路由选择，可能选择不同的路线，因此B可能在A到达之前先到达。&lt;BR&gt;&lt;IMG height=297 src="/blog/images/mscenter_edu_cn/eric_dyx/1.JPG" width=467 border=0&gt;&lt;BR&gt;1）服务类型（TO S）字段（8bit）包括一个3 bit的优先权子字段（现在已被忽略），4 bit的TO S子字段和1 bit未用位但必须置0。4 bit的TO S分别代表：最小时延、最大吞吐量、最高可靠性和最小费用。&lt;BR&gt;2）总长度字段是指整个I P数据报的长度，以字节为单位。利用首部长度字段和总长度字段，可以知道I P数据报中数据内容的起始位置和长度。&lt;BR&gt;3）标识字段唯一地标识主机发送的每一份数据报。通常每发送一份报文它的值就会加1。&lt;BR&gt;4）T T L（t i m e - t o - l i v e）生存时间字段设置了数据报可以经过的最多路由器数。它指定了数据报的生存时间。T T L的初始值由源主机设置（通常为3 2或6 4），一旦经过一个处理它的路由器，它的值就减去1。当该字段的值为0时，数据报就被丢弃，并发送I C M P报文通知源主机。&lt;BR&gt;&lt;BR&gt;3.3&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; IP路由选择&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;如果目的主机与源主机直接相连（如点对点链路）或都在一个共享网络上（以太网或令牌环网），那么IP数据报就直接送到目的主机上，否则，数据报发往路由器上，由路由器来转发。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在一般的体制中，I P可以从T C P、U D P、I C M P和I G M P接收数据报（即在本地生成的数据报）并进行发送，或者从一个网络接口接收数据报（待转发的数据报）并进行发送。I P层在内存中有一个路由表。当收到一份数据报并进行发送时，它都要对该表搜索一次。当数据报来自某个网络接口时， I P首先检查目的I P地址是否为本机的I P地址之一或者I P广播地址。如果确实是这样，数据报就被送到由I P首部协议字段所指定的协议模块进行处理。如果数据报的目的不是这些地址，那么（ 1）如果I P层被设置为路由器的功能，那么就对数据报进行转发（也就是说，像下面对待发出的数据报一样处理）；否则（ 2）数据报被丢弃。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;路由表中的每一项都包含下面这些信息：目的IP地址（可以是完整的主机地址，也可以是一个网络地址，有该表目的标志字段指定）；&amp;nbsp;&amp;nbsp; 下一站路由器的IP地址；&amp;nbsp;&amp;nbsp; 标志（指明目的IP地址是网络地址还是主机地址）。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;所有的IP路由选择只为数据报传输提供下一站路由器的IP地址，IP路由选择主要完成以下功能：1) 搜索路由表，寻找能与目的I P地址完全匹配的表目（网络号和主机号都要匹配）；2) 搜索路由表，寻找能与目的网络号相匹配的表目。如果找到，则把报文发送给该表目指定的下一站路由器或直接连接的网络接口；3）搜索路由表，寻找标为&amp;#8220;默认（ d e f a u l t）&amp;#8221;的表目。如果找到，则把报文发送给该表目指定的下一站路由器。&lt;BR&gt;&lt;BR&gt;3.4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 子网寻址&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;A类和B类地址为主机号分配了太多的空间，由当地的系统管理员决定是否进行分配，是否建立子网，例如这里有一个B类网络地址（1 4 0 . 2 5 2），在剩下的16 bit中，8 bit用于子网号， 8 bit用于主机号，格式如图3 - 5所示。这样就允许有2 5 4个子网，每个子网可以有2 5 4台主机。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;与3 0个C类地址相比，用一个包含3 0个子网的B类地址的好处是，它可以缩小I n t e r n e t路由&lt;BR&gt;的规模。B类地址1 4 0 . 2 5 2被划分为若干子网的事实对于所有子网以外的I n t e r n e t路由器都是透明的。为了到达I P地址开始部分为1 4 0 . 2 5 2的主机，外部路由器只需要知道通往I P地址1 4 0 . 2 5 2 . 1 0 4 . 1的路径。这就是说，对于网络1 4 0 . 2 5 2只需一个路由表目，而如果采用3 0个C类地址，则需要3 0个路由表目。因此，子网划分缩减了路由表的规模。&lt;BR&gt;&lt;BR&gt;3.5&amp;nbsp;&amp;nbsp;&amp;nbsp; 子网掩码&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;除了I P地址以外，主机还需要知道有多少比特用于子网号及多少比特用于主机号。这是在引导过程中通过子网掩码来确定的。这个掩码是一个32 bit的值，其中值为1的比特留给网络号和子网号，为0的比特留给主机号。&lt;BR&gt;&lt;IMG height=146 src="/blog/images/mscenter_edu_cn/eric_dyx/2.JPG" width=565 border=0&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;假设我们的主机地址是1 4 0 . 2 5 2 . 1 . 1（一个B类地址），而子网掩码为2 5 5 . 2 5 5 . 2 5 5 . 0（其中8&lt;BR&gt;b i t为子网号， 8 bit为主机号）。&lt;BR&gt;&amp;#8226; 如果目的I P地址是1 4 0 . 2 5 2 . 4 . 5，那么我们就知道B类网络号是相同的（ 1 4 0 . 2 5 2），但是子网号是不同的（1和4）。用子网掩码在两个I P地址之间的比较如图3 - 8所示。&lt;BR&gt;&amp;#8226; 如果目的I P地址是1 4 0 . 2 5 2 . 1 . 2 2，那么B类网络号还是一样的（ 1 4 0 . 2 5 2），而且子网号也是一样的（ 1），但是主机号是不同的。&lt;BR&gt;&amp;#8226; 如果目的I P地址是1 9 2 . 4 3 . 2 3 5 . 6（一个C类地址），那么网络号是不同的，因而进一步的比较就不用再进行了。&lt;BR&gt;&lt;BR&gt;3.9&amp;nbsp;&amp;nbsp;&amp;nbsp; netstat命令&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;提供系统上的接口信息。netstat -n打印出每个接口的源地址、目的地址还有状态。在第九章中会用netstat命令检查路由表。&lt;img src ="http://www.mscenter.edu.cn/blog/eric_dyx/aggbug/786.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>Ericdyx</dc:creator><title>第二章 链路层</title><link>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/27/777.html</link><pubDate>Wed, 26 Jan 2005 16:44:00 GMT</pubDate><guid>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/27/777.html</guid><wfw:comment>http://www.mscenter.edu.cn/blog/eric_dyx/comments/777.html</wfw:comment><comments>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/27/777.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.mscenter.edu.cn/blog/eric_dyx/comments/commentRss/777.html</wfw:commentRss><trackback:ping>http://www.mscenter.edu.cn/blog/eric_dyx/services/trackbacks/777.html</trackback:ping><description>链路层主要有三个目的：（1）为I P模块发送和接收I P数据报；（ 2）为A R P模块发送A R P请求和接收A R P应答；（3）为R A R P发送R A R P请求和接收R A R P应答。&lt;BR&gt;&amp;nbsp;I E E E（电子电气工程师协会） 8 0 2委员会公布了一个稍有不同的标准集，其中&lt;BR&gt;8 0 2 . 3针对整个C S M A / C D网络，8 0 2 . 4针对令牌总线网络， 8 0 2 . 5针对令牌环网络。&lt;img src ="http://www.mscenter.edu.cn/blog/eric_dyx/aggbug/777.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>Ericdyx</dc:creator><title>1－2 分层</title><link>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/26/774.html</link><pubDate>Wed, 26 Jan 2005 11:52:00 GMT</pubDate><guid>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/26/774.html</guid><wfw:comment>http://www.mscenter.edu.cn/blog/eric_dyx/comments/774.html</wfw:comment><comments>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/26/774.html#Feedback</comments><slash:comments>126</slash:comments><wfw:commentRss>http://www.mscenter.edu.cn/blog/eric_dyx/comments/commentRss/774.html</wfw:commentRss><trackback:ping>http://www.mscenter.edu.cn/blog/eric_dyx/services/trackbacks/774.html</trackback:ping><description>TCP/IP通常被认为是一个四层协议系统&lt;BR&gt;1）链路层。通常包括操作系统中的设备驱动程序喝计算机中对应的网络接口卡，一起处理与电缆的物理接口细节。&lt;BR&gt;2）网络层。处理分组在网络中的活动，如分组的选路，网络层协议包括IP协议（网际协议），ICMP协议（internet互联网控制报文协议），IGMP协议（Internet组管理协议）。&lt;BR&gt;3）运输层。两台主机桑的应用程序提供端对端的通信。TCP（传输控制协议）和UDP（用户数据报协议）。&lt;BR&gt;4）应用层。负责处理特定的应用程序细节。如telnet远程登陆、Ftp文件传输协议、SMTP简单有机爱你传送协议、SNMP简单网络管理协议。&lt;BR&gt;&lt;BR&gt;IP路由器（IP Router）用于位不同类型的物理网络提供连接，是在网络层的机件。&lt;BR&gt;&lt;BR&gt;TCP/IP协议族中，网络层IP提供不可靠的服务，只是尽可能快地把分组从源结点送到目的结点，但并不提供任何可靠性保证；但TCP在IP层上提供了一个可靠地运输层，采用超时重传、发送和接受端到端地确认分组等机制。&lt;img src ="http://www.mscenter.edu.cn/blog/eric_dyx/aggbug/774.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>Ericdyx</dc:creator><title>Web项目的包括数据库的打包方法</title><link>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/26/773.html</link><pubDate>Wed, 26 Jan 2005 11:13:00 GMT</pubDate><guid>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/26/773.html</guid><wfw:comment>http://www.mscenter.edu.cn/blog/eric_dyx/comments/773.html</wfw:comment><comments>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/26/773.html#Feedback</comments><slash:comments>8</slash:comments><wfw:commentRss>http://www.mscenter.edu.cn/blog/eric_dyx/comments/commentRss/773.html</wfw:commentRss><trackback:ping>http://www.mscenter.edu.cn/blog/eric_dyx/services/trackbacks/773.html</trackback:ping><description>&lt;P&gt;新建一项目：visual C#项目&amp;nbsp; , 选中类库模板 ,如命名为myInstall,在此项目下面添加一新项：选择安装程序类（Installer1.cs），在此我采用默认名。此安装类库将实现数据库的安装及其他一些文件的安装。&lt;/P&gt;
&lt;P&gt;在installer1.cs里我们将重写安装类的方法：见参考1&lt;/P&gt;
&lt;P&gt;Install&amp;nbsp;&amp;nbsp;&amp;nbsp; Commit&amp;nbsp;&amp;nbsp;&amp;nbsp; Rollback&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Uninstall&lt;/P&gt;
&lt;P&gt;在install方法中来实现自定义的安装：&lt;/P&gt;
&lt;P&gt;首先我们要得到一些用户变量（用户在安装时所做的操作，如数据库管理帐号等）&lt;/P&gt;
&lt;P&gt;对于这些变量我们可以通过上下文环境来得到this.Context.Parameters["DataBaseName"];（在安装项目里将会进行设置）&lt;/P&gt;
&lt;P&gt;在此我们一共得到五个变量。安装数据库的服务器，指定的数据库名，数据库管理员帐号，密码。以及数据库备份文件。&lt;/P&gt;
&lt;P&gt;我们来看一下具体的代码，很简单：）&lt;/P&gt;
&lt;P&gt;private void InstallDateBase()&lt;/P&gt;
&lt;P&gt;{&lt;/P&gt;
&lt;P&gt;//&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.Reflection.Assembly&amp;nbsp; Asm;&lt;/P&gt;
&lt;P&gt;//&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Asm= System.Reflection.Assembly.GetExecutingAssembly();&lt;/P&gt;
&lt;P&gt;//&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.IO.FileInfo FileInfo = new System.IO.FileInfo(Asm.Location);&lt;/P&gt;
&lt;P&gt;//&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 也可以得到当前安装的目录，如wwwroot目录下myweb\你的安装工程名.dll&lt;/P&gt;
&lt;P&gt;//下面是进行数据库连线及命令&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SqlConnection sqlConnection1 = new SqlConnection();&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string DBName = this.Context.Parameters["DataBaseName"];&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string DBServer = this.Context.Parameters["ServerName"];&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string DBUser = this.Context.Parameters["InStallAdmin"];&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string DBKey = this.Context.Parameters["InstallPassword"];&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string dir=this.Context.Parameters["dir"];&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; StreamWriter ss=new StreamWriter(@"c:\aaa.txt",true);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ss.WriteLine("data source=" + DBName + ";database=master;uid=" + DBUser +";password=" + DBKey);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ss.Close();&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string CONSTR = "data source=" + DBServer +";database=master;uid=" + DBUser +";password=" + DBKey;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; sqlConnection1.ConnectionString =&amp;nbsp; CONSTR;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //数据库将安装到system32下&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string CurDir = Directory.GetCurrentDirectory() + @"\TestData\database";&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if(!Directory.Exists(CurDir))//不存在就新建一个&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Directory.CreateDirectory(CurDir);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string MSQL = "RESTORE DATABASE " + DBName +&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; " FROM DISK = '" + dir + @"\database\myDataBase.bak' " +&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; " WITH MOVE 'myDate_dat(这里是备份的数据库文件中的mdf后缀的那个名字)' TO '" + CurDir + @"\" + DBName + ".mdf', " +&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; " MOVE 'myDate_log（这里是备份的数据库文件中的ldf后缀的那个名字）' TO '" + CurDir + @"\" + DBName + ".ldf' " ;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SqlCommand cmd = new SqlCommand(MSQL,sqlConnection1);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; cmd.Connection.Open();&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; cmd.ExecuteNonQuery();&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; cmd.Connection.Close();&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if(sqlConnection1.State == ConnectionState.Open)&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; sqlConnection1.Close();&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/P&gt;
&lt;P&gt;}&lt;/P&gt;
&lt;P&gt;public override void Install(System.Collections.IDictionary stateSaver)&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; try&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; base.Install(stateSaver);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; this.InstallDateBase();//调用上面的方法&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;catch&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/P&gt;
&lt;P&gt;}&lt;/P&gt;
&lt;P&gt;public override void Uninstall(System.Collections.IDictionary stateSaver)&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; base.Uninstall(stateSaver);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public override void Commit(System.Collections.IDictionary stateSaver)&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; base.Commit(stateSaver);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public override void Rollback(System.Collections.IDictionary stateSaver)&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; base.Rollback(stateSaver);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/P&gt;
&lt;P&gt;2)创建部署项目 &lt;/P&gt;
&lt;P&gt;在&amp;#8220;文件&amp;#8221;菜单上，选择&amp;#8220;添加项目&amp;#8221;-&amp;gt;&amp;#8220;新建项目&amp;#8221;。 &lt;BR&gt;在&amp;#8220;添加项目&amp;#8221;对话框中，选择&amp;#8220;项目类型&amp;#8221;窗格中的&amp;#8220;安装和部署项目&amp;#8221;，然后选择&amp;#8220;模板&amp;#8221;窗格中的&amp;#8220;WEB安装项目&amp;#8221;。在&amp;#8220;名称&amp;#8221;框中，键入 websetup2。(我是默认的，没改。这些语句懒得打，抄来的，：)) &lt;BR&gt;在&amp;#8220;属性&amp;#8221;窗口中，选择 ProductName 属性并键入 mywebTest。 &lt;BR&gt;在文件系统编辑器中，选择&amp;#8220;应用程序文件夹&amp;#8221;。在&amp;#8220;操作&amp;#8221;菜单上，选择&amp;#8220;添加&amp;#8221;-&amp;gt;&amp;#8220;项目输出&amp;#8221;。 &lt;BR&gt;在&amp;#8220;添加项目输出组&amp;#8221;对话框中，为&amp;#8220;myInstall&amp;#8221;项目选择主输出。 &lt;BR&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;在前面的操作与一般打包一样，将你的WEB工程里的一些文件添加进来。及一些其他打包设置（产品名等），我就不多说了。&lt;/P&gt;
&lt;P&gt;我们这里仅仅要加入一个数据库备份文件（myDataBase.bak）&lt;/P&gt;
&lt;P&gt;首先打开websetup2的文件系统窗口，选中web应用程序文件夹，在此文件夹下面添加一个文件夹我们用来存放数据库，如database,再将mydatabase.bak文件复制到此目录下面。&lt;/P&gt;
&lt;P&gt;现在我们要添加一个安装时的窗口，用来让用户输入数据库帐号等。我们只要打开用户界面窗口就可以进行操作了。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;创建自定义安装对话框 &lt;/P&gt;
&lt;P&gt;在解决方案资源管理器中选择&amp;#8220;websetup2&amp;#8221;项目。在&amp;#8220;视图&amp;#8221;菜单上指向&amp;#8220;编辑器&amp;#8221;，然后选择&amp;#8220;用户界面&amp;#8221;。 &lt;BR&gt;在用户界面编辑器中，选择&amp;#8220;安装&amp;#8221;下的&amp;#8220;启动&amp;#8221;节点。在&amp;#8220;操作&amp;#8221;菜单上，选择&amp;#8220;添加对话框&amp;#8221;。 &lt;BR&gt;在&amp;#8220;添加对话框&amp;#8221;对话框中选择&amp;#8220;文本框 (A)&amp;#8221;对话框。 &lt;BR&gt;在&amp;#8220;操作&amp;#8221;菜单上选择&amp;#8220;上移&amp;#8221;。重复此步骤，直到&amp;#8220;文本框 (A)&amp;#8221;对话框位于&amp;#8220;安装文件夹&amp;#8221;节点之上。 &lt;BR&gt;在&amp;#8220;属性&amp;#8221;窗口中，选择 BannerText 属性并键入 自定义数据库安装 &lt;BR&gt;选择 BodyText 属性并键入自定义数据库安装 &lt;BR&gt;选择 Edit1Label 属性并键入服务器名:。 &lt;BR&gt;选择 Edit1Property 属性并键入 SERVERNAME。 &lt;BR&gt;选择 Edit2Label 属性并键入创建数据库名:。 &lt;BR&gt;选择 Edit2Property 属性并键入 DATABASENAME。 &lt;BR&gt;选择 Edit3Label 属性并键入数据库管理员帐号:。 &lt;BR&gt;选择 Edit3Property 属性并键入 INSTALLADMIN。 &lt;BR&gt;选择 Edit4Label 属性并键入管理员密码:。INSTALLPASSWORD &lt;BR&gt;选择 Edit4Property 属性并键入 SERVERNAME。&lt;BR&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;创建自定义操作 &lt;/P&gt;
&lt;P&gt;在解决方案资源管理器中选择&amp;#8220;websetup2&amp;#8221;项目。在&amp;#8220;视图&amp;#8221;菜单上指向&amp;#8220;编辑器&amp;#8221;，然后选择&amp;#8220;自定义操作&amp;#8221;。 &lt;BR&gt;在自定义操作编辑器中选择&amp;#8220;安装&amp;#8221;节点。在&amp;#8220;操作&amp;#8221;菜单上，选择&amp;#8220;添加自定义操作&amp;#8221;。 &lt;BR&gt;在&amp;#8220;选择项目中的项&amp;#8221;对话框中，双击&amp;#8220;应用程序文件夹&amp;#8221;。&lt;BR&gt;&amp;nbsp;选择&amp;#8220;添加输出&amp;#8221;；&lt;BR&gt;选择&amp;#8220;主输出来自 DBCustomAction（活动）&amp;#8221;项。 &lt;BR&gt;在&amp;#8220;属性&amp;#8221;窗口中，选择 CustomActionData 属性并键入 /InstallPassword=[INSTALLPASSWORD] /InStallAdmin=[INSTALLADMIN] /ServerName=[SERVERNAME] /DataBaseName=[DATABASENAME] /dir="[TARGETDIR]\"&lt;BR&gt;注意各个 用户数据之间是有空格的。如/aaa=[fdfad] /bfff=[fdafdfd]&lt;/P&gt;
&lt;P&gt;在&amp;#8220;生成&amp;#8221;菜单上选择&amp;#8220;生成websetup2&amp;#8221;。 &lt;BR&gt;上面的customActionData将会在安装类中引用的。注意各个之间一定要有空格。&lt;/P&gt;
&lt;P&gt;/dir=[&amp;#8220;TARGETDIR&amp;#8221;],取的是最终安装的WEB目录&lt;/P&gt;
&lt;P&gt;到此一个项目打包就此结束了&lt;BR&gt;&lt;/P&gt;&lt;img src ="http://www.mscenter.edu.cn/blog/eric_dyx/aggbug/773.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>Ericdyx</dc:creator><title>ADO.net性能改进</title><link>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/25/769.html</link><pubDate>Tue, 25 Jan 2005 05:58:00 GMT</pubDate><guid>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/25/769.html</guid><wfw:comment>http://www.mscenter.edu.cn/blog/eric_dyx/comments/769.html</wfw:comment><comments>http://www.mscenter.edu.cn/blog/eric_dyx/archive/2005/01/25/769.html#Feedback</comments><slash:comments>186</slash:comments><wfw:commentRss>http://www.mscenter.edu.cn/blog/eric_dyx/comments/commentRss/769.html</wfw:commentRss><trackback:ping>http://www.mscenter.edu.cn/blog/eric_dyx/services/trackbacks/769.html</trackback:ping><description>&lt;H4 class=dtH1&gt;Improving .NET Application Performance and Scalability&lt;/H4&gt;
&lt;P&gt;J.D. Meier, Srinath Vasireddy, Ashish Babbar, and Alex Mackman&lt;BR&gt;Microsoft Corporation&lt;/P&gt;
&lt;P&gt;May 2004&lt;/P&gt;
&lt;P&gt;&lt;B&gt;Related Links&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/scalenet.asp"&gt;Home Page&lt;/A&gt; for Improving .NET Application Performance and Scalability&lt;/P&gt;
&lt;P&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/scalenetchapt05.asp"&gt;Chapter 5, Improving Managed Code Performance&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/scalenetchapt14.asp"&gt;Chapter 14, Improving SQL Server Performance&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/scalenetcheck01.asp"&gt;Checklist: ADO.NET Performance&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&lt;A href="mailto:Scale@microsoft.com"&gt;Send feedback&lt;/A&gt; to Scale@microsoft.com&lt;/P&gt;
&lt;P&gt;&lt;A href="http://www.microsoft.com/practices/"&gt;patterns &amp;amp; practices Library&lt;/A&gt; &lt;/P&gt;
&lt;P&gt;&lt;B&gt;Summary: &lt;/B&gt;This chapter provides proven strategies to help you design and develop scalable data access solutions. Topics covered include different techniques to pass data across application layers, managing the database connection pool, optimizing stored procedure calls, reducing dataset serialization cost, techniques for paging through large result sets, managing transactions, handling BLOBS, and much more.&lt;/P&gt;
&lt;H4 class=dtH1&gt;Contents&lt;/H4&gt;
&lt;P&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic1" target=_self&gt;Objectives&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic2" target=_self&gt;Overview&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic3" target=_self&gt;How to Use This Chapter&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic4" target=_self&gt;Architecture&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic5" target=_self&gt;Performance and Scalability Issues&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic6" target=_self&gt;Design Considerations&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic7" target=_self&gt;Implementation Considerations&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic8" target=_self&gt;.NET Framework Data Providers&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic9" target=_self&gt;Connections&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic10" target=_self&gt;Commands&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic11" target=_self&gt;Stored Procedures&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic12" target=_self&gt;Parameters&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic13" target=_self&gt;DataSet vs. DataReader&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic14" target=_self&gt;DataReader&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic15" target=_self&gt;DataSet&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic16" target=_self&gt;XML and DataSet Objects&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic17" target=_self&gt;Typed DataSets&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic18" target=_self&gt;Types&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic19" target=_self&gt;Exception Management&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic20" target=_self&gt;Transactions&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic21" target=_self&gt;Binary Large Objects&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic22" target=_self&gt;Paging Records&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic23" target=_self&gt;Analyzing Performance and Scalability of Data Access&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic24" target=_self&gt;Summary&lt;/A&gt;&lt;BR&gt;&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt12.asp?frame=true#scalenetchapt12_topic25" target=_self&gt;Additional Resources&lt;/A&gt;&lt;/P&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic1&gt;&lt;/A&gt;Objectives&lt;/H2&gt;
&lt;UL type=disc&gt;
&lt;LI&gt;Optimize your data access design. 
&lt;LI&gt;Choose between &lt;B&gt;DataSets&lt;/B&gt; and &lt;B&gt;DataReaders&lt;/B&gt;. 
&lt;LI&gt;Run efficient database commands. 
&lt;LI&gt;Pass data between layers efficiently. 
&lt;LI&gt;Perform efficient transactions. 
&lt;LI&gt;Optimize connection management. 
&lt;LI&gt;Evaluate the cost of paging through records. 
&lt;LI&gt;Evaluate criteria for analyzing data access performance. 
&lt;LI&gt;Apply performance considerations to binary large object (BLOB) manipulation. &lt;/LI&gt;&lt;/UL&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic2&gt;&lt;/A&gt;Overview&lt;/H2&gt;
&lt;P&gt;Well-designed data access code and data processing commands are essential elements for application performance and scalability. Typically, the database is a focal point for application load because the majority of application requests require data that comes from a database.&lt;/P&gt;
&lt;P&gt;This chapter provides proven strategies for designing and implementing data access code for performance and scalability.&lt;/P&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic3&gt;&lt;/A&gt;How to Use This Chapter&lt;/H2&gt;
&lt;P&gt;Use this chapter to improve the implementation of your data access code for performance and scalability. To get the most out of this chapter, consider the following: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Jump to topics or read beginning to end&lt;/B&gt;. The main headings in this chapter help you to quickly identify and then locate the topic that interests you. Alternatively, you can read the chapter beginning to end to gain a thorough appreciation of the issues that affect ADO.NET performance. 
&lt;LI&gt;&lt;B&gt;Use the checklist&lt;/B&gt;. Use "&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/scalenetcheck01.asp"&gt;Checklist: ADO.NET Performance&lt;/A&gt;" in the "Checklists" section of this guide to quickly view and evaluate the guidelines presented in this chapter. 
&lt;LI&gt;&lt;B&gt;Use the "Architecture" section of this chapter to understand how ADO.NET works&lt;/B&gt;.&lt;B&gt; &lt;/B&gt;By understanding the architecture, you can make better design and implementation choices. Understand core ADO.NET components, such as data provider objects and the &lt;B&gt;DataSet&lt;/B&gt; object. 
&lt;LI&gt;&lt;B&gt;Use the "Design Considerations" section of this chapter to understand the high-level decisions that will affect implementation choices for ADO.NET code&lt;/B&gt;. 
&lt;LI&gt;Read Chapter 13, "&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/scalenetchapt13.asp"&gt;Code Review: .NET Application Performance&lt;/A&gt;" See the "Data Access" section for specific guidance. 
&lt;LI&gt;&lt;B&gt;Measure your application performance&lt;/B&gt;. Read the "ADO.NET/Data Access" and ".NET Framework Technologies" sections of Chapter 15, "&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/scalenetchapt15.asp"&gt;Measuring .NET Application Performance&lt;/A&gt;" to learn about the key metrics that you can use to measure application performance. You have to measure application performance so that you can identify and resolve performance issues. 
&lt;LI&gt;&lt;B&gt;Test your application performance&lt;/B&gt;. Read Chapter 16, "&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/scalenetchapt16.asp"&gt;Testing .NET Application Performance&lt;/A&gt;" to learn how to apply performance testing to your application. You have to apply a coherent testing process and analyze the results. 
&lt;LI&gt;&lt;B&gt;Tune your application performance&lt;/B&gt;. Read the "ADO.NET Tuning" section of Chapter 17, "&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/scalenetchapt17.asp"&gt;Tuning .NET Application Performance&lt;/A&gt;" to learn how to resolve performance issues that you identify through the use of tuning metrics. 
&lt;LI&gt;&lt;B&gt;Tune SQL Server&lt;/B&gt;. Read Chapter 14, "&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/scalenetchapt14.asp"&gt;Improving SQL Server Performance&lt;/A&gt;" to ensure that your Microsoft&amp;#174; SQL Server&amp;#8482; database is appropriately configured. &lt;/LI&gt;&lt;/UL&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic4&gt;&lt;/A&gt;Architecture&lt;/H2&gt;
&lt;P&gt;ADO.NET relies on data providers to provide access to the underlying data source. Each data provider exposes a set of objects that you use to manage connections, retrieve data, and update data. The core objects are the following: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Connection&lt;/B&gt; 
&lt;LI&gt;&lt;B&gt;Command&lt;/B&gt; 
&lt;LI&gt;&lt;B&gt;DataReader&lt;/B&gt; 
&lt;LI&gt;&lt;B&gt;DataAdapter&lt;/B&gt; &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;In addition, ADO.NET provides the &lt;B&gt;DataSet&lt;/B&gt; object, which provides a disconnected cache of data. The &lt;B&gt;DataSet&lt;/B&gt; object does not require a specific type of data source and is not tied to the underlying data source that the data was obtained from.&lt;/P&gt;
&lt;P&gt;The basic ADO.NET architecture is shown in Figure 12.1.&lt;/P&gt;
&lt;P class=fig&gt;&lt;IMG alt="" src="http://msdn.microsoft.com/library/en-us/dnpag/html/ch12---adonet-architecture.gif" border=0&gt;&lt;/P&gt;
&lt;P class=label&gt;&lt;B&gt;Figure 12.1: ADO.NET architecture&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;The following list outlines the purpose of each of the main ADO.NET objects: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Connection&lt;/B&gt;. This object represents a connection to a database. 
&lt;LI&gt;&lt;B&gt;Command&lt;/B&gt;. This object represents an SQL statement that is run while connected to a data source. This object can be a stored procedure or a direct SQL statement. 
&lt;LI&gt;&lt;B&gt;DataReader&lt;/B&gt;. This object retrieves a read-only, forward-only stream of data from a database. The &lt;B&gt;DataReader &lt;/B&gt;object is designed for connected scenarios and offers better performance than reading data into a &lt;B&gt;DataSet&lt;/B&gt; object at the expense of functionality. For more information about how to use &lt;B&gt;DataReader&lt;/B&gt; objects and &lt;B&gt;DataSet&lt;/B&gt; objects, see "DataSet vs. DataReader" later in this chapter. 
&lt;LI&gt;&lt;B&gt;DataAdapter&lt;/B&gt;. This object channels data to and from a &lt;B&gt;DataSet&lt;/B&gt; object and the underlying data source. The &lt;B&gt;DataAdapter&lt;/B&gt; object also provides enhanced batch update features that were previously associated with the ADO &lt;B&gt;Recordset&lt;/B&gt; object. 
&lt;LI&gt;&lt;B&gt;DataSet&lt;/B&gt;. The &lt;B&gt;DataSet&lt;/B&gt; object represents a disconnected, cached set of data. The &lt;B&gt;DataSet&lt;/B&gt; is independent of the provider and is not tied to the underlying data source that might have been used to populate it. &lt;B&gt;DataSet&lt;/B&gt; can easily be passed from component to component through the various layers of an application, and it can be serialized as XML. 
&lt;P&gt;You should be aware of the way a &lt;B&gt;DataSet&lt;/B&gt; is internally constructed because the &lt;B&gt;DataSet&lt;/B&gt; contains a potentially large number of internal objects. This means that a large number of memory allocations are required to construct a typical &lt;B&gt;DataSet&lt;/B&gt;. 
&lt;P&gt;A &lt;B&gt;DataSet&lt;/B&gt; consists of one or more &lt;B&gt;DataTable&lt;/B&gt; objects together with &lt;B&gt;DataRelation&lt;/B&gt; objects that maintain table relationship information. Each &lt;B&gt;DataTable&lt;/B&gt; contains &lt;B&gt;DataRow&lt;/B&gt; objects and &lt;B&gt;DataColumn&lt;/B&gt; objects. &lt;B&gt;Constraint&lt;/B&gt; objects are used to represent a constraint that can be enforced on one or more &lt;B&gt;DataColumn&lt;/B&gt; objects. 
&lt;BLOCKQUOTE class=dtBlock&gt;&lt;B&gt;Note&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/B&gt;You can also use typed datasets that derive from the basic &lt;B&gt;DataSet&lt;/B&gt; class. Typed datasets provide benefits at build time and at run time. For more information, see "Typed DataSets" later in this chapter.&lt;/BLOCKQUOTE&gt;
&lt;LI&gt;&lt;B&gt;DataView&lt;/B&gt;. Although the &lt;B&gt;DataView&lt;/B&gt; object is not shown in Figure 12.1, you can use a &lt;B&gt;DataView&lt;/B&gt; to sort and filter data in a &lt;B&gt;DataTable&lt;/B&gt;. This capability is often used for data binding. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Abstracting Data Access&lt;/H3&gt;
&lt;P&gt;ADO.NET is designed around a set of generic interfaces that abstract the underlying data processing functionality. You can use these interfaces directly to abstract your data access layer so that you can minimize the impact of changing the type of data source that you use. Abstracting data access is extremely helpful when you are designing systems where your customer chooses the database server.&lt;/P&gt;
&lt;P&gt;The core interfaces provided by ADO.NET are found in the &lt;B&gt;System.Data&lt;/B&gt; namespace: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;IDbConnection&lt;/B&gt;. This is an interface for managing database connections. 
&lt;LI&gt;&lt;B&gt;IDbCommand&lt;/B&gt;. This is an interface for running SQL commands. 
&lt;LI&gt;&lt;B&gt;IDbTransaction&lt;/B&gt;. This is an interface for managing transactions. 
&lt;LI&gt;&lt;B&gt;IDataReader&lt;/B&gt;. This is an interface for reading data returned by a command. 
&lt;LI&gt;&lt;B&gt;IDataAdapter&lt;/B&gt;. This is an interface for channeling data to and from datasets. &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;The various provider objects, such as &lt;B&gt;SqlConnection&lt;/B&gt; and &lt;B&gt;OleDbConnection&lt;/B&gt;, implement these generic ADO.NET data access interfaces. If you decide to program against the generic interfaces, be aware of the following issues: 
&lt;UL type=disc&gt;
&lt;LI&gt;There is some small cost associated with a virtual call through an interface. 
&lt;LI&gt;Certain expanded functionality is lost when you use the generic interfaces. For example, the &lt;B&gt;ExecuteXmlReader&lt;/B&gt; method is implemented by the &lt;B&gt;SqlCommand&lt;/B&gt; object but not by the &lt;B&gt;IDbCommand&lt;/B&gt; interface. 
&lt;LI&gt;There is no generic base exception type, so you must catch provider-specific exception types, such as &lt;B&gt;SqlException, OleDbException, &lt;/B&gt;or &lt;B&gt;OdbcException&lt;/B&gt;. 
&lt;LI&gt;When you use the generic interfaces, you cannot take advantage of database-specific types that are defined for the managed providers; for example, you cannot take advantage of &lt;B&gt;SqlDbType&lt;/B&gt; in &lt;B&gt;SqlClient&lt;/B&gt; and Oracle-specific types in the Oracle provider. Using specific database types is helpful for type checking and parameter binding. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;More Information&lt;/H3&gt;
&lt;P&gt;For more information about how to use the generic interfaces to abstract your data access, see the following resources: 
&lt;UL type=disc&gt;
&lt;LI&gt;Knowledge Base article 313304, "HOW TO: Use Base Classes to Reduce Code Forking with Managed Providers in Visual C# .NET," at &lt;A href="http://support.microsoft.com/default.aspx?scid=kb;en-us;313304"&gt;http://support.microsoft.com/default.aspx?scid=kb;en-us;313304&lt;/A&gt;. This article includes sample code. 
&lt;LI&gt;Use MSDN&amp;#174; to look at the interfaces that are described earlier in this section to identify the providers that implement each of the interfaces. &lt;/LI&gt;&lt;/UL&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic5&gt;&lt;/A&gt;Performance and Scalability Issues&lt;/H2&gt;
&lt;P&gt;The following is a list of the main issues that can adversely affect the performance and scalability of data access in your application. 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Inefficient queries&lt;/B&gt;. Queries that process and then return more columns or rows than necessary waste processing cycles that could best be used for servicing other requests. Queries that do not take advantage of indexes may also cause poor performance. 
&lt;LI&gt;&lt;B&gt;Retrieving too much data&lt;/B&gt;. Too much data in your results is usually the result of inefficient queries. The SELECT * query often causes this problem. You do not usually need to return all the columns in a row. Also, analyze the WHERE clause in your queries to ensure that you are not returning too many rows. Try to make the WHERE clause as specific as possible to ensure that the least number of rows are returned. 
&lt;LI&gt;&lt;B&gt;Inefficient or missing indexes&lt;/B&gt;. Query efficiency decreases when indexes are missing because a full table scan must be performed. Also, as your data grows, tables may become fragmented. Failure to periodically rebuild indexes may also result in poor query performance. 
&lt;LI&gt;&lt;B&gt;Unnecessary round trips&lt;/B&gt;. Round trips significantly affect performance. They are subject to network latency and to downstream server latency. Many data-driven Web sites heavily access the database for every user request. While connection pooling helps, the increased network traffic and processing load on the database server can adversely affect performance. Keep round trips to an absolute minimum. 
&lt;LI&gt;&lt;B&gt;Too many open connections&lt;/B&gt;. Connections are an expensive and scarce resource, which should be shared between callers by using connection pooling. Opening a connection for each caller limits scalability. To ensure the efficient use of connection pooling, avoid keeping connections open and avoid varying connection strings. 
&lt;LI&gt;&lt;B&gt;Failure to release resources&lt;/B&gt;. Failing to release resources can prevent them from being reused efficiently. If you fail to close connections before the connections fall out of scope, they are not reclaimed until garbage collection occurs for the connection. Failing to release resources can cause serious resource pressure and lead to shortages and timeouts. 
&lt;LI&gt;&lt;B&gt;Transaction misuse&lt;/B&gt;. If you select the wrong type of transaction management, you may add latency to each operation. Additionally, if you keep transactions active for long periods of time, the active transactions may cause resource pressure. Transactions are necessary to ensure the integrity of your data, but you need to ensure that you use the appropriate type of transaction for the shortest duration possible and only where necessary. 
&lt;LI&gt;&lt;B&gt;Overnormalized tables&lt;/B&gt;. Overnormalized tables may require excessive joins for simple operations. These additional steps may significantly affect the performance and scalability of your application, especially as the number of users and requests increases. &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;Subsequent sections in this chapter provide strategies and technical information to prevent or resolve each of these issues.&lt;/P&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic6&gt;&lt;/A&gt;Design Considerations&lt;/H2&gt;
&lt;P&gt;To help ensure that data access in your application is optimized for performance, there are several issues that you must consider and a number of decisions that you must make at design time: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Design your data access layer based on how the data is used&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Cache data to avoid unnecessary work&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Connect by using service accounts&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Acquire late, release early&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Close disposable resources&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Reduce round trips&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Return only the data you need&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use Windows authentication&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Choose the appropriate transaction type&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use stored procedures&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Prioritize performance, maintainability, and productivity when you choose how to pass data across layers&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Consider how to handle exceptions&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use appropriate normalization&lt;/B&gt;. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Design Your Data Access Layer Based on How the Data Is Used&lt;/H3&gt;
&lt;P&gt;If you choose to access tables directly from your application without an intermediate data access layer, you may improve the performance of your application at the expense of maintainability. The data access logic layer provides a level of abstraction from the underlying data store. A well-designed data access layer exposes data and functionality based on how the data is used and abstracts the underlying data store complexity.&lt;/P&gt;
&lt;P&gt;Do not arbitrarily map objects to tables and columns, and avoid deep object hierarchies. For example, if you want to display a subset of data, and your design retrieves an entire object graph instead of the necessary portions, there is unnecessary object creation overhead. Evaluate the data you need and how you want to use the data against your underlying data store.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Cache Data to Avoid Unnecessary Work&lt;/H3&gt;
&lt;P&gt;Caching data can substantially reduce the load on your database server. By caching data, you avoid the overhead of connecting to your database, searching, processing, and transmitting data from your database server. By caching data, you directly improve performance and scalability in your application.&lt;/P&gt;
&lt;P&gt;When you define your caching strategy, consider the following: 
&lt;UL type=disc&gt;
&lt;LI&gt;Is the data used application-wide and shared by all users, or is the data specific to each user? Data that is used across the application, such as a list of products, is a better caching candidate than data that is specific to each user. 
&lt;LI&gt;How frequently do you need to update the cache? Even though the source data may change frequently, your application may not need to update the cache as often. If your data changes too frequently, it may not be a good caching candidate. You need to evaluate the expense of updating the cache compared to the cost of fetching the data as needed. 
&lt;LI&gt;Where should you cache data? You can cache data throughout the application layers. By caching data as close as possible to the consumer of the data, you can reduce the impact of network latency. 
&lt;LI&gt;What form of the data should you cache? The best form of data to cache is usually determined by the form that your clients require the data to be in. Try to reduce the number of times that you need to transform data. 
&lt;LI&gt;How do you expire items in the cache? Consider the mechanism that you will use to expire old items in the cache and the best expiration time for your application. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Connect by Using Service Accounts&lt;/H3&gt;
&lt;P&gt;There are several ways to authenticate and to open a connection to the database. You can use SQL authentication that has an identity specified in the connection string. Or, you can use Windows authentication by using the process identity, by using a specific service identity, or by impersonating the original caller's identity.&lt;/P&gt;
&lt;P&gt;From a security perspective, you should use Windows authentication. From a performance perspective, you should use a fixed service account and avoid impersonation. The fixed service account is typically the process account of the application. By using a fixed service account and a consistent connection string, you help ensure that database connections are pooled efficiently. You also help ensure that the database connections are shared by multiple clients. Using a fixed service account and a consistent connection string is a major factor in helping application scalability.&lt;/P&gt;
&lt;P&gt;&lt;B&gt;More Information&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;For more information, see "The Trusted Subsystem Model" in the "Authentication and Authorization" chapter of "Building Secure ASP.NET Applications: Authentication, Authorization and Secure Communication" on MSDN at &lt;A href="http://msdn.microsoft.com/library/en-us/dnnetsec/html/SecNetch03.asp"&gt;http://msdn.microsoft.com/library/en-us/dnnetsec/html/SecNetch03.asp&lt;/A&gt;. This chapter explains how to use service accounts or process identity to connect to a database. You can also use the chapter to learn about the advantages and disadvantages of Windows and SQL authentication.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Acquire Late, Release Early&lt;/H3&gt;
&lt;P&gt;Your application should share expensive resources efficiently by acquiring the resources late, and then releasing them as early as possible. To do so: 
&lt;UL type=disc&gt;
&lt;LI&gt;Open database connections right when you need them. Close the database connections as soon as you are finished. Do not open them early, and do not hold them open across calls. 
&lt;LI&gt;Acquire locks late, and release them early. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Close Disposable Resources&lt;/H3&gt;
&lt;P&gt;Usually, disposable resources are represented by objects that provide a &lt;B&gt;Dispose&lt;/B&gt; method or a &lt;B&gt;Close&lt;/B&gt; method. Make sure that you call one of these methods as soon as you are finished with the resource. For more information about closing &lt;B&gt;Connection&lt;/B&gt; objects and &lt;B&gt;DataReader&lt;/B&gt; objects, see "Connections" later in this chapter.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Reduce Round Trips&lt;/H3&gt;
&lt;P&gt;Network round trips are expensive and affect performance. Minimize round trips by using the following techniques: 
&lt;UL type=disc&gt;
&lt;LI&gt;If possible, batch SQL statements together. Failure to batch work creates additional and often unnecessary trips to the database. You can batch text SQL statements by separating them with a semicolon or by using a stored procedure. If you need to read multiple result sets, use the &lt;B&gt;NextResult&lt;/B&gt; method of the &lt;B&gt;DataReader&lt;/B&gt; object to access subsequent result sets. 
&lt;LI&gt;Use connection pooling to help avoid extra round trips. By reusing connections from a connection pool, you avoid the round trips that are associated with connection establishment and authentication. For more information, see "Connections" later in this chapter. 
&lt;LI&gt;Do not return results if you do not need them. If you only need to retrieve a single value, use the &lt;B&gt;ExecuteScalar&lt;/B&gt; method to avoid the operations that are required to create a result set. You can also use the &lt;B&gt;ExecuteNonQuery&lt;/B&gt; method when you perform data definition language (DDL) operations such as the create table operation. This also avoids the expense of creating a result set. 
&lt;LI&gt;Use caching to bring nearly static data close to the consumer instead of performing round trips for each request. &lt;/LI&gt;&lt;/UL&gt;
&lt;H4 class=dtH1&gt;Implicit Round Trips&lt;/H4&gt;
&lt;P&gt;Be aware that certain operations can cause implicit round trips. Typically, any operation that extracts metadata from the database causes an implicit round trip. For example, avoid calling &lt;B&gt;DeriveParameters&lt;/B&gt; if you know the parameter information in advance. It is more efficient to fill the parameters collection by setting the information explicitly. The following code sample illustrates a call that causes an implicit round trip.&lt;/P&gt;&lt;PRE class=code&gt;// This causes an implicit round trip to the database
SqlCommandBuilder.DeriveParameters(cmd);
&lt;/PRE&gt;
&lt;H3 class=dtH1&gt;Return Only the Data You Need&lt;/H3&gt;
&lt;P&gt;Evaluate the data that your application actually requires. Minimize the data that is sent over the network to minimize bandwidth consumption. The following approaches help reduce data over the network: 
&lt;UL type=disc&gt;
&lt;LI&gt;Return only the columns and rows that you need. 
&lt;LI&gt;Cache data where possible. 
&lt;LI&gt;Provide data paging for large results. For more information about paging, see "Paging Records" later in this chapter. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Use Windows Authentication&lt;/H3&gt;
&lt;P&gt;From a security perspective, you should use Windows authentication to connect to Microsoft SQL Server. There are several advantages to using Windows authentication. For example, credentials are not passed over the network, database connection strings do not contain credentials, and you can apply standard Windows security policies to accounts. For example, you can enforce use of strong passwords and apply password expiration periods.&lt;/P&gt;
&lt;P&gt;From a performance perspective, SQL authentication is slightly faster than Windows authentication, but connection pooling helps minimize the difference. You also need to help protect the credentials in the connection string and in transit between your application and the database. Helping to protect the credentials adds to the overhead and minimizes the performance difference.&lt;/P&gt;
&lt;BLOCKQUOTE class=dtBlock&gt;&lt;B&gt;Note&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/B&gt;Generally, local accounts are faster than domain accounts when you use Windows authentication. However, the performance saving needs to be balanced with the administration benefits of using domain accounts.&lt;/BLOCKQUOTE&gt;
&lt;H3 class=dtH1&gt;Choose the Appropriate Transaction Type&lt;/H3&gt;
&lt;P&gt;Proper transaction management minimizes locking and contention, and provides data integrity. The three transaction types and their usage considerations include the following: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Native database support&lt;/B&gt;. Native database support for transactions permits you to control the transaction from a stored procedure. In SQL Server, use BEGIN TRAN, COMMIT TRAN, and ROLLBACK to control the transaction's outcome. This type of transaction is limited to a single call from your code to the database, although the SQL query or stored procedure can make use of multiple stored procedures. 
&lt;LI&gt;&lt;B&gt;ADO.NET transactions&lt;/B&gt;. Manual transactions in ADO.NET enable you to span a transaction across multiple calls to a single data store. Both the SQL Server .NET Data Provider and the OLE DB .NET Data Provider implement the &lt;B&gt;IDbTransaction&lt;/B&gt; interface and expose &lt;B&gt;BeginTransaction&lt;/B&gt; on their respective connection object. This permits you to begin a transaction and to run multiple SQL calls using that connection instance and control the transaction outcome from your data access code. 
&lt;LI&gt;&lt;B&gt;Enterprise Services&lt;/B&gt; &lt;B&gt;distributed transactions&lt;/B&gt;. Use declarative, distributed transactions when you need transactions to span multiple data stores or resource managers or where you need to flow transaction context between components. Also consider Enterprise Services transaction support for compensating transactions that permit you to enlist nontransactional resources in a transaction. For example, you can use the Compensating Resource Manager to combine a file system update and a database update into a single atomic transaction. 
&lt;P&gt;Enterprise Services distributed transactions use the services of the Microsoft Distributed Transaction Coordinator (DTC). The DTC introduces additional performance overhead. The DTC requires several round trips to the server and performs complex interactions to complete a transaction. &lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Use Stored Procedures&lt;/H3&gt;
&lt;P&gt;Avoid embedded SQL statements. Generally, well-designed stored procedures outperform embedded SQL statements. However, performance is not the only consideration. When you choose whether to store your SQL commands on the server by using stored procedures or to embed commands in your application by using embedded SQL statements, consider the following issues: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Logic separation&lt;/B&gt;. When you design your data access strategy, separate business logic from data manipulation logic for performance, maintainability, and flexibility benefits. Validate business rules before you send the data to the database to help reduce network round trips. Separate your business logic from data manipulation logic to isolate the impact of database changes or business rule changes. Use stored procedures to clarify the separation by moving the data manipulation logic away from the business logic so that the two do not become intertwined. 
&lt;LI&gt;&lt;B&gt;SQL optimizations&lt;/B&gt;. Some databases provide optimizations to stored procedures that do not apply to dynamic SQL. For example, Microsoft SQL Server&amp;#8482; versions prior to SQL Server 2000 kept a cached execution plan for stored procedures. The cached execution plan for stored procedures reduced the need to compile each stored procedure request. SQL Server 2000 is optimized to cache query plans for both stored procedure and for dynamic SQL query plans. 
&lt;LI&gt;&lt;B&gt;Tuning/deployment&lt;/B&gt;. Stored procedure code is stored in the database and permits database administrators to review data access code. Database administrators can tune both the stored procedures and the database, independent of the deployed application. The application does not always need to be redeployed when stored procedures change. 
&lt;P&gt;Embedded SQL is deployed as part of the application code and requires database administrators to profile the application to identify the SQL actually used. Profiling the application complicates tuning, because the application must be redeployed if any changes are made. &lt;/P&gt;
&lt;LI&gt;&lt;B&gt;Network traffic sent to the server&lt;/B&gt;. Source code for stored procedures is stored on the server. Only the name and parameters are sent across the network to the server. Conversely, when you use embedded SQL, the full source of the commands must be transmitted each time the commands are run. When you use stored procedures, you can reduce the amount of data that is sent to the server when large SQL operations are frequently run. 
&lt;LI&gt;&lt;B&gt;Simplified batching of commands&lt;/B&gt;. Stored procedures make it easy to batch work and provide simpler maintenance. 
&lt;LI&gt;&lt;B&gt;Data security and integrity&lt;/B&gt;. With stored procedures, administrators can secure tables against direct access or manipulation, and they can only permit the execution of selected stored procedures. Both users and applications are granted access to the stored procedures that enforce data integrity rules. Embedded SQL usually requires advanced permissions on tables. Using advanced permissions on tables is a more complex security model to maintain. 
&lt;LI&gt;&lt;B&gt;SQL Injection&lt;/B&gt;. Avoid using dynamically generated SQL with user input. SQL injection occurs when input from a malicious user is used to perform unauthorized actions, such as retrieving too much data or destructively modifying data. Parameterized stored procedures and parameterized SQL statements can help reduce the likelihood of SQL injection. Parameter collections force parameters to be treated as literal values so that the parameters are not treated as executable code. You should also constrain all user input to reduce the likelihood that a malicious user could use SQL injection to perform unauthorized actions. &lt;/LI&gt;&lt;/UL&gt;
&lt;H4 class=dtH1&gt;More Information&lt;/H4&gt;
&lt;P&gt;For more information about how to prevent SQL injection, see Chapter 14, "Building Secure Data Access," in &lt;I&gt;Improving Web Application Security: Threats and Countermeasures&lt;/I&gt; on MSDN at &lt;A href="http://msdn.microsoft.com/library/en-us/dnnetsec/html/THCMCh14.asp"&gt;http://msdn.microsoft.com/library/en-us/dnnetsec/html/THCMCh14.asp&lt;/A&gt;.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Prioritize Performance, Maintainability, and Productivity when You Choose How to Pass Data Across Layers&lt;/H3&gt;
&lt;P&gt;You should consider several factors when you choose an approach for passing data across layers: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Maintainability&lt;/B&gt;. Consider how hard it is to build and keep up with changes. 
&lt;LI&gt;&lt;B&gt;Productivity&lt;/B&gt;. Consider how hard it is to implement the solution. 
&lt;LI&gt;&lt;B&gt;Programmability&lt;/B&gt;. Consider how hard it is to code. 
&lt;LI&gt;&lt;B&gt;Performance&lt;/B&gt;. Consider how efficient it is for collections, browsing, and serializing. &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;This section summarizes the main approaches for passing data across application layers and the relative tradeoffs that exist: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;DataSets&lt;/B&gt;.&lt;B&gt; &lt;/B&gt;With this approach, you use a generic &lt;B&gt;DataSet&lt;/B&gt; object. This approach offers great flexibility because of the extensive functionality of the &lt;B&gt;DataSet&lt;/B&gt;. This includes serialization, XML support, ability to handle complex relationships, support for optimistic concurrency, and others. However, &lt;B&gt;DataSet&lt;/B&gt; objects are expensive to create because of their internal object hierarchy, and clients must access them through collections. 
&lt;P&gt;The &lt;B&gt;DataSet&lt;/B&gt; contains collections of many subobjects, such as the &lt;B&gt;DataTable&lt;/B&gt;, &lt;B&gt;DataRow&lt;/B&gt;, &lt;B&gt;DataColumn&lt;/B&gt;, &lt;B&gt;DataRelation&lt;/B&gt; and &lt;B&gt;Constraint&lt;/B&gt; objects. Most of these objects are passed with the &lt;B&gt;DataSet&lt;/B&gt; between the layers. This is a lot of objects and a lot of data to be passed between the layers. It also takes time to fill a &lt;B&gt;DataSet&lt;/B&gt;, because there are many objects that need to be instantiated and populated. All of this affects performance. Generally, the &lt;B&gt;DataSet&lt;/B&gt; is most useful for caching when you want to create an in-memory representation of your database, when you want to work with relations between tables, and when you want to perform sorting and filtering operations. &lt;/P&gt;
&lt;LI&gt;&lt;B&gt;Typed&lt;/B&gt; &lt;B&gt;DataSets&lt;/B&gt;. Instantiation and marshaling performance of the typed &lt;B&gt;DataSet&lt;/B&gt; is roughly equivalent to the &lt;B&gt;DataSet&lt;/B&gt;. The main performance advantage of the typed &lt;B&gt;DataSet&lt;/B&gt; is that clients can access methods and properties directly, without having to use collections. 
&lt;LI&gt;&lt;B&gt;DataReaders&lt;/B&gt;. This approach offers the optimum performance when you need to render data as quickly as possible. You should close &lt;B&gt;DataReader&lt;/B&gt; objects as soon as possible and make sure that client applications cannot affect the amount of time the &lt;B&gt;DataReader&lt;/B&gt; and, hence, the database connection is held open. 
&lt;P&gt;The &lt;B&gt;DataReader&lt;/B&gt; is very fast compared to a &lt;B&gt;DataSet,&lt;/B&gt; but you should avoid passing &lt;B&gt;DataReader&lt;/B&gt; objects between layers, because they require an open connection. &lt;/P&gt;
&lt;LI&gt;&lt;B&gt;XML&lt;/B&gt;. This is a loosely coupled approach that natively supports serialization and collections of data. For example, an XML document can contain data for multiple business entities. It also supports a wide range of client types. Performance issues to consider include the fact that XML strings can require substantial parsing effort, and large and verbose strings can consume large amounts of memory. 
&lt;LI&gt;&lt;B&gt;Custom Classes&lt;/B&gt;. With this approach, you use private data members to maintain the object's state and provide public accessor methods. For simple types, you can use structures instead of classes, which means you avoid having to implement your own serialization. The main performance benefit of custom classes is that they enable you to create your own optimized serialization. You should avoid complex object hierarchies and optimize your class design to minimize memory consumption and reduce the amount of data that needs to be serialized when the object is passed between layers. &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;&lt;B&gt;More Information&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;For more information about how to pass data across the layers, see "&lt;I&gt;Designing Data Tier Components and Passing Data Through Tiers&lt;/I&gt;" at &lt;A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/BOAGag.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/BOAGag.asp&lt;/A&gt;.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Consider How to Handle Exceptions&lt;/H3&gt;
&lt;P&gt;In data access code in particular, you can use &lt;B&gt;try&lt;/B&gt;/&lt;B&gt;finally&lt;/B&gt; blocks to ensure that connections and other resources are closed, regardless of whether exceptions are generated. However, be aware of the following considerations: 
&lt;UL type=disc&gt;
&lt;LI&gt;Exceptions are expensive. Do not catch exceptions and then throw them again if your data access logic cannot add any value. A less costly approach is to permit the exception to propagate from the database to the caller. Similarly, do not wrap transaction attempts with &lt;B&gt;try&lt;/B&gt;/&lt;B&gt;catch&lt;/B&gt; blocks unless you plan to implement retry mechanisms. 
&lt;LI&gt;If you want to completely abstract your caller from the data-specific details, you have to catch the exception, you have to log detailed information to a log store, and then you have to return an enumerated value from a list of application-specific error codes. The log store could be a file or the Windows event log. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Use Appropriate Normalization&lt;/H3&gt;
&lt;P&gt;Overnormalization of a database schema can affect performance and scalability. For example, if you program against a fully normalized database, you are often forced to use cross-table joins, subqueries, and data views as data sources. Obtaining the right degree of normalization involves tradeoffs.&lt;/P&gt;
&lt;P&gt;On one hand, you want a normalized database to minimize data duplication, to ensure that data is logically organized, and to help maintain data integrity. On the other hand, it may be harder to program against fully normalized databases, and performance can suffer. Consider the following techniques: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Start with a normalized model&lt;/B&gt;.&lt;B&gt; &lt;/B&gt;Start with a normalized model and then de-normalize later. 
&lt;LI&gt;&lt;B&gt;Reduce the cost of joins by repeating certain columns&lt;/B&gt;.&lt;B&gt; &lt;/B&gt;Deep joins may result in the creation of temporary tables and table scans. To reduce the cost of joining across multiple tables, consider repeating certain columns. 
&lt;LI&gt;&lt;B&gt;Store precomputed results&lt;/B&gt;.&lt;B&gt; &lt;/B&gt;Consider storing precomputed results, such as subtotals, instead of computing them dynamically for each request. &lt;/LI&gt;&lt;/UL&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic7&gt;&lt;/A&gt;Implementation Considerations&lt;/H2&gt;
&lt;P&gt;When you move from application design to application development, consider the implementation details of your ADO.NET code. You can improve resource management by acquiring connections late, by releasing them early, and by using connection pooling.&lt;/P&gt;
&lt;P&gt;When you run code on the server, prefer stored procedures. Stored procedures are optimized by the database and use provider-specific types when they pass parameters, to reduce processing. Choose the best transaction management mechanism, and then choose an appropriate isolation level. Keep transactions as short as possible, and avoid code that can lead to deadlocks. You can improve responsiveness by employing the correct paging strategy. Employing the correct paging strategy can also reduce server load.&lt;/P&gt;
&lt;P&gt;By following best practice implementation guidelines, you can increase the performance of your ADO.NET code. The following sections summarize performance considerations for ADO.NET features and scenarios.&lt;/P&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic8&gt;&lt;/A&gt;.NET Framework Data Providers&lt;/H2&gt;
&lt;P&gt;Microsoft .NET Framework Data Providers are divided into two categories: bridge providers and native providers. Bridge providers permit you to use data access libraries, such as OLE DB and Open Database Connectivity (ODBC). The bridge provider wraps the underlying data access library. Native providers, such as those for SQL Server and Oracle, typically offer performance improvements due, in part, to the fact that there is less abstraction. It is important to choose the correct data provider for your specific data source as described below: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Use System.Data.SqlClient for SQL Server 7.0 and later&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use System.Data.OleDb for SQL Server 6.5 or OLE DB providers&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use System.Data.ODBC for ODBC data sources&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use System.Data.OracleClient for Oracle&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use SQLXML managed classes for XML data and for SQL Server 2000&lt;/B&gt;. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Use System.Data.SqlClient for SQL Server 7.0 and Later&lt;/H3&gt;
&lt;P&gt;For SQL Server 7.0 or later, use the .NET Framework Data Provider for SQL Server, &lt;B&gt;System.Data.SqlClient&lt;/B&gt;. It is optimized for accessing SQL Server and communicates directly by using the Tabular Data Stream (TDS) protocol. TDS is the native data transfer protocol of SQL Server.&lt;/P&gt;
&lt;P&gt;The commonly used classes in &lt;B&gt;System.Data.SqlClient&lt;/B&gt; are &lt;B&gt;SqlConnection&lt;/B&gt;, &lt;B&gt;SqlCommand&lt;/B&gt;, &lt;B&gt;SqlDataAdapter&lt;/B&gt;, and &lt;B&gt;SqlDataReader&lt;/B&gt;.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Use System.Data.OleDb for SQL Server 6.5 or OLE DB Providers&lt;/H3&gt;
&lt;P&gt;For SQL Server 6.5 or OLE DB data sources, use the .NET Framework Data Provider for OLE DB, &lt;B&gt;System.Data.OleDb&lt;/B&gt;. For example, in SQL Server 6.5 or earlier, you would use the OLE DB Provider for SQL Server (SQLOLEDB) with the .NET Framework Data Provider for OLE DB. Using the OLE DB provider is less efficient than using the .NET Framework Data Provider for SQL Server, because it calls through the OLE DB layer by using COM interop when communicating with the database.&lt;/P&gt;
&lt;P&gt;The commonly used classes in &lt;B&gt;System.Data.OleDb&lt;/B&gt; are &lt;B&gt;OleDbConnection&lt;/B&gt;, &lt;B&gt;OleDbCommand&lt;/B&gt;, &lt;B&gt;OleDbDataAdapter&lt;/B&gt;, and &lt;B&gt;OleDbDataReader&lt;/B&gt;.&lt;/P&gt;
&lt;BLOCKQUOTE class=dtBlock&gt;&lt;B&gt;Note&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/B&gt;The .NET Framework Data Provider for OLE DB does not support the Microsoft OLE DB Provider for ODBC (MSDASQL). For ODBC data sources, use the .NET Framework Data Provider for ODBC instead.&lt;/BLOCKQUOTE&gt;
&lt;H3 class=dtH1&gt;Use System.Data.ODBC for ODBC Data Sources&lt;/H3&gt;
&lt;P&gt;For ODBC data sources, use the .NET Framework Data Provider for ODBC, &lt;B&gt;System.Data.ODBC&lt;/B&gt;. This provider uses the native ODBC Driver Manager through COM interop.&lt;/P&gt;
&lt;P&gt;If you are using .NET Framework 1.0, you must download the .NET Framework Data Provider for ODBC from the Microsoft .NET Framework Developer Center on MSDN at &lt;A href="http://msdn.microsoft.com/netframework/downloads/updates/default.aspx"&gt;http://msdn.microsoft.com/netframework/downloads/updates/default.aspx&lt;/A&gt;. Note that the namespace is &lt;B&gt;Microsoft.Data.Odbc&lt;/B&gt;. If you are using .NET Framework 1.1, it is included in the &lt;B&gt;System.Data.Odbc&lt;/B&gt; namespace.&lt;/P&gt;
&lt;P&gt;The commonly used classes in &lt;B&gt;System.Data.Odbc&lt;/B&gt; are &lt;B&gt;OdbcConnection&lt;/B&gt;, &lt;B&gt;OdbcCommand&lt;/B&gt;, &lt;B&gt;OdbcDataAdapter&lt;/B&gt;, and &lt;B&gt;OdbcDataReader&lt;/B&gt;.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Use System.Data.OracleClient for Oracle&lt;/H3&gt;
&lt;P&gt;For Oracle data sources, use the .NET Framework Data Provider for Oracle, &lt;B&gt;System.Data.OracleClient&lt;/B&gt;. This provider enables data access to Oracle data sources through Oracle client connectivity software. The data provider supports Oracle client software version 8.1.7 and later.&lt;/P&gt;
&lt;P&gt;If you are using .NET Framework 1.0, you must download the .NET Framework Data provider for Oracle from the Microsoft .NET Framework Developer Center on MSDN at &lt;A href="http://msdn.microsoft.com/netframework/downloads/updates/default.aspx"&gt;http://msdn.microsoft.com/netframework/downloads/updates/default.aspx&lt;/A&gt;. If you are using .NET Framework 1.1 or later, the .NET Framework Data provider for Oracle is included in the &lt;B&gt;System.Data.OracleClient&lt;/B&gt; namespace in System.Data.OracleClient.dll.&lt;/P&gt;
&lt;P&gt;The commonly used classes in &lt;B&gt;System.Data.OracleClient&lt;/B&gt; are &lt;B&gt;OracleConnection&lt;/B&gt;, &lt;B&gt;OracleCommand&lt;/B&gt;, &lt;B&gt;OracleDataAdapter&lt;/B&gt;, and &lt;B&gt;OracleDataReader&lt;/B&gt;.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Use SQLXML Managed Classes for XML Data and SQL Server 2000&lt;/H3&gt;
&lt;P&gt;To manipulate data in a SQL Server database as XML, use SQLXML Managed Classes. You can download SQLXML 3.0 from the Data Access and Storage Developer Center on MSDN at &lt;A href="http://msdn.microsoft.com/data/downloads/default.aspx"&gt;http://msdn.microsoft.com/data/downloads/default.aspx&lt;/A&gt;.&lt;/P&gt;
&lt;H3 class=dtH1&gt;More Information&lt;/H3&gt;
&lt;P&gt;For more information about .NET Framework Data Providers, see the following: 
&lt;UL type=disc&gt;
&lt;LI&gt;Knowledge Base article 313480, "INFO: Roadmap for .NET Data Providers," at &lt;A href="http://support.microsoft.com/default.aspx?scid=kb;en-us;313480"&gt;http://support.microsoft.com/default.aspx?scid=kb;en-us;313480&lt;/A&gt; 
&lt;LI&gt;MSDN article, "Using .NET Framework Data Provider for Oracle to Improve .NET Application Performance," at &lt;A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/manprooracperf.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/manprooracperf.asp&lt;/A&gt; 
&lt;LI&gt;"Implementing a .NET Framework Data Provider" in &lt;I&gt;.NET Framework Developer's Guide&lt;/I&gt; at &lt;A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconimplementingnetdataprovider.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconimplementingnetdataprovider.asp&lt;/A&gt; &lt;/LI&gt;&lt;/UL&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic9&gt;&lt;/A&gt;Connections&lt;/H2&gt;
&lt;P&gt;Database connections are an expensive and limited resource. Your approach to connection management can significantly affect the overall performance and scalability of your application. Issues to consider include acquiring and releasing connections, pooling, and authentication. To improve database connection performance and scalability, apply the following strategies to your connection management policy: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Open and close the connection in the method&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Explicitly close connections&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;When using DataReaders, specify CommandBehavior.CloseConnection&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Do not explicitly open a connection if you use Fill or Update for a single operation&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Avoid checking the State property of OleDbConnection&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Pool connections&lt;/B&gt;. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Open and Close the Connection in the Method&lt;/H3&gt;
&lt;P&gt;Acquire connections late and release them early. Opening connections before they are needed reduces the number of connections that are available and increases resource pressure. Close connections quickly to ensure that they can be reused as soon as possible. Do not hold on to connections. Holding on to connections reduces the connections that are available to other code and increases resource pressure. The general pattern is to open and close connections on a per-method basis.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Explicitly Close Connections&lt;/H3&gt;
&lt;P&gt;Explicitly call the &lt;B&gt;Close&lt;/B&gt; or &lt;B&gt;Dispose &lt;/B&gt;methods on &lt;B&gt;SqlConnection&lt;/B&gt; objects as soon as you finish using them to release the resources that they use. Do not wait for the connection to fall out of scope. The connection is not returned to the pool until garbage collection occurs. This delays the reuse of the connection and negatively affects performance and scalability. The following are guidelines to consider. These guidelines are specific to &lt;B&gt;SqlConnection&lt;/B&gt; because of the way it is implemented. These guidelines are not universal for all classes that have &lt;B&gt;Close&lt;/B&gt; and &lt;B&gt;Dispose&lt;/B&gt; functionality. 
&lt;UL type=disc&gt;
&lt;LI&gt;Using either the &lt;B&gt;Close&lt;/B&gt; method or the &lt;B&gt;Dispose&lt;/B&gt; method is sufficient. You do not have to call one method after the other. There is no benefit to calling one method after the other. 
&lt;LI&gt;&lt;B&gt;Dispose&lt;/B&gt; internally calls &lt;B&gt;Close&lt;/B&gt;. In addition, &lt;B&gt;Dispose&lt;/B&gt; clears the connection string. 
&lt;LI&gt;If you do not call &lt;B&gt;Dispose&lt;/B&gt; or &lt;B&gt;Close&lt;/B&gt;, and if you do not use the &lt;B&gt;using&lt;/B&gt; statement, you are reliant upon the finalization of the inner object to free the physical connection. 
&lt;LI&gt;Use the &lt;B&gt;using&lt;/B&gt; statement, instead of &lt;B&gt;Dispose &lt;/B&gt;or&lt;B&gt; Close&lt;/B&gt;, when you are working with a single type, and you are coding in Visual C#&amp;#174;. &lt;B&gt;Dispose&lt;/B&gt; is automatically called for you when you use the &lt;B&gt;using&lt;/B&gt; statement, even when an exception occurs. 
&lt;LI&gt;If you do not use the &lt;B&gt;using&lt;/B&gt; statement, close connections inside a &lt;B&gt;finally&lt;/B&gt; block. Code in the &lt;B&gt;finally&lt;/B&gt; block always runs, regardless of whether an exception occurs. 
&lt;LI&gt;You do not have to set the &lt;B&gt;SqlConnection&lt;/B&gt; reference to &lt;B&gt;null&lt;/B&gt; or &lt;B&gt;Nothing&lt;/B&gt; because there is no complex object graph. Setting object references to &lt;B&gt;null &lt;/B&gt;or to&lt;B&gt; Nothing&lt;/B&gt; is usually done to make a graph of objects unreachable. &lt;/LI&gt;&lt;/UL&gt;
&lt;BLOCKQUOTE class=dtBlock&gt;&lt;B&gt;Note&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/B&gt;Closing a connection automatically closes any active &lt;B&gt;DataReader&lt;/B&gt; objects that are associated with the connection.&lt;/BLOCKQUOTE&gt;
&lt;H4 class=dtH1&gt;Closing Connections in Visual Basic .NET&lt;/H4&gt;
&lt;P&gt;The following Visual Basic&amp;#174; .NET code snippet shows how to explicitly close a connection as soon as the connection is no longer needed.&lt;/P&gt;&lt;PRE class=code&gt;Try
  conn.Open()
  cmd.ExecuteNonQuery()
  customerCount = paramCustCount.Value
Catch ex As Exception
  ' &amp;#8230; handle exception
Finally
  ' This is guaranteed to run regardless of whether an exception occurs
  ' in the Try block.
  If Not(conn is Nothing) Then
    conn.Close()
  End If
End Try
&lt;/PRE&gt;
&lt;H4 class=dtH1&gt;Closing Connections in C#&lt;/H4&gt;
&lt;P&gt;The following example shows how to close connections in C#.&lt;/P&gt;&lt;PRE class=code&gt;public void DoSomeWork()
{
  SqlConnection conn = new SqlConnection(connectionString);
  &amp;#8230;  try
  {
    conn.Open();
    // Do Work
  }
  catch (Exception e)
  {
    // Handle and log error
  }
  finally
  {
    if(null!=conn)
       conn.Close();
  }
}
&lt;/PRE&gt;
&lt;H4 class=dtH1&gt;Closing Connections with the Using Statement in C#&lt;/H4&gt;
&lt;P&gt;The &lt;B&gt;using&lt;/B&gt; statement simplifies code for C# developers by automatically generating a &lt;B&gt;try&lt;/B&gt; and &lt;B&gt;finally&lt;/B&gt; block when the code is compiled. This ensures that the &lt;B&gt;Dispose&lt;/B&gt; method is called even if an exception occurs. The following code fragment shows how to use the &lt;B&gt;using&lt;/B&gt; statement.&lt;/P&gt;&lt;PRE class=code&gt;using (SqlConnection conn = new SqlConnection(connString))
{
  conn.Open();
  . . .
} // Dispose is automatically called on the conn variable here
&lt;/PRE&gt;
&lt;P&gt;The C# compiler converts this code into the following equivalent code, which has a &lt;B&gt;try &lt;/B&gt;and&lt;B&gt; finally&lt;/B&gt; block to ensure that the &lt;B&gt;Dispose&lt;/B&gt; method on the &lt;B&gt;SqlConnection&lt;/B&gt; object is called, regardless of whether an exception occurs.&lt;/P&gt;&lt;PRE class=code&gt;SqlConnection conn = new SqlConnection(connString);
try
{
  conn.Open();
}
finally
{
  conn.Dispose();
}
&lt;/PRE&gt;
&lt;P&gt;One limitation of the &lt;B&gt;using&lt;/B&gt; statement is that you can only put a single type in the parentheses. If you want to ensure that &lt;B&gt;Dispose&lt;/B&gt; is called on additional resources, you must nest the &lt;B&gt;using&lt;/B&gt; statements as shown in the following example.&lt;/P&gt;&lt;PRE class=code&gt;using (SqlConnection conn = new SqlConnection(connString))
{
  SqlCommand cmd = new SqlCommand("CustomerRead");

  conn.Open();
  using (SqlDataReader dr = cmd.ExecuteReader())
  {
    while (dr.Read())
      Console.WriteLine(dr.GetString(0));
  }
}
&lt;/PRE&gt;
&lt;BLOCKQUOTE class=dtBlock&gt;&lt;B&gt;Note&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/B&gt;Using a nested &lt;B&gt;using&lt;/B&gt; statement on the &lt;B&gt;DataReader&lt;/B&gt; object is useful only if you need to perform further operations with the same connection after the inner &lt;B&gt;using&lt;/B&gt; block. If you close the connection right away, this approach is of limited value because any active &lt;B&gt;DataReader&lt;/B&gt; objects are closed automatically when the connection closes.&lt;/BLOCKQUOTE&gt;
&lt;H3 class=dtH1&gt;When Using DataReaders, Specify CommandBehavior.CloseConnection&lt;/H3&gt;
&lt;P&gt;When you create a &lt;B&gt;DataReader&lt;/B&gt; object, specify the &lt;B&gt;CommandBehavior.CloseConnection&lt;/B&gt; enumeration in your call to &lt;B&gt;ExecuteReader&lt;/B&gt;. This ensures that when you close the &lt;B&gt;DataReader&lt;/B&gt;,&lt;B&gt; &lt;/B&gt;the connection is also closed. The following code fragment shows how to use the &lt;B&gt;CommandBehavior&lt;/B&gt; enumeration.&lt;/P&gt;&lt;PRE class=code&gt;// Create connection and command. Open connection.
. . .
SqlDataReader myReader= myCommand.ExecuteReader(CommandBehavior.CloseConnection);
// read some data
. . .
myReader.Close(); // The connection and reader are closed.
&lt;/PRE&gt;
&lt;P&gt;The &lt;B&gt;CommandBehavior.CloseConnection&lt;/B&gt; is especially helpful when you return a &lt;B&gt;DataReader&lt;/B&gt; from a function, and you do not have control over the calling code. If the caller forgets to close the connection but closes the reader, both are closed when the &lt;B&gt;DataReader&lt;/B&gt; is created by using &lt;B&gt;CommandBehavior.CloseConnection&lt;/B&gt;. This is shown in the following code fragment.&lt;/P&gt;&lt;PRE class=code&gt;public SqlDataReader CustomerRead(int CustomerID)
{
  //... create connection and command, open connection
  return myCommand.ExecuteReader(CommandBehavior.CloseConnection);
}

//... client code
SqlDataReader myReader = CustomerRead(10248);
//... read some data
myReader.Close(); // reader and connection are closed
&lt;/PRE&gt;
&lt;H3 class=dtH1&gt;Do Not Explicitly Open a Connection if You Use Fill or Update for a Single Operation&lt;/H3&gt;
&lt;P&gt;If you perform a single &lt;B&gt;Fill&lt;/B&gt; or &lt;B&gt;Update&lt;/B&gt; operation, do not open the connection before you call the &lt;B&gt;Fill &lt;/B&gt;method, because the &lt;B&gt;DataAdapter&lt;/B&gt; automatically opens and closes the connection for you. The following code fragment shows how to call &lt;B&gt;Fill&lt;/B&gt;.&lt;/P&gt;&lt;PRE class=code&gt;DataSet dSet = new DataSet("test");
SqlConnection conn = new SqlConnection(connString);
SqlCommand cmd = new SqlCommand(sqlQuery,conn);
SqlDataAdapter dAdapter = new SqlDataAdapter(cmd);
dAdapter.Fill(dSet); // The connection was not explicitly opened.
// The connection is opened and closed by the DataAdapter automatically.
&lt;/PRE&gt;
&lt;P&gt;The &lt;B&gt;SqlDataAdapter&lt;/B&gt; automatically opens the connection, runs the selected command, and then closes the connection when it is finished. This enables the connection to be open for the shortest period of time.&lt;/P&gt;
&lt;P&gt;Note that if you need to perform multiple file or update operations, you need to open the connection before the first &lt;B&gt;Fill&lt;/B&gt; or &lt;B&gt;Update&lt;/B&gt; method and close it after the last one. Alternatively, you could wrap multiple &lt;B&gt;Fill&lt;/B&gt; or &lt;B&gt;Update&lt;/B&gt; operations inside a C# &lt;B&gt;using&lt;/B&gt; block to ensure that the connection is closed after the last use.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Avoid Checking the State Property of OleDbConnection&lt;/H3&gt;
&lt;P&gt;If you need to monitor or check connection status and you are using an &lt;B&gt;OleDbConnection&lt;/B&gt;, consider handling the &lt;B&gt;StateChange&lt;/B&gt; event, and avoid checking the &lt;B&gt;State&lt;/B&gt; property. This approach helps to minimize round trips.&lt;/P&gt;
&lt;P&gt;Using the &lt;B&gt;State&lt;/B&gt; property increases application overhead, because each call results in a call to the OLE DB &lt;B&gt;DBPROP_CONNECTIONSTATUS&lt;/B&gt; property (if the connection is an &lt;B&gt;OleDbConnection&lt;/B&gt;) for an open connection.&lt;/P&gt;
&lt;BLOCKQUOTE class=dtBlock&gt;&lt;B&gt;Note&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/B&gt;The .NET Framework 2.0 (code named "Whidbey"), at the time of writing, provides an updated OLE DB .NET Data Provider that resolves this problem.&lt;/BLOCKQUOTE&gt;
&lt;P&gt;The following code fragment shows how to implement the &lt;B&gt;StateChange&lt;/B&gt; event. This event is raised when the state of the connection changes from open to closed or from closed to open.&lt;/P&gt;&lt;PRE class=code&gt;OleDbConnection conn = new OleDbConnection(connStr);

// Set up a connection state change handler.
conn.StateChange  += new StateChangeEventHandler(OnStateChange);
. . .
// StateChange event handler.
protected static void OnStateChange(object sender, StateChangeEventArgs args)
{
  Console.WriteLine("The current Connection state has changed from {0} to {1}.",
                                  args.OriginalState, args.CurrentState);
}
&lt;/PRE&gt;
&lt;BLOCKQUOTE class=dtBlock&gt;&lt;B&gt;Note&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/B&gt;The ODBC provider also incurs similar overhead when using the &lt;B&gt;State&lt;/B&gt; property.&lt;/BLOCKQUOTE&gt;
&lt;H3 class=dtH1&gt;Pool Connections&lt;/H3&gt;
&lt;P&gt;Creating database connections is expensive. You reduce overhead by pooling your database connections. Make sure you call &lt;B&gt;Close&lt;/B&gt; or &lt;B&gt;Dispose&lt;/B&gt; on a connection as soon as possible. When pooling is enabled, calling &lt;B&gt;Close&lt;/B&gt; or &lt;B&gt;Dispose&lt;/B&gt; returns the connection to the pool instead of closing the underlying database connection.&lt;/P&gt;
&lt;P&gt;You must account for the following issues when pooling is part of your design: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Share connections&lt;/B&gt;. Use a per-application or per-group service account to connect to the database. This creates a single pool or a small number of pools, and it enables many client requests to share the same connections. 
&lt;LI&gt;&lt;B&gt;Avoid per-user logons to the database&lt;/B&gt;. Each logon creates a separate pooled connection. This means that you end up with a large number of small pools. If you need a different user for each connection, disable pooling or set a small maximum size for the pool. 
&lt;LI&gt;&lt;B&gt;Do not vary connection strings&lt;/B&gt;. Different connection strings generate different connection pools. For example, using different capitalization, extra spaces, or different ordering of attributes causes connections to go to different pools. The SQL Server .NET Data Provider performs a byte-by-byte comparison to determine whether connection strings match. 
&lt;LI&gt;&lt;B&gt;Release connections&lt;/B&gt;. Do not cache connections. For example, do not put them in session or application variables. Close connections as soon as you are finished with them. Busy connections are not pooled. 
&lt;LI&gt;&lt;B&gt;Passing connections&lt;/B&gt;. Do not pass connections between logical or physical application layers. 
&lt;LI&gt;&lt;B&gt;Consider tuning your pool size if needed&lt;/B&gt;. For example, in the case of the .NET Framework Data Provider for SQL Server, the default minimum pool size is zero and the maximum is 100. You might need to increase the minimum size to reduce warm-up time. You might need to increase the maximum size if your application needs more than 100 connections. 
&lt;LI&gt;&lt;B&gt;Connection pools are managed by the specific database provider&lt;/B&gt;. SqlClient, OleDB client, and third-party clients may provide different configuration and monitoring options. &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;The following list details the pooling mechanisms that are available, and it summarizes pooling behavior for the .NET Framework data providers: 
&lt;UL type=disc&gt;
&lt;LI&gt;The .NET Framework Data Provider for SQL Server pools connections by using a pooling mechanism implemented in managed code. You control pooling behaviors such as lifetime and pool size through connection string arguments. 
&lt;LI&gt;The .NET Framework Data Provider for Oracle also pools connections by using a managed code solution. 
&lt;LI&gt;The .NET Framework Data Provider for OLE DB automatically uses OLE DB session pooling to pool connections. You control pooling behavior through connection string arguments. 
&lt;LI&gt;The .NET Framework Data Provider for ODBC uses ODBC connection pooling. &lt;/LI&gt;&lt;/UL&gt;
&lt;H4 class=dtH1&gt;Monitoring Pooling&lt;/H4&gt;
&lt;P&gt;You can monitor connection pooling to determine that it is working as expected and to help you identify the best minimum and maximum pool sizes.&lt;/P&gt;
&lt;P&gt;&lt;B&gt;Monitoring Pooling on a Computer that is Running SQL Server&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;You can monitor the number of open connections to SQL Server by using the SQL Server &lt;B&gt;SQLServer:General Statistics&lt;/B&gt; performance counter object. This object is available only on a computer that is running SQL Server.&lt;/P&gt;
&lt;P&gt;The connections are not specific to one particular application. If there are multiple applications accessing the server, this object reflects the total number of open connections for every application. Figure 12.2 shows the &lt;B&gt;SQLServer:General Statistics&lt;/B&gt; object in the Performance Monitor tool.&lt;/P&gt;
&lt;P class=fig&gt;&lt;IMG alt="" src="http://msdn.microsoft.com/library/en-us/dnpag/html/ch12---sql-server-general-statistics.gif" border=0&gt;&lt;/P&gt;
&lt;P class=label&gt;&lt;B&gt;Figure 12.2: Performance monitor showing the SQLServer:General Statistics counter&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;When monitoring &lt;B&gt;SQLServer:General Statistics&lt;/B&gt;, you should observe the following: 
&lt;UL type=disc&gt;
&lt;LI&gt;The number of logins per second increases during application startup when the connection pool is established. The number of logins per second should then drop to zero and stay there. Repeated logins and logouts per second indicate that the connection pool is not being used because a different security context is being used to establish the connection. 
&lt;LI&gt;The &lt;B&gt;User Connections&lt;/B&gt; value should stabilize and remain constant. If this value increases and you see a jagged pattern in the number of logins per second, you may be experiencing a connection leak in the connection pool. &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;&lt;B&gt;Monitoring Pooling Using the .NET Framework&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;The .NET Framework Data Provider for SQL Server provides several counters. The following counters are of particular significance: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;SqlClient: Current # connection pools&lt;/B&gt; 
&lt;LI&gt;&lt;B&gt;SqlClient: Current # pooled and nonpooled connections&lt;/B&gt; 
&lt;LI&gt;&lt;B&gt;SqlClient: Current # pooled connections&lt;/B&gt; 
&lt;LI&gt;&lt;B&gt;SqlClient: Peak # pooled connections&lt;/B&gt; &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;The &lt;B&gt;SqlClient: Current # connection pools&lt;/B&gt; counter indicates the number of connection pools that are currently in use. A large number of pools indicates that a pool is not being shared across clients. Using different connection strings creates new pools.&lt;/P&gt;
&lt;P&gt;The &lt;B&gt;SqlClient: Peak # pooled connections&lt;/B&gt; counter indicates the maximum number of connections that are currently in use. If this value remains at its peak, consider measuring the performance impact of increasing the &lt;B&gt;Max Pool Size &lt;/B&gt;attribute in your connection string. The default value is 100. If you see this value at its peak in conjunction with a high number of failed connections in the &lt;B&gt;SqlClient: Total # failed connects&lt;/B&gt; counter, consider changing the value and monitoring performance.&lt;/P&gt;
&lt;BLOCKQUOTE class=dtBlock&gt;&lt;B&gt;Note&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/B&gt;These SqlClient counters may not be reset in .NET Framework version 1.1 when you stop and then restart an application. To reset the counters, stop the application and exit System Monitor, and then start the application and System Monitor again.&lt;/BLOCKQUOTE&gt;
&lt;H3 class=dtH1&gt;More Information&lt;/H3&gt;
&lt;P&gt;For more information about pooling connections, see the following resources on MSDN: 
&lt;UL type=disc&gt;
&lt;LI&gt;"Connection Pooling for the .NET Framework Data Provider for SQL Server" in &lt;I&gt;.NET Framework Developer's Guide&lt;/I&gt; at &lt;A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconConnectionPoolingForSQLServerNETDataProvider.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconConnectionPoolingForSQLServerNETDataProvider.asp&lt;/A&gt; &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;For more information about pooling connections, see the following Knowledge Base articles: 
&lt;UL type=disc&gt;
&lt;LI&gt;164221, "INFO: How to Enable Connection Pooling in an ODBC Application" at &lt;A href="http://support.microsoft.com/default.aspx?scid=kb;en-us;164221"&gt;http://support.microsoft.com/default.aspx?scid=kb;en-us;164221&lt;/A&gt; 
&lt;LI&gt;166083, "INFO: How to Enable Connection Pooling in an OLE DB Application" at &lt;A href="http://support.microsoft.com/default.aspx?scid=kb;en-us;166083"&gt;http://support.microsoft.com/default.aspx?scid=kb;en-us;166083&lt;/A&gt; 
&lt;LI&gt;169470, "INFO: Frequently Asked Questions About ODBC Connection Pooling" at &lt;A href="http://support.microsoft.com/default.aspx?scid=kb;en-us;169470"&gt;http://support.microsoft.com/default.aspx?scid=kb;en-us;169470&lt;/A&gt; 
&lt;LI&gt;216950, "How to Enable ODBC Connection Pooling Performance Counters" at &lt;A href="http://support.microsoft.com/default.aspx?scid=kb;en-us;216950"&gt;http://support.microsoft.com/default.aspx?scid=kb;en-us;216950&lt;/A&gt; 
&lt;LI&gt;237977, "INFO: OLE DB Session Pooling Timeout Configuration" at &lt;A href="http://support.microsoft.com/default.aspx?scid=kb;en-us;237977"&gt;http://support.microsoft.com/default.aspx?scid=kb;en-us;237977&lt;/A&gt; 
&lt;LI&gt;316757, "BUG: SqlClient Connection Pooling That Uses Integrated Security Is Slower Than OleDb" at &lt;A href="http://support.microsoft.com/default.aspx?scid=kb;en-us;316757"&gt;http://support.microsoft.com/default.aspx?scid=kb;en-us;316757&lt;/A&gt; &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;For more information about how to reset the .NET counters, see Knowledge Base article 314429, "BUG: Performance Counters for SQL Server .NET Data Provider Are Not Reset," at &lt;A href="http://support.microsoft.com/default.aspx?scid=kb;en-us;314429"&gt;http://support.microsoft.com/default.aspx?scid=kb;en-us;314429&lt;/A&gt;.&lt;/P&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic10&gt;&lt;/A&gt;Commands&lt;/H2&gt;
&lt;P&gt;You use &lt;B&gt;Command&lt;/B&gt; objects such as &lt;B&gt;SqlCommand&lt;/B&gt;, &lt;B&gt;OleDbCommand,&lt;/B&gt; or &lt;B&gt;OdbcCommand&lt;/B&gt; to run SQL commands against the database. You can run dynamic SQL statements or stored procedures by setting the &lt;B&gt;CommandType&lt;/B&gt; property. You can set the &lt;B&gt;CommandText&lt;/B&gt; property to contain the name of a stored procedure or the SQL statement that you want to run.&lt;/P&gt;
&lt;P&gt;You use command &lt;B&gt;Parameter&lt;/B&gt; objects, such as &lt;B&gt;SqlParameter&lt;/B&gt;, &lt;B&gt;OleDbParameter&lt;/B&gt;, or &lt;B&gt;OdbcParameter&lt;/B&gt;, to specify the input and output parameters required by the current command. The recommended approach for running commands is to call stored procedures by using &lt;B&gt;Parameter&lt;/B&gt; objects that provide parameter type checking. This approach provides both performance and security benefits. When you run commands against a database, consider the following recommendations: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Validate SQL input and use Parameter objects&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Retrieve only the columns and rows you need&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Support paging over large result sets&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Batch SQL statements to reduce round trips&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use ExecuteNonQuery for commands that do not return data&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use ExecuteScalar to return single values&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use CommandBehavior.SequentialAccess for very wide rows or for rows with BLOBs&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Do not use CommandBuilder at run time&lt;/B&gt;. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Validate SQL Input and Use Parameter Objects&lt;/H3&gt;
&lt;P&gt;Validate all the input data that you use in SQL commands. Do not permit the client to retrieve more data than it should. Also, do not trust user input, and do not permit the client to perform operations that it should not perform. Doing so helps to lower the risk of SQL injection. By rejecting invalid data early before you issue a command that has the invalid data, you can improve performance by eliminating unnecessary database requests.&lt;/P&gt;
&lt;P&gt;Use &lt;B&gt;Parameter&lt;/B&gt; objects when you build database commands. When you use &lt;B&gt;Parameter&lt;/B&gt; objects, each parameter is automatically type checked. Checking the type is another effective countermeasure you can use to help prevent SQL injection. Ideally, use &lt;B&gt;Parameter&lt;/B&gt; objects in conjunction with stored procedures to improve performance. For more information about using parameters, see "Parameters" later in this chapter.&lt;/P&gt;
&lt;H4 class=dtH1&gt;Using Parameters with Stored Procedures&lt;/H4&gt;
&lt;P&gt;The following code sample illustrates how to use the &lt;B&gt;Parameters&lt;/B&gt; collection.&lt;/P&gt;&lt;PRE class=code&gt;SqlDataAdapter myCommand = new SqlDataAdapter("AuthorLogin", conn);
myCommand.SelectCommand.CommandType = CommandType.StoredProcedure;
SqlParameter parm = myCommand.SelectCommand.Parameters.Add(
                       "@au_id", SqlDbType.VarChar, 11);
parm.Value = Login.Text;
&lt;/PRE&gt;
&lt;P&gt;In the code sample, the &lt;B&gt;@au_id&lt;/B&gt; parameter is treated as a literal value and not as code that can be run. Also, the parameter is checked for type and length. In the code fragment, the input value cannot be longer than 11 characters. If the data does not conform to the type or length that is defined by the parameter, an exception is generated.&lt;/P&gt;
&lt;P&gt;Using stored procedures alone does not necessarily prevent SQL injection. The important thing to do is use parameters with stored procedures. If you do not use parameters, your stored procedures may be susceptible to SQL injection if the stored procedures use unfiltered input. For example, the following code fragment is susceptible to SQL injection.&lt;/P&gt;&lt;PRE class=code&gt;SqlDataAdapter myCommand = new SqlDataAdapter("LoginStoredProcedure '" +
                               Login.Text + "'", conn);
&lt;/PRE&gt;
&lt;H4 class=dtH1&gt;Using Parameters with Dynamic SQL&lt;/H4&gt;
&lt;P&gt;If you cannot use stored procedures, you can still use parameters with dynamic SQL as shown in the following code fragment.&lt;/P&gt;&lt;PRE class=code&gt;SqlDataAdapter myCommand = new SqlDataAdapter(
"SELECT au_lname, au_fname FROM Authors WHERE au_id = @au_id", conn);
SqlParameter parm = myCommand.SelectCommand.Parameters.Add("@au_id",
                        SqlDbType.VarChar, 11);
parm.Value = Login.Text;
&lt;/PRE&gt;
&lt;H3 class=dtH1&gt;Retrieve Only the Columns and Rows You Need&lt;/H3&gt;
&lt;P&gt;Reduce unnecessary processing and network traffic by retrieving only the columns and the rows you need. Do not use the SELECT * query. This is poor practice because you might not know the schema, or it might change. It is easy to retrieve more data than you expect. Consider a scenario where you want four columns, but you perform an operation by using the SELECT * query on a 400-column table. In that scenario, you receive many more results than you expect. Instead, use WHERE clauses to filter the rows.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Support Paging Over Large Result Sets&lt;/H3&gt;
&lt;P&gt;If you have a large result set that contains many rows of data, consider whether you can implement a paging technique to batch the retrieval of data. Batching the retrieval of data helps to reduce database server load, to reduce network traffic, and to put fewer memory requirements on the data access client. For more information, see "Paging Records" later in this chapter.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Batch SQL Statements to Reduce Round Trips&lt;/H3&gt;
&lt;P&gt;Batching is the process of grouping several SQL statements in one trip to the server. The syntax in the following code fragment calls a stored procedure (that groups several queries) to return multiple result sets. The code uses the &lt;B&gt;NextResult&lt;/B&gt; method of the &lt;B&gt;DataReader&lt;/B&gt; object to advance to the next result set. &lt;B&gt;NextResult&lt;/B&gt; can be called multiple times, and it returns true when another result set exists. It returns false when there are no more result sets.&lt;/P&gt;&lt;PRE class=code&gt;SqlCommand cmd = new SqlCommand();
cmd.CommandText = "ReadCustomerAndOrders";
// The stored procedure returns multiple result sets.
SqlDataReader myReader = cmd.ExecuteReader();

if (myReader.read())
//... read first result set

reader.NextResult();

if (myReader.read())
//... read
&lt;/PRE&gt;
&lt;P&gt;If you build complex SQL strings dynamically, you can use a &lt;B&gt;StringBuilder&lt;/B&gt; object to reduce the performance cost of building the strings.&lt;/P&gt;
&lt;H4 class=dtH1&gt;More Information&lt;/H4&gt;
&lt;P&gt;You can also use stored procedures to batch SQL operations. For more information, see Knowledge Base article 311274, "HOW TO: Handle Multiple Results by Using the DataReader in Visual C# .NET," at &lt;A href="http://support.microsoft.com/default.aspx?scid=kb;en-us;311274"&gt;http://support.microsoft.com/default.aspx?scid=kb;en-us;311274&lt;/A&gt;.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Use ExecuteNonQuery for Commands That Do Not Return Data&lt;/H3&gt;
&lt;P&gt;If you want to run commands that do not retrieve data, use the &lt;B&gt;ExecuteNonQuery&lt;/B&gt; method. For example, you would use &lt;B&gt;ExecuteNonQuery&lt;/B&gt; for the following types of commands: 
&lt;UL type=disc&gt;
&lt;LI&gt;Data Definition Language commands such as CREATE and ALTER 
&lt;LI&gt;Data Modification Language commands such as INSERT, UPDATE, and DELETE 
&lt;LI&gt;Data Control Language commands such as GRANT and REVOKE. &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;The following code fragment shows an update to the customer table that uses &lt;B&gt;ExecuteNonQuery&lt;/B&gt;.&lt;/P&gt;&lt;PRE class=code&gt;SqlConnection conn = new SqlConnection(connString);
SqlCommand cmd = new SqlCommand(
    "UPDATE Customer SET Freight = 45.44 WHERE CustomerID = 10248", conn);
cmd.ExecuteNonQuery();
&lt;/PRE&gt;
&lt;H3 class=dtH1&gt;Use ExecuteScalar to Return Single Values&lt;/H3&gt;
&lt;P&gt;If you want to retrieve a single value from your query by using a function such as COUNT(*) or SUM(Price), you can use a stored procedure output parameter, and then use the &lt;B&gt;Command.ExecuteNonQuery&lt;/B&gt; method. This eliminates the overhead that is associated with creating a result set.&lt;/P&gt;
&lt;P&gt;The following stored procedure returns the number of rows in a &lt;B&gt;Customers&lt;/B&gt; table.&lt;/P&gt;&lt;PRE class=code&gt;CREATE PROCEDURE GetNumberOfCustomers(
@CustomerCount int OUTPUT)
AS
SELECT @CustomerCount = COUNT(*)
FROM Customers
&lt;/PRE&gt;
&lt;P&gt;To call the stored procedure, use the following code.&lt;/P&gt;&lt;PRE class=code&gt;static int GetCustomerCount()
{
  int customerCount = 0;

  SqlConnection conn = new SqlConnection("server=(local);" +
    "Integrated Security=SSPI;database=northwind");
  SqlCommand cmd = new SqlCommand("GetNumberOfCustomers", conn );
  cmd.CommandType = CommandType.StoredProcedure;

  SqlParameter paramCustCount =
    cmd.Parameters.Add("@CustomerCount", SqlDbType.Int );
    paramCustCount.Direction = ParameterDirection.Output;

  try
  {
    conn.Open();
    cmd.ExecuteNonQuery();
    customerCount = (int)paramCustCount.Value;
  }
  finally
  {
    if(null!=conn)
      conn.Close();
  }
  return customerCount;
}
&lt;/PRE&gt;
&lt;P&gt;If you do not have control over the stored procedure, and if the stored procedure returns the number of rows as a return value, then you can use &lt;B&gt;Command.ExecuteScalar&lt;/B&gt; as shown in the following code fragment. The &lt;B&gt;ExecuteScalar&lt;/B&gt; method returns the value of the first column of the first row of the result set.&lt;/P&gt;&lt;PRE class=code&gt;static int GetCustomerCountWithScalar()
{
  int customerCount = 0;

  SqlConnection conn = new SqlConnection(
     "server=(local);Integrated Security=SSPI;database=northwind");
  SqlCommand cmd = new SqlCommand("GetCustomerCountWithScalar", conn );
  cmd.CommandType = CommandType.StoredProcedure;

  try
  {
    conn.Open();
    customerCount = (int)cmd.ExecuteScalar();
  }
  finally
  {
    if(null!=conn)
       conn.Close();
  }
  return customerCount;
}
&lt;/PRE&gt;
&lt;P&gt;The previous code fragment requires the following stored procedure.&lt;/P&gt;&lt;PRE class=code&gt;CREATE PROCEDURE GetCustomerCountWithScalar
AS
SELECT COUNT(*) FROM Customers
&lt;/PRE&gt;
&lt;H3 class=dtH1&gt;Use CommandBehavior.SequentialAccess for Very Wide Rows or for Rows with BLOBs&lt;/H3&gt;
&lt;P&gt;Use the &lt;B&gt;CommandBehavior.SequentialAccess&lt;/B&gt; enumeration for very wide rows or for rows that contain binary large object (BLOB) data. This permits you to return specific bytes from the retrieved row instead of returning the entire row. Returning the entire row may consume large amounts of memory because of the BLOB data.&lt;/P&gt;
&lt;P&gt;When you use &lt;B&gt;CommandBehavior.SequentialAccess&lt;/B&gt;, the BLOB data is retrieved only when you reference it. For example, you can call the &lt;B&gt;GetBytes&lt;/B&gt; method. The &lt;B&gt;GetBytes&lt;/B&gt; method permits you to control the precise number of bytes that are read. The following code fragment shows how to use &lt;B&gt;CommandBehavior.SequentialAccess&lt;/B&gt;.&lt;/P&gt;&lt;PRE class=code&gt;SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)
&lt;/PRE&gt;
&lt;P&gt;Also, if you are performing optimistic locking against a table with very wide rows or against rows that contain BLOB data, use timestamps. Use timestamps instead of comparing all the fields in the table to the original versions. Using time stamps reduces the number of arguments by a value that is equal to &lt;I&gt;n&lt;/I&gt;/2+1.&lt;/P&gt;
&lt;H4 class=dtH1&gt;More Information&lt;/H4&gt;
&lt;P&gt;For a complete sample, see "Obtaining BLOB Values from a Database" in &lt;I&gt;.NET Framework Developer's Guide&lt;/I&gt; on MSDN at &lt;A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconobtainingblobvaluesfromdatabase.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconobtainingblobvaluesfromdatabase.asp&lt;/A&gt;.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Do Not Use CommandBuilder at Run Time&lt;/H3&gt;
&lt;P&gt;&lt;B&gt;CommandBuilder&lt;/B&gt; objects such as &lt;B&gt;SqlCommandBuilder&lt;/B&gt; and &lt;B&gt;OleDbCommandBuilder&lt;/B&gt; automatically generate the &lt;B&gt;InsertCommand&lt;/B&gt;, &lt;B&gt;UpdateCommand&lt;/B&gt;, and &lt;B&gt;DeleteCommand&lt;/B&gt; properties of a &lt;B&gt;DataAdapter. &lt;/B&gt;The &lt;B&gt;CommandBuilder&lt;/B&gt; objects generate these properties based on the &lt;B&gt;SelectCommand&lt;/B&gt; property of the &lt;B&gt;DataAdapter&lt;/B&gt;. &lt;B&gt;CommandBuilder&lt;/B&gt; objects are useful when you are designing and prototyping your application. However, you should not use them in production applications. The processing required to generate the commands affects performance. Manually create stored procedures for your commands, or use the Visual Studio&amp;#174; .NET design-time wizard and customize them later if necessary.&lt;/P&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic11&gt;&lt;/A&gt;Stored Procedures&lt;/H2&gt;
&lt;P&gt;This section discusses how to write and to call stored procedures for maximum performance. You should generally prefer stored procedures over direct SQL statements, because stored procedures perform better. Stored procedures perform better because the database can optimize the data access plan used by the procedure and then cache it for subsequent reuse. In addition, stored procedures provide security and maintenance benefits. 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Use stored procedures&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use CommandType.Text with OleDbCommand&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use CommandType.StoredProcedure with SqlCommand&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Consider using Command.Prepare&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use output parameters where possible&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Consider SET NOCOUNT ON for SQL Server&lt;/B&gt;. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Use Stored Procedures&lt;/H3&gt;
&lt;P&gt;Stored procedures generally provide improved performance in comparison to SQL statements that are run directly. The following list explains the benefits of stored procedures compared to running data access logic directly from the middle tier of your application: 
&lt;UL type=disc&gt;
&lt;LI&gt;The database can prepare, optimize, and cache the execution plan so that the execution plan can be reused at a later time. 
&lt;LI&gt;Stored procedures pass less information over the network on the initial request, because they only need to transmit the procedure name and the parameters. Everything else is already at the server. 
&lt;LI&gt;Stored procedures abstract SQL statements from the client and business object developers and put responsibility for their maintenance in the hands of SQL experts. 
&lt;LI&gt;Stored procedures also provide maintenance and security benefits. &lt;/LI&gt;&lt;/UL&gt;
&lt;H4 class=dtH1&gt;More Information&lt;/H4&gt;
&lt;P&gt;For more information about the security benefits of stored procedures and about how you can use them as a countermeasure for SQL injection, see the following: 
&lt;UL type=disc&gt;
&lt;LI&gt;Chapter 14, "Building Secure Data Access," in "Improving Web Application Security: Threats and Countermeasures" on MSDN at &lt;A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/thcmch14.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/thcmch14.asp&lt;/A&gt; 
&lt;LI&gt;"Stored Procedure and Trigger Execution" in &lt;I&gt;Microsoft SQL Server 2000: SQL Server Architecture&lt;/I&gt; on MSDN at &lt;A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/architec/8_ar_sa_7cmm.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/architec/8_ar_sa_7cmm.asp&lt;/A&gt; 
&lt;LI&gt;MSDN article, "Query Recompilation in SQL Server 2000," at &lt;A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsql2k/html/sql_queryrecompilation.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsql2k/html/sql_queryrecompilation.asp&lt;/A&gt; 
&lt;LI&gt;Knowledge Base article 243586, "INF: Troubleshooting Stored Procedure Recompilation," at &lt;A href="http://support.microsoft.com/default.aspx?scid=kb;en-us;243586"&gt;http://support.microsoft.com/default.aspx?scid=kb;en-us;243586&lt;/A&gt; 
&lt;LI&gt;&lt;I&gt;SQL Server Magazine&lt;/I&gt; article, "Inside SQL Server: Stored Procedure Plans: Improve performance through caching" at http://www.sqlmag.com/Articles/Print.cfm?ArticleID=6113. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Use CommandType.Text with OleDbCommand&lt;/H3&gt;
&lt;P&gt;If you use an &lt;B&gt;OleDbCommand&lt;/B&gt; object to call a stored procedure, use the &lt;B&gt;CommandType.Text&lt;/B&gt; enumeration with the ODBC call syntax. If you use &lt;B&gt;CommandType.StoredProcedure&lt;/B&gt;, ODBC call syntax is generated by the provider anyway. By using explicit call syntax, you reduce the work of the provider.&lt;/P&gt;
&lt;P&gt;You should also set the type and length of any parameters that the stored procedure requires. Set the type and length of the parameters to prevent the provider from performing an additional round trip to obtain the parameter information from the database. The following code fragment demonstrates how to use ODBC call syntax and &lt;B&gt;CommandType.Text&lt;/B&gt;, and how to explicitly set parameter information.&lt;/P&gt;&lt;PRE class=code&gt;using (OleDbConnection conn = new OleDbConnection(connStr))
{
  OleDbCommand cmd = new OleDbCommand("call CustOrderHist(?)", conn);
  cmd.CommandType = CommandType.Text;
  OleDbParameter param = cmd.Parameters.Add("@CustomerID", OleDbType.Char, 5);
  param.Value = "ALFKI";
  conn.Open();
  OleDbDataReader reader = cmd.ExecuteReader();
  try
  {
    // List each product.
    while (reader.Read())
      Console.WriteLine(reader.GetString(0));
  }
  finally
  {
    reader.Close();
  }
} // Dispose is called on conn here&lt;/PRE&gt;
&lt;H3 class=dtH1&gt;Use CommandType.StoredProcedure with SqlCommand&lt;/H3&gt;
&lt;P&gt;If you are using the &lt;B&gt;SqlCommand &lt;/B&gt;object, use &lt;B&gt;CommandType.StoredProcedure&lt;/B&gt; when you call stored procedures. Do not use &lt;B&gt;CommandType.Text&lt;/B&gt; because it requires extra parsing. The following code fragment shows how to set the &lt;B&gt;CommandType&lt;/B&gt; property to avoid extra parsing on the server.&lt;/P&gt;&lt;PRE class=code&gt;SqlConnection conn = new SqlConnection(connString);
SqlCommand cmd = new SqlCommand("UpdateCustomerProcedure", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(...
&lt;/PRE&gt;
&lt;H3 class=dtH1&gt;Consider Using Command.Prepare&lt;/H3&gt;
&lt;P&gt;If your application runs the same set of SQL queries multiple times, preparing those queries by using the &lt;B&gt;Command.Prepare&lt;/B&gt; method may give you better performance. In ADO.NET, the &lt;B&gt;SqlCommand.Prepare&lt;/B&gt; method calls the &lt;B&gt;sp_prepare&lt;/B&gt; stored procedure for SQL Server 7. The &lt;B&gt;SqlCommand.Prepare&lt;/B&gt; method calls &lt;B&gt;sp_prepexec&lt;/B&gt; for SQL Server 2000 and later. &lt;B&gt;SqlCommand.Prepare&lt;/B&gt; makes these calls instead of running a regular batch remote procedure call (RPC). The following code fragment shows how to use &lt;B&gt;Command.Prepare&lt;/B&gt;.&lt;/P&gt;&lt;PRE class=code&gt;cmd.CommandText =
   "insert into Region (RegionID, RegionDescription) values (@id, @desc)";

cmd.Parameters.Add ( "@id", SqlDbType.Int, 4, "RegionID") ;
cmd.Parameters.Add ( "@desc", SqlDbType.NChar, 50, "RegionDescription") ;

cmd.Parameters[0].Value = 5;
cmd.Parameters[1].Value = "North West";
cmd.Prepare();
cmd.ExecuteNonQuery();

cmd.Parameters[0].Value = 6;
cmd.Parameters[1].Value = "North East";
cmd.ExecuteNonQuery();

cmd.Parameters[0].Value = 7;
cmd.Parameters[1].Value = "South East";
cmd.ExecuteNonQuery();

cmd.Parameters[0].Value = 8;
cmd.Parameters[1].Value = "South West";
cmd.ExecuteNonQuery();
&lt;/PRE&gt;
&lt;P&gt;Using the &lt;B&gt;Prepare&lt;/B&gt; method does not yield a benefit if you are only going to run the statement one or two times. The next version of SQL Server will better leverage how plans are cached, so using it would not make a difference. You should only use the &lt;B&gt;Prepare&lt;/B&gt; method for those statements that you run multiple times.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Use Output Parameters Where Possible&lt;/H3&gt;
&lt;P&gt;Use output parameters and &lt;B&gt;ExecuteNonQuery&lt;/B&gt; to return small amounts of data instead of returning a result set that contains a single row. When you use output parameters and &lt;B&gt;ExecuteNonQuery&lt;/B&gt; to return small amounts of data, you avoid the performance overhead that is associated with creating the result set on the server.&lt;/P&gt;
&lt;P&gt;The following code fragment uses a stored procedure to retrieve the product name and unit price for a specific product that is contained in the Products table in the Northwind database.&lt;/P&gt;&lt;PRE class=code&gt;void GetProductDetails( int ProductID,
                        out string ProductName, out decimal UnitPrice )
{
  using( SqlConnection conn = new SqlConnection(
        "server=(local);Integrated Security=SSPI;database=Northwind") )
  {
    // Set up the command object used to run the stored procedure.
    SqlCommand cmd = new SqlCommand( "DATGetProductDetailsSPOutput", conn );
    cmd.CommandType = CommandType.StoredProcedure;
    // Establish stored procedure parameters.
    //  @ProductID int INPUT
    //  @ProductName nvarchar(40) OUTPUT
    //  @UnitPrice money OUTPUT

    // Must explicitly set the direction of the output parameters.
    SqlParameter paramProdID =
             cmd.Parameters.Add( "@ProductID", ProductID );
    paramProdID.Direction = ParameterDirection.Input;
    SqlParameter paramProdName =
             cmd.Parameters.Add( "@ProductName", SqlDbType.VarChar, 40 );
    paramProdName.Direction = ParameterDirection.Output;
    SqlParameter paramUnitPrice =
             cmd.Parameters.Add( "@UnitPrice", SqlDbType.Money );
    paramUnitPrice.Direction = ParameterDirection.Output;

    conn.Open();
    // Use ExecuteNonQuery to run the command.
    // Although no rows are returned, any mapped output parameters
    // (and potential return values) are populated
    cmd.ExecuteNonQuery( );
    // Return output parameters from stored procedure.
    ProductName = paramProdName.Value.ToString();
    UnitPrice = (decimal)paramUnitPrice.Value;
  }
}&lt;/PRE&gt;
&lt;H3 class=dtH1&gt;Consider SET NOCOUNT ON for SQL Server&lt;/H3&gt;
&lt;P&gt;When you use SET NOCOUNT ON, the message that indicates the number of rows that are affected by the T-SQL statement is not returned as part of the results. When you use SET NOCOUNT OFF, the count is returned. Using SET NOCOUNT ON can improve performance because network traffic can be reduced. SET NOCOUNT ON prevents SQL Server from sending the DONE_IN_PROC message for each statement in a stored procedure or batch of SQL statements.&lt;/P&gt;
&lt;P&gt;For example, if you have eight operations in a stored procedure, eight messages are returned to the caller. Each message contains the number of rows affected by the respective statement. When you use SET NOCOUNT ON, you reduce the processing that SQL Server performs and the size of the response that is sent across the network.&lt;/P&gt;
&lt;BLOCKQUOTE class=dtBlock&gt;&lt;B&gt;Note&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/B&gt;In Query Analyzer, the DONE_IN_PROC message is intercepted and displayed as "&lt;I&gt;N&lt;/I&gt; rows affected".&lt;/BLOCKQUOTE&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic12&gt;&lt;/A&gt;Parameters&lt;/H2&gt;
&lt;P&gt;Most SQL commands require input or output parameters, regardless of whether they are stored procedures or direct SQL statements. Each .NET Framework data provider provides a &lt;B&gt;Parameter&lt;/B&gt; object implementation. You can use a &lt;B&gt;Parameter&lt;/B&gt; object implementation in conjunction with a &lt;B&gt;Command&lt;/B&gt; object. Some sample parameter objects include &lt;B&gt;SqlParameter&lt;/B&gt;, &lt;B&gt;OleDbParameter,&lt;/B&gt; and &lt;B&gt;OdbcParameter&lt;/B&gt;. When you use parameters, consider the following recommendations: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Use the Parameters collection when you call a stored procedure&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use the Parameters collection when you build SQL statements&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Explicitly create stored procedure parameters&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Specify parameter types&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Cache stored procedure SqlParameter objects&lt;/B&gt;. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Use the Parameters Collection When You Call a Stored Procedure&lt;/H3&gt;
&lt;P&gt;Use the &lt;B&gt;Parameters&lt;/B&gt; collection property of the &lt;B&gt;SqlCommand&lt;/B&gt; object to pass parameters to a stored procedure. By using strongly typed parameters, types do not need to be discovered at run time. You can also save round trips by checking the data type on the client; for example, you can save round trips by checking the Web server. This prevents wasted cycles and wasted bandwidth that is caused by passing invalid data to the database server. The following code fragment shows how to add a typed parameter to the &lt;B&gt;Parameters&lt;/B&gt; collection.&lt;/P&gt;&lt;PRE class=code&gt;SqlDataAdapter adapter = new SqlDataAdapter("GetProductDesc",
                                             conn);
adapter.SelectCommand.CommandType = CommandType.StoredProcedure;
SqlParameter parm = adapter.SelectCommand.Parameters.Add(
                                             "@ProdID", SqlDbType.Int);
parm.Value = 10;
&lt;/PRE&gt;
&lt;H3 class=dtH1&gt;Use the Parameters Collection When You Build SQL Statements&lt;/H3&gt;
&lt;P&gt;Even if you do not use stored procedures for data access, you should still use the &lt;B&gt;Parameters&lt;/B&gt; collection when you build your SQL statements in code. By using the &lt;B&gt;Parameter&lt;/B&gt; collection and by explicitly setting the data type, you reduce the likelihood that the &lt;B&gt;Parameter&lt;/B&gt; object could set an invalid type. The following code fragment shows how to use the &lt;B&gt;Parameters&lt;/B&gt; collection when you build your SQL statements in code.&lt;/P&gt;&lt;PRE class=code&gt;SqlDataAdapter adapter = new SqlDataAdapter(
"SELECT ProductID, ProductName FROM Products WHERE ProductID = @ProdID", conn);
// Set the parameter including name and type.
SqlParameter parm = adapter.SelectCommand.Parameters.Add("@ProdID",
                                                          SqlDbType.Int);
// Set the parameter value.
parm.Value = 10;&lt;/PRE&gt;
&lt;H3 class=dtH1&gt;Explicitly Create Stored Procedure Parameters&lt;/H3&gt;
&lt;P&gt;Identifying parameters at run time requires a round trip to the server for each use of a stored procedure. This is an expensive operation. Explicitly create parameters for stored procedures. Explicitly supply the parameter type, size, precision, and scale information to prevent the &lt;B&gt;Command&lt;/B&gt; object from recreating them every time a command is run. The following code demonstrates how to set the type, size, and direction.&lt;/P&gt;&lt;PRE class=code&gt;void GetProductDetails( int productID,
                        out string productName, out decimal unitPrice)
{
  using( SqlConnection conn = new SqlConnection(
        "server=(local);Integrated Security=SSPI;database=Northwind") )
  {
    // Set up the command object used to run the stored procedure.
    SqlCommand cmd = new SqlCommand( "GetProductDetails", conn );
    cmd.CommandType = CommandType.StoredProcedure;
    // Establish stored procedure parameters.
    //  @ProductID int INPUT
    //  @ProductName nvarchar(40) OUTPUT
    //  @UnitPrice money OUTPUT

    // Must explicitly set the direction of output parameters.
    SqlParameter paramProdID =
             cmd.Parameters.Add( "@ProductID", SqlDbType.Int );
    paramProdID.Direction = ParameterDirection.Input;
    SqlParameter paramProdName =
             cmd.Parameters.Add( "@ProductName", SqlDbType.VarChar, 40 );
    paramProdName.Direction = ParameterDirection.Output;
    SqlParameter paramUnitPrice =
             cmd.Parameters.Add( "@UnitPrice", SqlDbType.Money );
    paramUnitPrice.Direction = ParameterDirection.Output;

    conn.Open();
    cmd.ExecuteNonQuery( );
    // Return output parameters from the stored procedure.
    productName = paramProdName.Value.ToString();
    unitPrice = (decimal)paramUnitPrice.Value;
  }
}&lt;/PRE&gt;
&lt;H3 class=dtH1&gt;Specify Parameter Types&lt;/H3&gt;
&lt;P&gt;When you create a new parameter, use the relevant enumerated type to specify the data type of the parameter. Use an enumerated type such as &lt;B&gt;SqlDbType&lt;/B&gt; or &lt;B&gt;OleDbType&lt;/B&gt;. This prevents unnecessary type conversions that are otherwise performed by the data provider.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Cache Stored Procedure SqlParameter Objects&lt;/H3&gt;
&lt;P&gt;Often, applications must run commands multiple times. To avoid recreating the &lt;B&gt;SqlParameter&lt;/B&gt; objects each time, cache them so that they can be reused later. A good approach is to cache parameter arrays in a &lt;B&gt;Hashtable&lt;/B&gt; object. Each parameter array contains the parameters that are required by a particular stored procedure that is used by a particular connection. The following code fragment shows this approach.&lt;/P&gt;&lt;PRE class=code&gt;public static void CacheParameterSet(string connectionString,
                                     string commandText,
                                     params SqlParameter[] commandParameters)
{
  if( connectionString == null || connectionString.Length == 0 )
    throw new ArgumentNullException( "connectionString" );
  if( commandText == null || commandText.Length == 0 )
    throw new ArgumentNullException( "commandText" );

  string hashKey = connectionString + ":" + commandText;
  paramCache[hashKey] = commandParameters;
}
&lt;/PRE&gt;
&lt;P&gt;The following function shows the equivalent parameter retrieval function&lt;/P&gt;&lt;PRE class=code&gt;public static SqlParameter[] GetCachedParameterSet(string connectionString, string commandText)
{
  if( connectionString == null || connectionString.Length == 0 )
    throw new ArgumentNullException( "connectionString" );
  if( commandText == null || commandText.Length == 0 )
    throw new ArgumentNullException( "commandText" );

  string hashKey = connectionString + ":" + commandText;

  SqlParameter[] cachedParameters = paramCache[hashKey] as SqlParameter[];
  if (cachedParameters == null)
  {
    return null;
  }
  else
  {
    return CloneParameters(cachedParameters);
  }
}
&lt;/PRE&gt;
&lt;P&gt;When parameters are retrieved from the cache, a cloned copy is created so that the client application can change parameter values, without affecting the cached parameters. The &lt;B&gt;CloneParameters&lt;/B&gt; method is shown in the following code fragment.&lt;/P&gt;&lt;PRE class=code&gt;private static SqlParameter[] CloneParameters(SqlParameter[] originalParameters)
{
  SqlParameter[] clonedParameters = new SqlParameter[originalParameters.Length];

  for (int i = 0, j = originalParameters.Length; i &amp;lt; j; i++)
  {
    clonedParameters[i] =
      (SqlParameter)((ICloneable)originalParameters[i]).Clone();
  }
  return clonedParameters;
}
&lt;/PRE&gt;
&lt;H4 class=dtH1&gt;More Information&lt;/H4&gt;
&lt;P&gt;The code samples for the parameter caching approach that is shown above are based on samples from the Data Access Application Block. The Data Access Application Block implements this functionality in a generic data access component. For more information, see the Data Access Application Block on MSDN at &lt;A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/daab-rm.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/daab-rm.asp&lt;/A&gt;.&lt;/P&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic13&gt;&lt;/A&gt;DataSet vs. DataReader&lt;/H2&gt;
&lt;P&gt;When you need to retrieve multiple rows of data so that you can display or process the data in some other way, you have two basic choices. You can use a &lt;B&gt;DataSet&lt;/B&gt; object or a &lt;B&gt;DataReader &lt;/B&gt;object.&lt;/P&gt;
&lt;P&gt;The &lt;B&gt;DataReader&lt;/B&gt; approach is generally quicker because it avoids the overhead that is associated with creating a &lt;B&gt;DataSet&lt;/B&gt; object. The overhead that is associated with a &lt;B&gt;DataSet&lt;/B&gt; object includes creating &lt;B&gt;DataSet&lt;/B&gt; subobjects such as &lt;B&gt;DataTables&lt;/B&gt;, &lt;B&gt;DataRows&lt;/B&gt;, and &lt;B&gt;DataColumns&lt;/B&gt;. However, the &lt;B&gt;DataReader&lt;/B&gt; provides less flexibility, and is less suited to situations where you have to cache data and pass the data to components in an application that has multiple tiers.&lt;/P&gt;
&lt;BLOCKQUOTE class=dtBlock&gt;&lt;B&gt;Note&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/B&gt;The &lt;B&gt;DataAdapter&lt;/B&gt; used to fill the &lt;B&gt;DataSet&lt;/B&gt; uses a &lt;B&gt;DataReader&lt;/B&gt; internally.&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Use a &lt;B&gt;DataReader&lt;/B&gt; when the following conditions are true: 
&lt;UL type=disc&gt;
&lt;LI&gt;You need forward-only, read-only access to data (the fire hose scenario), and you want to access the data as quickly as possible, and you do not need to cache it. 
&lt;LI&gt;You have a data container such as a business component that you can put the data in. &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;Use a &lt;B&gt;DataSet&lt;/B&gt; when the following conditions are true: 
&lt;UL type=disc&gt;
&lt;LI&gt;You have to cache or pass the data between layers. 
&lt;LI&gt;You require an in-memory relational view of the data for XML or non-XML manipulation. 
&lt;LI&gt;You want to update some or all the retrieved rows, and you want to use the batch update facilities of the &lt;B&gt;SqlDataAdapter &lt;/B&gt;class. 
&lt;LI&gt;You have to bind data to a control type that the &lt;B&gt;DataReader&lt;/B&gt; cannot be bound to. Many Windows Forms controls capable of data binding require a data source that implements the &lt;B&gt;IList &lt;/B&gt;interface. The &lt;B&gt;DataSet&lt;/B&gt; implements &lt;B&gt;IList&lt;/B&gt;, but the &lt;B&gt;DataReader&lt;/B&gt; implements &lt;B&gt;IEnumerable&lt;/B&gt;. &lt;B&gt;IEnumerable&lt;/B&gt; supports data binding to most Web Form controls but not to certain Windows Forms controls. Check the data source requirements for the particular control type that you want to bind. 
&lt;LI&gt;You have to access multiple sets of data at the same time, and you do not want to hold open server resources. &lt;/LI&gt;&lt;/UL&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic14&gt;&lt;/A&gt;DataReader&lt;/H2&gt;
&lt;P&gt;The &lt;B&gt;DataReader&lt;/B&gt; provides a read-only, forward-only stream of data from a database. When you use &lt;B&gt;DataReader&lt;/B&gt; objects such as &lt;B&gt;SqlDataReader&lt;/B&gt; or &lt;B&gt;OleDbDataReader&lt;/B&gt;, consider the following recommendations: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Close DataReader objects&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Consider using CommandBehavior.CloseConnection to close connections&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Cancel pending data&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Consider using CommandBehavior.SequentialAccess with ExecuteReader&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use GetOrdinal when using an index-based lookup&lt;/B&gt;. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Close DataReader Objects&lt;/H3&gt;
&lt;P&gt;Close your &lt;B&gt;DataReader&lt;/B&gt; object as soon as you are finished with it, either by calling its &lt;B&gt;Close&lt;/B&gt; method or by calling its &lt;B&gt;Dispose&lt;/B&gt; method. It is best to use a &lt;B&gt;finally&lt;/B&gt; block to ensure that the &lt;B&gt;DataReader&lt;/B&gt; is closed as shown in the following code fragment.&lt;/P&gt;&lt;PRE class=code&gt;using (SqlConnection conn = new SqlConnection(connString))
{
  SqlCommand cmd = new SqlCommand("CustomerRead",conn);

  conn.Open();
  SqlDataReader dr = cmd.ExecuteReader();
  try
  {
    while (dr.Read())
      Console.WriteLine(dr.GetString(0));
  }
  finally
  {
    dr.Close();
  }
}
&lt;/PRE&gt;
&lt;H3 class=dtH1&gt;Consider Using CommandBehavior.CloseConnection to Close Connections&lt;/H3&gt;
&lt;P&gt;If you need to return a &lt;B&gt;DataReader&lt;/B&gt; from a method, consider using the &lt;B&gt;CommandBehavior.CloseConnection&lt;/B&gt; method to ensure that the associated connection is closed when the &lt;B&gt;DataReader&lt;/B&gt; is closed. The following code fragment shows this approach.&lt;/P&gt;&lt;PRE class=code&gt;public SqlDataReader RetrieveRowsWithDataReader()
{
  SqlConnection conn = new SqlConnection(
         "server=(local);Integrated Security=SSPI;database=northwind");
  SqlCommand cmd = new SqlCommand("RetrieveProducts", conn );
  cmd.CommandType = CommandType.StoredProcedure;
  try
  {
    conn.Open();
    // Generate the reader. CommandBehavior.CloseConnection causes
    // the connection to be closed when the reader object is closed.
    return( cmd.ExecuteReader( CommandBehavior.CloseConnection ) );
  }
  finally
  {
    if(null!=conn)
       conn.Close();
  }
}

// Display the product list using the console.
private void DisplayProducts()
{
  SqlDataReader reader = RetrieveRowsWithDataReader();
  try
  {
    while (reader.Read())
    {
      Console.WriteLine("{0} {1}",
                        reader.GetInt32(0).ToString(),
                        reader.GetString(1));
    }
  }
  finally
  {
     if(null!= reader)
        reader.Close(); // Also closes the connection due to the CommandBehavior
                        // enumerator used when generating the reader.
  }
}&lt;/PRE&gt;
&lt;H3 class=dtH1&gt;Cancel Pending Data&lt;/H3&gt;
&lt;P&gt;When you call the &lt;B&gt;Close &lt;/B&gt;method, the method does not return until all the remaining data has been fetched. If you know you have pending data when you want to close your &lt;B&gt;DataReader&lt;/B&gt;, you can call the &lt;B&gt;Cancel &lt;/B&gt;method before you call &lt;B&gt;Close&lt;/B&gt; to tell the server to stop sending data.&lt;/P&gt;
&lt;P&gt;This approach does not always result in a performance improvement, because &lt;B&gt;Cancel&lt;/B&gt; is not guaranteed to make the server stop sending data. Control information is still exchanged after the call to &lt;B&gt;Cancel&lt;/B&gt;, and the control information may or may not be interleaved with leftover data. Therefore, before you restructure your code to call &lt;B&gt;Cancel&lt;/B&gt; before &lt;B&gt;Close&lt;/B&gt;, test &lt;B&gt;Cancel&lt;/B&gt; to learn if it actually helps in your particular scenario and to learn if you really need the extra performance at the expense of readability.&lt;/P&gt;
&lt;BLOCKQUOTE class=dtBlock&gt;&lt;B&gt;Note&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/B&gt;If you need output parameters, do not call &lt;B&gt;Close&lt;/B&gt; until you have retrieved the output parameters. After you retrieve the output parameters, you can then call &lt;B&gt;Close&lt;/B&gt;.&lt;/BLOCKQUOTE&gt;
&lt;H3 class=dtH1&gt;Consider Using CommandBehavior.SequentialAccess with ExecuteReader&lt;/H3&gt;
&lt;P&gt;If you do not have to have random access to columns, use &lt;B&gt;CommandBehavior.SequentialAccess&lt;/B&gt; when you call the &lt;B&gt;ExecuteReader&lt;/B&gt; method of the &lt;B&gt;Command&lt;/B&gt; object.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Use GetOrdinal When Using an Index-Based Lookup&lt;/H3&gt;
&lt;P&gt;Using an index or ordinal-based lookup is faster than using string-based column names. However, using an index adds code maintenance overhead. Using an index requires you to change the index when the query column-order changes or when table columns are changed. Instead of hard coding the values, you can use &lt;B&gt;GetOrdinal&lt;/B&gt; to get the index as shown in the following code fragment.&lt;/P&gt;&lt;PRE class=code&gt;cmd.CommandText = "Select RegionDescription, RegionId from Region";
SqlDataReader dr = cmd.ExecuteReader();

int RegionId = dr.GetOrdinal("RegionId");
int RegionDescription = dr.GetOrdinal("RegionDescription");

while( dr.Read())
{
  Console.WriteLine(dr[RegionId] + " - " + dr[RegionDescription]);
}
&lt;/PRE&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic15&gt;&lt;/A&gt;DataSet&lt;/H2&gt;
&lt;P&gt;If you need to work with a disconnected, cached set of data, you usually create a &lt;B&gt;DataSet&lt;/B&gt; by using a &lt;B&gt;DataAdapter&lt;/B&gt;. To help optimize the performance of &lt;B&gt;DataSet&lt;/B&gt; objects, consider the following recommendations: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Reduce serialization&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use primary keys and Rows.Find for indexed searching&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use a DataView for repetitive non-primary key searches&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use the Optimistic concurrency model for datasets&lt;/B&gt;. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Reduce Serialization&lt;/H3&gt;
&lt;P&gt;&lt;B&gt;DataSet&lt;/B&gt; serialization is more efficiently implemented in .NET Framework version 1.1 than in version 1.0. However, &lt;B&gt;DataSet&lt;/B&gt; serialization often introduces performance bottlenecks. You can reduce the performance impact in a number of ways: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Use column name aliasing&lt;/B&gt;. The serialized data contains column names so that you can use column name aliasing to reduce the size of the serialized data. 
&lt;LI&gt;&lt;B&gt;Avoid serializing multiple versions of the same data&lt;/B&gt;. The &lt;B&gt;DataSet&lt;/B&gt; maintains the original data along with the changed values. If you do not need to serialize new and old values, call &lt;B&gt;AcceptChanges&lt;/B&gt; before you serialize a &lt;B&gt;DataSet&lt;/B&gt; to reset the internal buffers. 
&lt;LI&gt;&lt;B&gt;Reduce the number of DataTable objects that are serialized&lt;/B&gt;. If you do not need to send all the &lt;B&gt;DataTable&lt;/B&gt; objects contained in a &lt;B&gt;DataSet&lt;/B&gt;, consider copying the &lt;B&gt;DataTable&lt;/B&gt; objects you need to send into a separate &lt;B&gt;DataSet&lt;/B&gt;. &lt;/LI&gt;&lt;/UL&gt;
&lt;H4 class=dtH1&gt;More Information&lt;/H4&gt;
&lt;P&gt;For more information, see "&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/scalenethowto01.asp"&gt;How To: Improve Serialization Performance&lt;/A&gt;" in the "How To" section of this guide.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Use Primary Keys and Rows.Find for Indexed Searching&lt;/H3&gt;
&lt;P&gt;If you need to search a &lt;B&gt;DataSet&lt;/B&gt; by using a primary key, create the primary key on the &lt;B&gt;DataTable&lt;/B&gt;. This creates an index that the &lt;B&gt;Rows.Find&lt;/B&gt; method can use to quickly find the records that you want. Do not use &lt;B&gt;DataTable.Select &lt;/B&gt;because &lt;B&gt;DataTable.Select&lt;/B&gt; does not use indices.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Use a DataView for Repetitive Non-Primary Key Searches&lt;/H3&gt;
&lt;P&gt;If you need to repetitively search by using non-primary key data, create a &lt;B&gt;DataView&lt;/B&gt; that has a sort order. This creates an index that can be used to perform the search. This is best suited to repetitive searches because there is some cost to creating the index.&lt;/P&gt;
&lt;P&gt;The &lt;B&gt;DataView&lt;/B&gt; object exposes the &lt;B&gt;Find&lt;/B&gt; and &lt;B&gt;FindRows&lt;/B&gt; methods so that you can query the data in the underlying &lt;B&gt;DataTable&lt;/B&gt;. If you are only performing a single query, the processing that is required to create the index reduces the performance that is gained by using the index.&lt;/P&gt;
&lt;P&gt;When you create a &lt;B&gt;DataView&lt;/B&gt; object, use the &lt;B&gt;DataView&lt;/B&gt; constructor that takes the &lt;B&gt;Sort&lt;/B&gt;, &lt;B&gt;RowFilter&lt;/B&gt;, and &lt;B&gt;RowStateFilter&lt;/B&gt; values as constructor arguments along with the underlying &lt;B&gt;DataTable&lt;/B&gt;. Using the &lt;B&gt;DataView&lt;/B&gt; constructor ensures that the index is built once. If you create an empty &lt;B&gt;DataView&lt;/B&gt; and set the &lt;B&gt;Sort&lt;/B&gt;, &lt;B&gt;RowFilter,&lt;/B&gt; or &lt;B&gt;RowStateFilter&lt;/B&gt; properties afterwards, the index is built at least two times.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Use the Optimistic Concurrency Model for Datasets&lt;/H3&gt;
&lt;P&gt;There are two concurrency models that you can use when working with datasets in an environment that has multiple users. These two models are the pessimistic and optimistic models. When you read data and use the pessimistic model, locks are established and held until updates are made and the locks are released. Holding locks on server resources, in this case database tables, leads to contention issues. It is best to use granular locks for very short durations.&lt;/P&gt;
&lt;P&gt;The optimistic model does not lock the data when the data is read. The optimistic model locks the data just before the data is updated and releases the lock afterwards. There is less contention for data with the optimistic model, which is good for shared server scenarios; however, you should take into account the scenarios for managing the concurrency violations. A common technique you can use to manage concurrency violations is to implement a timestamp column or to verify against the original copy of data.&lt;/P&gt;
&lt;H4 class=dtH1&gt;More Information&lt;/H4&gt;
&lt;P&gt;For more information about how to implement optimistic concurrency solutions, see "Optimistic Concurrency" in &lt;I&gt;.NET Framework Developer's Guide&lt;/I&gt; at &lt;A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconOptimisticConcurrency.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconOptimisticConcurrency.asp&lt;/A&gt;.&lt;/P&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic16&gt;&lt;/A&gt;XML and DataSet Objects&lt;/H2&gt;
&lt;P&gt;Data and schema information maintained within &lt;B&gt;DataSet&lt;/B&gt; objects can be output as XML. Also, you can populate a &lt;B&gt;DataSet&lt;/B&gt; object from an XML data stream. If you use XML and DataSets, consider the following recommendations: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Do not infer schemas at run time&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Perform bulk updates and inserts by using OpenXML&lt;/B&gt;. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Do Not Infer Schemas at Run Time&lt;/H3&gt;
&lt;P&gt;Limit schema inference to design time. When you load a &lt;B&gt;DataSet&lt;/B&gt;, ensure that your schema is not inferred, which can happen by default. The inference process is costly. To ensure that your existing schema is used and that no schema is inferred, pass &lt;B&gt;XmlReadMode.IgnoreSchema&lt;/B&gt; to the &lt;B&gt;ReadXml&lt;/B&gt; method.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Perform Bulk Updates and Inserts by Using OpenXML&lt;/H3&gt;
&lt;P&gt;Different .NET Framework data providers enable you to do bulk updates and inserts by using the &lt;B&gt;OpenXML&lt;/B&gt; method. You can use &lt;B&gt;OpenXML&lt;/B&gt; to minimize SQL Server database calls, because you can use the &lt;B&gt;OpenXML&lt;/B&gt; function to insert multiple rows of data in a single database call. &lt;B&gt;OpenXML&lt;/B&gt; enables you to effectively package data together in a single call as XML, map it to a rowset view, and execute all of the inserts within the same database call. This helps reduce calls and resource utilization. The following code fragment shows you how to use &lt;B&gt;OpenXML&lt;/B&gt; for updates and inserts.&lt;/P&gt;&lt;PRE class=code&gt;--This code UPDATES data.
UPDATE Employee
SET
   Employee.FirstName = XMLEmployee.FirstName,
   Employee.LastName = XMLEmployee.LastName
   FROM OPENXML(@hDoc, 'NewDataSet/Employee')
       WITH (EmployeeId Integer, FirstName varchar(100),  LastName varchar(100))  XMLEmployee
WHERE    Employee.EmployeeId = XMLEmployee.EmployeeId

--This code inserts new data.
Insert Into Employee
SELECT EmployeeId, FirstName, LastName
       FROM  OPENXML (@hdoc, '/NewDataSet/Employee',1)
WITH (EmployeeId Integer, FirstName varchar(100),  LastName varchar(100))  XMLEmployee
Where XMLEmployee.EmployeeId Not IN (Select EmployeeID from Employee)&lt;/PRE&gt;
&lt;H4 class=dtH1&gt;More Information&lt;/H4&gt;
&lt;P&gt;For a complete code sample that shows how to use the OpenXML method, see Knowledge Base article 315968, "HOW TO: Perform Bulk Updates and Inserts Using OpenXML with .NET Providers in Visual C# .NET," at &lt;A href="http://support.microsoft.com/default.aspx?scid=kb;en-us;315968"&gt;http://support.microsoft.com/default.aspx?scid=kb;en-us;315968&lt;/A&gt;&lt;U&gt;.&lt;/U&gt;&lt;/P&gt;
&lt;P&gt;For more information about XML and DataSet objects, see "Employing XML in the .NET Framework" in &lt;I&gt;.NET Framework Developer's Guide &lt;/I&gt;at &lt;A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconemployingxmlinnetframework.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconemployingxmlinnetframework.asp&lt;/A&gt;.&lt;/P&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic17&gt;&lt;/A&gt;Typed DataSets&lt;/H2&gt;
&lt;P&gt;A typed &lt;B&gt;DataSet&lt;/B&gt; is a custom object that derives from the &lt;B&gt;DataSet&lt;/B&gt; base class. It supports typed data access through an exposed set of properties specific to the encapsulated data.&lt;/P&gt;
&lt;P&gt;Use typed &lt;B&gt;DataSet&lt;/B&gt; objects to avoid late-bound field access. The strongly typed accessors are provided by the typed &lt;B&gt;DataSet&lt;/B&gt; and are faster because they eliminate column or table name lookups in a collection.&lt;/P&gt;
&lt;P&gt;In addition to run-time performance benefits, typed datasets provide strong type checking and IntelliSense&amp;#174; by using custom field names at design time.&lt;/P&gt;
&lt;H3 class=dtH1&gt;More Information&lt;/H3&gt;
&lt;P&gt;For more information about typed DataSet objects, see the following Knowledge Base articles: 
&lt;UL type=disc&gt;
&lt;LI&gt;320714, "HOW TO: Create and Use a Typed DataSet by Using Visual C# .NET," at &lt;A href="http://support.microsoft.com/default.aspx?scid=kb;en-us;320714"&gt;http://support.microsoft.com/default.aspx?scid=kb;en-us;320714&lt;/A&gt; 
&lt;LI&gt;313486, "INFO: Roadmap for Visual Database Tools and Typed DataSets," at &lt;A href="http://support.microsoft.com/default.aspx?scid=kb;en-us;313486"&gt;http://support.microsoft.com/default.aspx?scid=kb;en-us;313486&lt;/A&gt; &lt;/LI&gt;&lt;/UL&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic18&gt;&lt;/A&gt;Types&lt;/H2&gt;
&lt;P&gt;When you access a data source by using stored procedures or dynamic SQL commands, make sure that you specify the precise data type for the input and output parameters. By specifying the precise database type, you can help performance in the following ways: 
&lt;UL type=disc&gt;
&lt;LI&gt;You help prevent internal type conversions from being performed by the data provider. Internal type conversions can lead to loss of precision. 
&lt;LI&gt;You help reduce round trips that the data provider might make to the database to discover type information at run time. 
&lt;LI&gt;You enable the data provider to perform type checks at the client and fail early with type exceptions. This helps avoid unnecessary round trips to the server. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Avoid Unnecessary Type Conversions&lt;/H3&gt;
&lt;P&gt;Type conversions can occur when you pass parameters to and from stored procedures or other SQL statements. To avoid type conversions, make sure that you: 
&lt;UL type=disc&gt;
&lt;LI&gt;Set the provider-specific type property of each &lt;B&gt;Parameter&lt;/B&gt; object. 
&lt;LI&gt;Pass a consistent object type when you set the &lt;B&gt;Value&lt;/B&gt; property&lt;B&gt; &lt;/B&gt;of the&lt;B&gt; Parameter&lt;/B&gt; object. &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;For example, the .NET Framework Data Provider for SQL Server defines the &lt;B&gt;System.Data.SqlTypes&lt;/B&gt; namespace. This namespace provides classes that represent the native data types in SQL Server. This namespace also includes the &lt;B&gt;SqlDbType&lt;/B&gt; enumeration, which you use to specify the precise type of a parameter that is passed through a &lt;B&gt;SqlParameter&lt;/B&gt; object.&lt;/P&gt;
&lt;P&gt;The following code demonstrates how to avoid type conversions for the .NET Framework Data Provider for SQL Server.&lt;/P&gt;&lt;PRE class=code&gt;// Set the provider-specific type for the parameter.
SqlParameter param = new SqlParameter("@Name", SqlDbType.NVarChar, 20);
// Use the right provider-specific type. In this case use SqlString, which
// corresponds to SqlDbType.NVarChar
param.Value = new SqlString("Frederick Smith");
&lt;/PRE&gt;
&lt;P&gt;The type namespaces and type enumerations for each data provider are summarized in Table 12.1.&lt;/P&gt;
&lt;P class=label&gt;&lt;B&gt;Table 12.1: Database Provider Types&lt;/B&gt;&lt;/P&gt;
&lt;TABLE class=data&gt;
&lt;TBODY&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=label width="33%"&gt;&lt;B&gt;Provider&lt;/B&gt;&lt;/TD&gt;
&lt;TD class=label width="33%"&gt;&lt;B&gt;Type namespace&lt;/B&gt;&lt;/TD&gt;
&lt;TD class=label width="34%"&gt;&lt;B&gt;Type enumeration&lt;/B&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="33%"&gt;SQL Server&lt;/TD&gt;
&lt;TD class=data width="33%"&gt;System.Data.SqlTypes&lt;/TD&gt;
&lt;TD class=data width="34%"&gt;SqlDbType&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="33%"&gt;OLE DB&lt;/TD&gt;
&lt;TD class=data width="33%"&gt;System.Data.OleDb&lt;/TD&gt;
&lt;TD class=data width="34%"&gt;OleDbType&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="33%"&gt;ODBC&lt;/TD&gt;
&lt;TD class=data width="33%"&gt;System.Data.OdbcType&lt;/TD&gt;
&lt;TD class=data width="34%"&gt;OdbcType&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="33%"&gt;Oracle&lt;/TD&gt;
&lt;TD class=data width="33%"&gt;System.Data.OracleClient&lt;/TD&gt;
&lt;TD class=data width="34%"&gt;OracleType&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic19&gt;&lt;/A&gt;Exception Management&lt;/H2&gt;
&lt;P&gt;ADO.NET errors that are propagated through &lt;B&gt;SqlException&lt;/B&gt; or &lt;B&gt;OleDbException&lt;/B&gt; objects use custom error handling for specific data access code. Consider the following guidelines for exception handling in ADO.NET data access code: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Use the ConnectionState property&lt;/B&gt;. Avoid relying on an error handler to detect connection state availability. When you can, use the &lt;B&gt;ConnectionState.Open&lt;/B&gt; or &lt;B&gt;ConnectionState.Close&lt;/B&gt; method to check the state before use. 
&lt;LI&gt;&lt;B&gt;Use try/finally to clean up resources&lt;/B&gt;.&lt;B&gt; &lt;/B&gt;Use &lt;B&gt;try&lt;/B&gt;/&lt;B&gt;finally&lt;/B&gt; more often than &lt;B&gt;try&lt;/B&gt;/&lt;B&gt;catch&lt;/B&gt;/&lt;B&gt;finally&lt;/B&gt;. Using &lt;B&gt;finally&lt;/B&gt; gives you the option to close the connection, even if an exception occurs. If you develop in C#, the &lt;B&gt;using&lt;/B&gt; statement provides this functionality with code that is easy to maintain, as shown in the following code fragment. &lt;PRE class=code&gt;using( SqlConnection conn = new SqlConnection(
      "server=(local);Integrated Security=SSPI;database=Northwind") )
{ . . . }
&lt;/PRE&gt;
&lt;LI&gt;&lt;B&gt;Use specific handlers to catch specific exceptions&lt;/B&gt;. If you know that there are scenarios where specific errors could possibly occur, use specific handlers. For example, if you want to know if a concurrency violation occurs when multiple updates are occurring, look for exceptions of type &lt;B&gt;DBConcurrencyException&lt;/B&gt;. The specific exception classes &lt;B&gt;SqlException&lt;/B&gt; and &lt;B&gt;OleDbException&lt;/B&gt; provide a detailed message when errors occur. Use this message to log the details. Ensure that specific exceptions precede generic handlers, as shown in the following code fragment. &lt;PRE class=code&gt;try
{ ...
}
catch (SqlException sqlex) // specific handler
{ ...
}
catch (Exception ex) // Generic handler
{ ...
}
&lt;/PRE&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;For more information about exception handling guidelines specific to performance, see Chapter 5, "Improving Managed Code Performance."&lt;/P&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic20&gt;&lt;/A&gt;Transactions&lt;/H2&gt;
&lt;P&gt;Transactions are important for ensuring data integrity but come at an operational cost. Selecting the right transaction management mechanism for your application can significantly improve scalability and performance. Key considerations include the type and quantity of resources involved and the isolation level required for the transactions. When you determine how you should manage transactions in your system, consider the following recommendations: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Use SQL transactions for server-controlled transactions on a single data store&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use ADO.NET transactions for client-controlled transactions on a single data store&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use DTC for transactions that span multiple data stores&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Keep transactions as short as possible&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use the appropriate isolation level&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Avoid code that can lead to deadlock&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Set the connection string Enlist property to false&lt;/B&gt;. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Use SQL Transactions for Server-Controlled Transactions on a Single Data Store&lt;/H3&gt;
&lt;P&gt;If you need to write to a single data store, and if you can complete the operation in a single call to the database, use the transaction control provided by the SQL language on your database server. The transaction runs close to the data and reduces the cost of the transaction. Running the transaction close to the data also permits database administrators to tune the operation without changing the deployment of your application code. The following code fragment shows a simple T-SQL transaction performed in a stored procedure.&lt;/P&gt;&lt;PRE class=code&gt;BEGIN TRAN

UPDATE Orders SET Freight=@Freight Where OrderID=@OrderID
UPDATE [Order Details] SET Quantity=@Quantity Where OrderID=@OrderID

IF (@@ERROR &amp;gt; 0)
ROLLBACK TRANSACTION
ELSE
COMMIT TRANSACTION
&lt;/PRE&gt;
&lt;BLOCKQUOTE class=dtBlock&gt;&lt;B&gt;Note&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/B&gt;If you need to control a transaction across multiple calls to a single data store, use ADO.NET manual transactions.&lt;/BLOCKQUOTE&gt;
&lt;H3 class=dtH1&gt;Use ADO.NET Transactions for Client-Controlled Transactions on a Single Data Store&lt;/H3&gt;
&lt;P&gt;If you need to make multiple calls to a single data store participate in a transaction, use ADO.NET manual transactions. The .NET Data Provider for SQL Server and the .NET Data Provider for Oracle use the appropriate transaction language to enforce transactions on all subsequent SQL commands.&lt;/P&gt;
&lt;P&gt;If you use SQL Profiler to monitor your use of ADO.NET manual transactions, you see that BEGIN TRAN, COMMIT TRAN, or ROLLBACK TRAN is run against the data store on your behalf by the provider. This enables you to control the transaction from your .NET Framework code and to maintain performance at a level that is similar to SQL transactions. The following code fragment shows how to use ADO.NET transactions.&lt;/P&gt;&lt;PRE class=code&gt;SqlConnection conn = new SqlConnection(connString);
SqlTransaction trans = conn.BeginTransaction();
try
{
  SqlCommand cmd = new SqlCommand("MyWriteProc",conn, trans);
  cmd.CommandType = CommandType.StoredProcedure;
  cmd.Parameters.Add(....
  &amp;#8230;  // additional transactioned writes to database
  trans.Commit();
}
catch
{
  trans.Rollback();
}
&lt;/PRE&gt;
&lt;P&gt;When you use ADO.NET manual transactions, you can set the desired isolation level on the &lt;B&gt;BeginTransacion&lt;/B&gt; method as shown in the following code fragment.&lt;/P&gt;&lt;PRE class=code&gt;SqlConnection conn = new SqlConnection(connString);
SqlTransaction trans = conn.BeginTransaction(IsolationLevel.ReadCommitted);
&lt;/PRE&gt;
&lt;P&gt;&lt;B&gt;More Information&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;For a more information about isolation levels, see "Use the Appropriate Isolation Level" later in this chapter.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Use DTC for Transactions That Span Multiple Data Stores&lt;/H3&gt;
&lt;P&gt;Enterprise Services uses the Microsoft Distributed Transaction Coordinator (DTC) to enforce transactions. If you have a transaction that spans multiple data stores or resource manager types, it is best to use Enterprise Services to enlist the data sources in a distributed transaction. Using Enterprise Services to enlist the data sources in this scenario is simple to configure.&lt;/P&gt;
&lt;P&gt;The DTC performs the inter-data source communication and ensures that either all the data is committed or that none of the data is committed. This action creates an operational cost. If you do not have transactions that span multiple data sources, use SQL or ADO.NET manual transactions because they perform better.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Keep Transactions as Short as Possible&lt;/H3&gt;
&lt;P&gt;Design your code to keep transactions as short as possible to help minimize lock contention and to increase throughput. Avoid selecting data or performing long operations in the middle of a transaction.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Use the Appropriate Isolation Level&lt;/H3&gt;
&lt;P&gt;Resource managers such as SQL Server and other database systems support various levels of isolation for transactions. Isolation shields operations from the effect of other concurrent transactions. Most resource managers support the four isolation levels shown in Table 12.2. The isolation level determines the types of operation that can occur. The types of operation that can occur include dirty reads, nonrepeatable reads, or phantoms.&lt;/P&gt;
&lt;P class=label&gt;&lt;B&gt;Table 12.2: Isolation Levels&lt;/B&gt;&lt;/P&gt;
&lt;TABLE class=data&gt;
&lt;TBODY&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=label width="25%"&gt;&lt;B&gt;Isolation level&lt;/B&gt;&lt;/TD&gt;
&lt;TD class=label width="25%"&gt;&lt;B&gt;Dirty reads&lt;/B&gt;&lt;/TD&gt;
&lt;TD class=label width="25%"&gt;&lt;B&gt;Nonrepeatable reads&lt;/B&gt;&lt;/TD&gt;
&lt;TD class=label width="25%"&gt;&lt;B&gt;Phantoms&lt;/B&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="25%"&gt;Read uncommitted&lt;/TD&gt;
&lt;TD class=data width="25%"&gt;Yes&lt;/TD&gt;
&lt;TD class=data width="25%"&gt;Yes&lt;/TD&gt;
&lt;TD class=data width="25%"&gt;Yes&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="25%"&gt;Read committed&lt;/TD&gt;
&lt;TD class=data width="25%"&gt;No&lt;/TD&gt;
&lt;TD class=data width="25%"&gt;Yes&lt;/TD&gt;
&lt;TD class=data width="25%"&gt;Yes&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="25%"&gt;Repeatable read&lt;/TD&gt;
&lt;TD class=data width="25%"&gt;No&lt;/TD&gt;
&lt;TD class=data width="25%"&gt;No&lt;/TD&gt;
&lt;TD class=data width="25%"&gt;Yes&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="25%"&gt;Serializable&lt;/TD&gt;
&lt;TD class=data width="25%"&gt;No&lt;/TD&gt;
&lt;TD class=data width="25%"&gt;No&lt;/TD&gt;
&lt;TD class=data width="25%"&gt;No&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;
&lt;P&gt;The highest isolation level, serializable, protects a transaction completely from the effects of other concurrent transactions. This is the most expensive isolation level in terms of server resources and performance. By selecting a lower level of isolation and writing the code for your transactions to deal with the effects of other concurrent transactions, you can improve performance and scalability. However, this approach may come at the expense of more complex code.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Avoid Code That Can Lead to Deadlock&lt;/H3&gt;
&lt;P&gt;Consider the following general guidelines when you use transactions so that you can avoid causing deadlocks: 
&lt;UL type=disc&gt;
&lt;LI&gt;Always access tables in the same order across transactions in your application. The likelihood of a deadlock increases when you access tables in a different order each time you access them. 
&lt;LI&gt;Keep transactions as short as possible. Do not make blocking or long-running calls from a transaction. Keep the duration of the transactions short. One approach is to run transactions close to the data source. For example, run a transaction from a stored procedure instead of running the transaction from a different computer. 
&lt;LI&gt;Choose a level of isolation that balances concurrency and data integrity. The highest isolation level, serializable, reduces concurrency and provides the highest level of data integrity. The lowest isolation level, read uncommitted, gives the opposite result. For more information, see "Use the Appropriate Isolation Level" earlier in this chapter. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Set the Connection String Enlist Property to False&lt;/H3&gt;
&lt;P&gt;A pooled transactional object must enlist its connection into the current transaction manually. To enable it to do so, you must disable automatic transaction enlistment by setting the connection string &lt;B&gt;Enlist&lt;/B&gt; property to &lt;B&gt;False&lt;/B&gt;.&lt;/P&gt;
&lt;BLOCKQUOTE class=dtBlock&gt;&lt;B&gt;Note&lt;/B&gt; This applies to a &lt;B&gt;SqlConnection&lt;/B&gt;. For an &lt;B&gt;OleDbConnection&lt;/B&gt;, you need to set &lt;B&gt;OLE&lt;/B&gt; &lt;B&gt;DB Services=-7&lt;/B&gt; as a connection string parameter.&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Pooled components that maintain database connections might be used in different transactions by separate clients. A pooled transactional object must be able to determine if it is activated in a new transaction that is different from the last time it was activated.&lt;/P&gt;
&lt;P&gt;Each time a pooled transactional object is activated, it should check for the presence of a COM+ transaction in its context by examining &lt;B&gt;ContextUtil.Transaction&lt;/B&gt;. If a transaction is present and the connection is not already enlisted, the object should enlist its connection manually by calling the &lt;B&gt;EnlistDistributedTransaction&lt;/B&gt; method of the &lt;B&gt;Connection&lt;/B&gt; object.&lt;/P&gt;
&lt;H3 class=dtH1&gt;More Information&lt;/H3&gt;
&lt;P&gt;For more information about transaction options and how to analyze transaction performance, see the following resources on MSDN: 
&lt;UL type=disc&gt;
&lt;LI&gt;"Performance Comparison: Transaction Control" at &lt;A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/bdadotnetarch13.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/bdadotnetarch13.asp&lt;/A&gt; 
&lt;LI&gt;"Implementing Database Transactions with Microsoft .NET" at &lt;A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/psent.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/psent.asp&lt;/A&gt; &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;For more information about enlisting a pooled object in a distributed transaction, see "Enlisting in a Distributed Transaction" in the &lt;I&gt;.NET Framework Developer's Guide&lt;/I&gt; at &lt;A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconenlistingindistributedtransaction.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconenlistingindistributedtransaction.asp&lt;/A&gt;.&lt;/P&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic21&gt;&lt;/A&gt;Binary Large Objects&lt;/H2&gt;
&lt;P&gt;A binary large object (BLOB) is a binary resource such as an image, a sound or video clip, or a document. Storing BLOBs in a database can cause significant resource pressure. For example, large BLOBs can consume large amounts of memory, CPU, and networking resources on both the client and the server.&lt;/P&gt;
&lt;P&gt;You can choose to handle BLOBs as a whole or handle them in chunks.&lt;/P&gt;
&lt;P&gt;Handling BLOBs as a whole is useful when the BLOB is not very large in size, and you require the complete BLOB to be in memory before you perform the operation. This approach tends to put excessive memory pressure on the server as well as on network bandwidth.&lt;/P&gt;
&lt;P&gt;Compared to handling BLOBs as a whole, chunking does cause more round trips, but chunking creates less load on the server and reduces network bandwidth use. The network bandwidth is not excessively consumed because you transfer the data in chunks rather than passing the BLOB all at one time. Therefore, the server only has to take care of the immediate buffer passed to it. The server can either store the buffer to the disk or redirect it as an output stream to the client.&lt;/P&gt;
&lt;P&gt;ADO.NET data providers do not provide the &lt;B&gt;GetChunk&lt;/B&gt; and &lt;B&gt;AppendChunk&lt;/B&gt; methods in the same way that Data Access Objects (DAO) and ActiveX Data Objects (ADO) do with &lt;B&gt;Recordset&lt;/B&gt; objects. However, this section describes the alternate options that exist.&lt;/P&gt;
&lt;P&gt;Consider the following when you are working with BLOBs: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Use CommandBehavior.SequentialAccess and GetBytes to read data&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use READTEXT to read from SQL Server 2000&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use OracleLob.Read to read from Oracle databases&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use UpdateText to write to SQL Server databases&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Use OracleLob.Write to write to Oracle databases&lt;/B&gt;. 
&lt;LI&gt;&lt;B&gt;Avoid moving binary large objects repeatedly&lt;/B&gt;. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Use CommandBehavior.SequentialAccess and GetBytes to Read Data&lt;/H3&gt;
&lt;P&gt;The default behavior of the &lt;B&gt;DataReader&lt;/B&gt; is to read an entire row into memory. All columns are accessible in any order until the next row is read. &lt;/P&gt;
&lt;P&gt;If you retrieve large BLOBs, reading the whole BLOB into memory may cause excessive memory consumption. Using &lt;B&gt;CommandBehavior.SequentialAccess&lt;/B&gt; enables you to stream the data or to send the data in chunks from the column containing the BLOB by using the &lt;B&gt;GetBytes&lt;/B&gt;, &lt;B&gt;GetChars,&lt;/B&gt; or &lt;B&gt;GetString&lt;/B&gt; methods.&lt;/P&gt;
&lt;P&gt;The following code fragment shows how to use the &lt;B&gt;SequentialAccess &lt;/B&gt;and&lt;B&gt; GetBytes &lt;/B&gt;methods.&lt;/P&gt;&lt;PRE class=code&gt;// Allocate a buffer to hold a BLOB chunk.
int bufferSize = 100;  // the size of the buffer to hold interim chunks of the BLOB
byte[] outbyte = new byte[bufferSize];  // The buffer to hold the BLOB

SqlDataReader myReader = empCmd.ExecuteReader(CommandBehavior.SequentialAccess);
while (myReader.Read())
{
  // The BLOB data is in column two. Must get the first column
  // before the BLOB data.
   empID = myReader.GetInt32(0); // First column
  // Read the bytes into outbyte[] and retain the number of bytes returned.
   retval = myReader.GetBytes(1, startIndex, outbyte, 0, bufferSize);
  // Continue reading and writing while there are bytes beyond the
  // Size of the buffer.
  while (retval == bufferSize)
  {
    // Write data to a file or to a Web page (omitted for brevity).
    . . .
    // Reposition the start index to the end of the last buffer
    // and fill the buffer.
    startIndex += bufferSize;
    retval = myReader.GetBytes(1, startIndex, outbyte, 0, bufferSize);
   }
}
&lt;/PRE&gt;
&lt;BLOCKQUOTE class=dtBlock&gt;&lt;B&gt;Note&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/B&gt;When you use &lt;B&gt;CommandBehavior.SequentialAccess&lt;/B&gt;, you must retrieve columns in sequence. For example, if you have three columns, and the BLOB data is in the third column, you must retrieve the data from the first and second columns, before you retrieve the data from the third column.&lt;/BLOCKQUOTE&gt;
&lt;H4 class=dtH1&gt;More Information&lt;/H4&gt;
&lt;P&gt;For more information, see "Obtaining BLOB Values from a Database" in &lt;I&gt;.NET Framework Developer's Guide &lt;/I&gt;at &lt;A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconobtainingblobvaluesfromdatabase.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconobtainingblobvaluesfromdatabase.asp&lt;/A&gt;.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Use READTEXT to Read from SQL Server 2000&lt;/H3&gt;
&lt;P&gt;The READTEXT command reads &lt;B&gt;text&lt;/B&gt;, &lt;B&gt;ntext&lt;/B&gt;, or &lt;B&gt;image&lt;/B&gt; values from a &lt;B&gt;text&lt;/B&gt;,&lt;B&gt; ntext&lt;/B&gt;, or &lt;B&gt;image&lt;/B&gt; column. The READTEXT command starts reading from a specified offset and reads the specified number of bytes. This command is available in SQL Server 2000 and later. This command enables you to read data in chunks by sending a fixed set of bytes over the network for each iteration. The following are the steps you must follow to use the READTEXT command: 
&lt;OL type=1&gt;
&lt;LI&gt;Obtain a pointer to the BLOB by using the TEXTPTR command. 
&lt;LI&gt;Read the BLOB, by using the READTEXT command, in the required chunk size, with the help of the pointer that you obtained in step 1. 
&lt;LI&gt;Send the data to the client. 
&lt;LI&gt;Read the data on the client, and then store it in a buffer or a stream. &lt;/LI&gt;&lt;/OL&gt;
&lt;P&gt;The following code fragment shows how to use the READTEXT command.&lt;/P&gt;&lt;PRE class=code&gt;int BUFFER_LENGTH  = 32768; // chunk size
// Obtain a pointer to the BLOB using TEXTPTR.
SqlCommand cmdGetPointer = new SqlCommand(
    "SELECT @Pointer=TEXTPTR(Picture), @Length=DataLength(Picture)" +
    "FROM Categories WHERE CategoryName='Test'", conn);

// Set up the parameters.
SqlParameter PointerOutParam = cmdGetPointer.Parameters.Add("@Pointer", SqlDbType.VarBinary, 100);

// Run the query.
// Set up the READTEXT command to read the BLOB by passing the following
// parameters: @Pointer &amp;#8211; pointer to blob, @Offset &amp;#8211; number of bytes to
// skip before starting the read, @Size &amp;#8211; number of bytes to read.
SqlCommand cmdReadBinary = new SqlCommand(
     "READTEXT Categories.Picture @Pointer @Offset @Size HOLDLOCK", conn);
// Set up the parameters for the command.
SqlParameter SizeParam  = cmdReadBinary.Parameters.Add("@Size", SqlDbType.Int);
SqlDataReader dr;
int Offset= 0;
Byte []Buffer = new Byte[BUFFER_LENGTH ];
// Read buffer full of data.
do {
  // Add code for calculating the buffer size - may be less than
  // BUFFER  LENGTH  for the last block.
  dr = cmdReadBinary.ExecuteReader(CommandBehavior.SingleResult);
  dr.Read();
  dr.GetBytes(PictureCol, 0, Buffer, 0,  System.Convert.ToInt32(SizeParam.Value));
  Offset += System.Convert.ToInt32(SizeParam.Value);
  OffsetParam.Value = Offset;
} while( //Check for the offset until it reaches the maximum size.);
&lt;/PRE&gt;
&lt;H4 class=dtH1&gt;More Information&lt;/H4&gt;
&lt;P&gt;For more information about the READTEXT command, see Knowledge Base article 317043, "HOW TO: Read and Write a File to and from a BLOB Column by Using Chunking in ADO.NET and Visual C# .NET," at &lt;A href="http://support.microsoft.com/default.aspx?scid=kb;en-us;317043"&gt;http://support.microsoft.com/default.aspx?scid=kb;en-us;317043&lt;/A&gt;.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Use OracleLob.Read to Read from Oracle Databases&lt;/H3&gt;
&lt;P&gt;To read BLOBs from an Oracle database, use the .NET Framework Data Provider for Oracle. This data provider provides the &lt;B&gt;System.Data.OracleClient.OracleLob &lt;/B&gt;class that can read BLOBs. The following code fragment shows how the &lt;B&gt;OracleLob.Read&lt;/B&gt; method enables you to read the data in chunks.&lt;/P&gt;&lt;PRE class=code&gt;byte[] buffer = new byte[100];
  while((actual = blob.Read(buffer, 0/*buffer offset*/,
         buffer.Length/*count*/)) &amp;gt;0)
{ //write the buffer to some stream
}&lt;/PRE&gt;
&lt;H4 class=dtH1&gt;More Information&lt;/H4&gt;
&lt;P&gt;For more information about OracleLob.Read, see "OracleLob.Read Method" in &lt;I&gt;.NET Framework Class Library&lt;/I&gt; at &lt;A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemdataoracleclientoraclelobclassreadtopic.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemdataoracleclientoraclelobclassreadtopic.asp&lt;/A&gt;.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Use UpdateText to Write to SQL Server Databases&lt;/H3&gt;
&lt;P&gt;If you are using SQL Server, you can use the &lt;B&gt;UpdateText&lt;/B&gt; function to write the data in chunks, as shown in the following code fragment.&lt;/P&gt;&lt;PRE class=code&gt;int BUFFER_LENGTH = 32768; // Chunk size.
// Set the existing BLOB to null and
// Obtain a pointer to the BLOB using TEXTPTR
SqlCommand cmdGetPointer = new SqlCommand(
 "SET NOCOUNT ON;UPDATE Categories SET Picture = 0x0 WHERE CategoryName='Test';" +
 "SELECT @Pointer=TEXTPTR(Picture) FROM Categories WHERE CategoryName='Test'",
  cn);

// Set up the parameters.
// Run the query.

// Set up the UPDATETEXT command to read the BLOB by passing the following
// parameters: @Pointer &amp;#8211; pointer to blob, @Offset &amp;#8211; number of bytes to
// skip before starting the read, @Size &amp;#8211; number of bytes to read.
SqlCommand cmdUploadBinary = new SqlCommand(
  "UPDATETEXT Categories.Picture @Pointer @Offset @Delete WITH LOG @Bytes", cn);
// Set up the parameters.
// Read buffer full of data and then run the UPDATETEXT statement.
Byte [] Buffer = br.ReadBytes(BUFFER_LENGTH);
while(Buffer.Length &amp;gt; 0)
{
  PointerParam.Value = PointerOutParam.Value;
  BytesParam.Value = Buffer;
  cmdUploadBinary.ExecuteNonQuery();
  DeleteParam.Value = 0; //Do not delete any other data.
  Offset += Buffer.Length;
  OffsetParam.Value = Offset;
  Buffer = br.ReadBytes(BUFFER_LENGTH);
}&lt;/PRE&gt;
&lt;H4 class=dtH1&gt;More Information&lt;/H4&gt;
&lt;P&gt;For more information about writing BLOB data to SQL Server, see "Conserving Resources When Writing BLOB Values to SQL Server" in .&lt;I&gt;NET Framework Developer's Guide &lt;/I&gt;at &lt;A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconconservingresourceswhenwritingblobvaluestosqlserver.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconconservingresourceswhenwritingblobvaluestosqlserver.asp&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;Or, see Knowledge Base article 317043, "HOW TO: Read and Write a File to and from a BLOB Column by Using Chunking in ADO.NET and Visual C# .NET," at &lt;A href="http://support.microsoft.com/default.aspx?scid=kb;en-us;317043"&gt;http://support.microsoft.com/default.aspx?scid=kb;en-us;317043&lt;/A&gt;.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Use OracleLob.Write to Write to Oracle Databases&lt;/H3&gt;
&lt;P&gt;You can write BLOBs to an Oracle database by using the .NET Framework data provider for Oracle. This data provider permits the &lt;B&gt;System.Data.OracleClient.OracleLob &lt;/B&gt;class to write BLOBs. The &lt;B&gt;OracleLob.Write&lt;/B&gt; method enables you to write data in chunks.&lt;/P&gt;
&lt;H4 class=dtH1&gt;More Information&lt;/H4&gt;
&lt;P&gt;For more information, see "OracleLob.Write Method," in &lt;I&gt;.NET Framework Class Library&lt;/I&gt; at &lt;A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemdataoracleclientoraclelobclasswritetopic.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemdataoracleclientoraclelobclasswritetopic.asp&lt;/A&gt;.&lt;/P&gt;
&lt;H3 class=dtH1&gt;Avoid Moving Binary Large Objects Repeatedly&lt;/H3&gt;
&lt;P&gt;Avoid moving BLOB data more than one time. For example, if you build a Web application that serves images, store the images on the file system and the file names in the database instead of storing the images as BLOBs in the database.&lt;/P&gt;
&lt;P&gt;Storing the images as BLOBs in the database means that you must read the BLOB from the database to the Web server and then send the image from the Web server to the browser. Reading the file name from the database and having the Web server send the image to the browser reduces the load on the database server. It also reduces the data that is sent between the database and the Web server. This can significantly affect performance and scalability.&lt;/P&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic22&gt;&lt;/A&gt;Paging Records&lt;/H2&gt;
&lt;P&gt;Paging records is a common application scenario. The records that you need to page through can often be based on user input. For example, they can be based on a search keyword entered through a search screen. Or, the records can be common to all users. For example, a product catalogue is a record that is common to all users.&lt;/P&gt;
&lt;P&gt;Paging costs can be divided into the following stages: 
&lt;UL type=disc&gt;
&lt;LI&gt;Processing cost at the database. This includes processor and memory use, and disk I/O. 
&lt;LI&gt;Network cost for the amount of data sent across the network. 
&lt;LI&gt;Processing cost at the client. This includes the memory required to store records, and processor use for processing the records. &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;Paging records may be expensive for the following reasons: 
&lt;UL type=disc&gt;
&lt;LI&gt;Inefficient queries may increase the processing cost in all the stages mentioned in this section. The database has to process an increased number of rows, more data than is required is sent over the network, and the client has to process additional records to show the relevant ones to the user. 
&lt;LI&gt;Inappropriate caching of data to be paged. Some of the paging implementations require the client to cache data and then page through it. These solutions can lead to excessive memory pressure if the cache is maintained on a per-user basis. &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;There are two basic approaches to paging: 
&lt;UL type=disc&gt;
&lt;LI&gt;You can return the whole result set from the database to the client. The client caches the result set and then displays the most relevant results to the user by using the correct filtering mechanism. 
&lt;LI&gt;You can have the database assume the additional role of a filter by making the database return only the most relevant result set to the client. &lt;/LI&gt;&lt;/UL&gt;
&lt;H4 class=dtH1&gt;More Information&lt;/H4&gt;
&lt;P&gt;For more information about how to choose and implement the best solution for your scenario, see "&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/scalenethowto05.asp"&gt;How To: Page Records in .NET Applications&lt;/A&gt;" in the "How To" section of this guide.&lt;/P&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic23&gt;&lt;/A&gt;Analyzing Performance and Scalability of Data Access&lt;/H2&gt;
&lt;P&gt;When you evaluate the performance and scalability of your data access decisions, you should examine the impact that your code has on the server, on the network, and on the client. A good data access solution uses server resources in a timely and efficient manner, transports only the data that is required, and permits the client to quickly consume the data and then release resources.&lt;/P&gt;
&lt;P&gt;Start by running simple logical operation tests, and then examine the key metrics and related questions listed in Table 12.3. Review the results, and then use the information in the table to improve performance and scalability.&lt;/P&gt;
&lt;P class=label&gt;&lt;B&gt;Table 12.3: Metrics for Analyzing Data Access Performance&lt;/B&gt;&lt;/P&gt;
&lt;TABLE class=data&gt;
&lt;TBODY&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=label width="27%"&gt;&lt;B&gt;Metric&lt;/B&gt;&lt;/TD&gt;
&lt;TD class=label width="73%"&gt;&lt;B&gt;Questions&lt;/B&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="27%"&gt;Run time&lt;/TD&gt;
&lt;TD class=data width="73%"&gt;How long did the operation take on the server? 
&lt;P&gt;How long did the operation take on the client?&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="27%"&gt;Network trips&lt;/TD&gt;
&lt;TD class=data width="73%"&gt;How many network trips were required to complete the operation? 
&lt;P&gt;Is there a way to reduce or consolidate the trips?&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="27%"&gt;Index use&lt;/TD&gt;
&lt;TD class=data width="73%"&gt;Did the operation use indexes? 
&lt;P&gt;Was the index use efficient?&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="27%"&gt;Records processed/retrieved&lt;/TD&gt;
&lt;TD class=data width="73%"&gt;Did the operation process more records than it returned? 
&lt;P&gt;Did the operation return more records than you wanted?&lt;/P&gt;
&lt;P&gt;Can paging help reduce the records processed and returned?&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="27%"&gt;CPU use&lt;/TD&gt;
&lt;TD class=data width="73%"&gt;Was CPU use on the server excessive? 
&lt;P&gt;Can it be reduced by different SQL language or by computing data beforehand?&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="27%"&gt;Memory use&lt;/TD&gt;
&lt;TD class=data width="73%"&gt;How much memory on the server was used to process the SQL operation? 
&lt;P&gt;How much memory on the client was used to process the data that was retrieved?&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="27%"&gt;Network bandwidth&lt;/TD&gt;
&lt;TD class=data width="73%"&gt;How much bandwidth did the operation use? 
&lt;P&gt;Is there a way to decrease that amount used by returning fewer rows or fewer columns?&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR vAlign=top&gt;
&lt;TD class=data width="27%"&gt;Transactions&lt;/TD&gt;
&lt;TD class=data width="73%"&gt;Are transactions creating a deadlock or failing? 
&lt;P&gt;Is there a way to commit the transaction faster?&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;
&lt;P&gt;After you examine single operations, run load tests. Monitor the following when you run the load tests: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;Pooling&lt;/B&gt;. Monitor pooling to ensure that connections are returned efficiently to the pool so that they can be reused. Ensure that you can close your connections early in your code. 
&lt;LI&gt;&lt;B&gt;Locks&lt;/B&gt;. Monitor locks to find out whether the locks are held as long as they could be held. Find out if you can reduce the number of locks that you hold, and if you can shorten the duration that you hold the existing locks. &lt;/LI&gt;&lt;/UL&gt;
&lt;H3 class=dtH1&gt;Tool Support&lt;/H3&gt;
&lt;P&gt;Use the following tools to monitor the metrics that are listed in Table 12.3: 
&lt;UL type=disc&gt;
&lt;LI&gt;&lt;B&gt;SQL Query Analyzer&lt;/B&gt;. When you run an SQL command, you can use the &lt;B&gt;Statistics&lt;/B&gt; tab to monitor the duration (in milliseconds), the affected rows, the server round trips, and the bytes transmitted. You can also use SQL Query Analyzer to show you the execution plan that SQL Server uses to run your SQL operation. You can use this feature to identify missed indexes that manifest as table scans. 
&lt;LI&gt;&lt;B&gt;SQL Profiler&lt;/B&gt;. You can use SQL Profiler to monitor an enormous amount of information that includes cursors, locks, and transactions. Use this tool to identify the resources that are used, the operations that are performed, and the length of time (in milliseconds) that particular operations take. 
&lt;LI&gt;&lt;B&gt;Performance Counters&lt;/B&gt;. Use performance counters to monitor connection pooling, index hits and misses, cache hits and misses, and locks. &lt;/LI&gt;&lt;/UL&gt;
&lt;H4 class=dtH1&gt;More Information&lt;/H4&gt;
&lt;P&gt;For more information about measuring data access performance, see "ADO.NET/Data Access" in Chapter 15, "&lt;A href="http://msdn.microsoft.com/library/en-us/dnpag/html/scalenetchapt15.asp"&gt;Measuring .NET Application Performance&lt;/A&gt;"&lt;/P&gt;
&lt;H2 class=dtH1&gt;&lt;A name=scalenetchapt12_topic24&gt;&lt;/A&gt;Summary&lt;/H2&gt;
&lt;P&gt;The database is often a focal point for application load because the majority of application requests require data that comes from a database. Therefore, developing efficient ADO.NET data access code is critical.&lt;/P&gt;
&lt;P&gt;This chapter has provided a brief overview of ADO.NET architecture and has highlighted the main performance and scalability issues that you need to be aware of when you develop data access code. By following the design and implementation guidelines in this chapter, you will greatly increase your chances of building data access code that enables your application to meet its performance objectives.&lt;/P&gt;&lt;img src ="http://www.mscenter.edu.cn/blog/eric_dyx/aggbug/769.html" width = "1" height = "1" /&gt;</description></item></channel></rss>