Using Database Connections

To access the database, you need to provide connection parameters, such as the machine that the database is running on and possibly your login credentials. Anyone who has worked with ADO will be familiar with the .NET connection classes: OleDbConnection and SqlConnection. Below figure show two of the connection classes and includes the class hierarchy.

 The following code snippet illustrates how to create, open and close a connection to the Northwind database:

 1 using System.Data.SqlClient;
 2 
 3 string source = "server=(local);" +
 4                 "integrated security=SSPI;" +
 5                 "database=Northwind";
 6 
 7 SqlConnection conn = new SqlConnection(source);
 8 
 9 conn.Open();
10 
11 // Do something useful
12 
13 conn.Close();
Connection String

In the example connection string, the parameters used are as follows (the parameters are delimited by a semicolon in the connection string):

server=(local):This denotes the database server to connection to SQL Server permits a number of separate database server instances to be running on the same machine, and here you are connecting to the default SQL Server instance. If you are using SQL Express, change the server part to server=.\sqlexpress.

integrated security=SSPI: This uses Windows Authentication to connect to the database, which is highly recommended over using a username and password within the source code.

database=Northwind: This describes the database instance to connect to; each SQL Server process can expose several database instances.

Once the connection has been opened, you can issue commands against the data source, and when you are finished, the connection can be closed.

Managing Connection Strings

In the initial release of .NET, it was up to the developer to manage the database connection strings, which was often done by storing a connection string in the application configuration file or, more commonly, hard-coded somewhere within the application itself.

Beginning with .NET 2.0, you have a predefined way to store connection strings, and even use database connections in a type-agnostic manner ---- for example, it is now possible to write an application and then plug in various database providers, all without altering the main application.

To define a database connection string, you should use the <connectionStrings> section of the configuration file. Here you can specify a name for the connection and the actual database connection string parameters; in addition, you can also specify the provider for this connection type. Here is an example:

private DbConnection GetDatabaseConnection(string name)
{
    ConnectionStringSettings settings = ConfigurationManager.ConnectionStrings[name];
    DbProviderFactory factory = DbProviderFactories.GetFactory(settings.ProviderName);
    DbConnection conn = factory.CreateConnection();
    conn.ConnectionString = settings.ConnectionString;
    return conn;
}

This code reads the named connection string section (using the ConnectionStringSettings class), and then requests a provider factory from the base DbProviderFactories class. This uses the ProviderName property, which was set to “System.Data.SqlClient” in the application configuration file. You might be wondering how this maps to the actual factory class used to generate a database connection for SQL Server — in this case, it should utilize the SqlClientFactory class from System.Data.SqlClient. You will need to add a reference to the System.Configuration assembly in order to resolve the ConfigurationManager class used in the preceding code.

This may seem like a lot of unnecessary work to obtain a database connection, and indeed it is if your application is never going to run on any other database than the one it was designed for. If, however, you use the preceding factory method and also use the generic Db* classes, you will future-proof the application, and any move in the future to another database system will be fairly simple.

Using Connections Efficiently

In general, when using scarce resources in .NET, such as database connections, windows, or graphics objects, it is good practice to ensure that each resource is closed after use. Although the designers of .NET have implemented automatic garbage collection, which will tidy up eventually, it is necessary to release as early as possible to avoid starvation of resources.

This is all too apparent when writing code that accesses a database because keeping a connection open for slightly longer than necessary can affect other sessions. In extreme circumstances, not closing a connection can lock other users out of an entire set of tables, hurting application performance considerably. Closing database connections should be considered mandatory, so this section shows how to structure your code to minimize the risk of leaving a resource open.

You have two main ways to ensure that database connections and the like are released after use. These are explained in the following sections.

Option One: try . . . catch . . . finally

The first option to ensure that resources are cleaned up is to use try...catch...finally blocks, and enusre that you close any open connections within th finally block. Here is a short example:

try
{
    // Open the connction
    conn.Open();
    // Do something useful
}
catch (SqlException ex)
{
    // Log the exception
}
finally
{
    // Ensure that the connection is freed
    conn.Close();
}

Within the finally block, you can release any resources you have used. The only trouble with this method is that you have to ensure that you close the conntion ---- it is all too easy to forget to add the finally clause, so something less prone to vagaries in coding style might be worthwhile.

In additional, you might find that you open a number of resources (say two database connections and a file) within a given method, so the cascade of try...catch...finaly blocks can sometimes become less easy to read. There is, however, another way to guarantee resource cleanup ---- the using statement.

Option Two: the using Block Statement

During development of C#, the debate on how .NET uses nondeterministic destruction became very heated.

In C++, as soon as an object went out of scope, its destructor would be automatically called. This was great news for designers of resource-based classes because the destructor was the ideal place to close the resource if the user had forgotten to do so. A C++ destructor is called whenever an object goes out of scope ---- so, for instance, if an exception were raised and not caught, all destructors would be called.

With C# and the other managed languages, there is no concept of automatic, deterministic destruction. Instead, there is the garbage collecotr, which disposes of resources at some point in the future. What makes this nondeterministic is that you have little say over when this process actually happens. Forgetting to close a database connection could casue all sorts of problems for a .NET executable. Luckily, help is at hand. The following code demonstrates how to use the using clause to ensure that objects that implement the IDisposable interface are cleaned up immediately after the block exits:

using (SqlConnection conn = new SqlConnection(source))
{
    // Open the connection
    conn.Open();

    // Do something useful
}

In this instance, the using clause ensures that the databse connection is closed, regardless of how the block is existed.

Looking at the IL code for the Dispose() method of the connection classes, you can see that all of them check the state of the connection object and, if it is open, will call the Close() method.

When programming, you should use at least one of these methods, and probably both. Wherever you acquire resources, it is good pratice to use the using statement; even though we all mean to write the Close() statement, sometimes we forget, and in the face of exceptions  the using clause does the right thing. There is no substitute for good exception handling either, so in most instances, it is better to use both methods together, as in the following example:

try
{
    using (SqlConnection conn = new SqlConnection(source))
    {
        // Open the connection
        conn.Open();

        // Do something useful

     // Close it myself
     conn.Close(); } } catch (SqlException) { // Log the exception }

Note that this example called Close(), which is not strictly necessary, because the using clause will ensure that this is done anyway. However, you should ensure that any resources such as this are released as soon as possible ---- you might have more code in the rest of the block, and there is no point locking a resource unnecessarily.

In addition, if an exception is raised within the using block, the IDisposable. Dispose method will be called on the resource guarded by the using clause, which in this example ensures that the database connection is always closed. This produces easier-to-read code than having no ensure you close a connection within an exception clause.

You might also note that the exception is defined as a SqlException rather than the catch-all Exception type ---- always try to catch as specific an exception as possible and let all others that are not explicity handled rise up the execution stack. You really should only catch this exception if your specific data class can handle the error and do something with it.

In conclusion, if you are writing a class that wraps a resource, whatever that resource may be, always implement the IDisposable interface to close the resource. That way anyone coding with your class can use the using() statement and guarantee that the resource will be cleaned up.

Transactions

Often when there is more than one update to be made to the database, these updates must be performed within the scope of transaction. It is common in code to find a transaction object being passed around to many methods that update the database; however, since the release of the .NET Framework 2.0, the TransactionScopy class has been available. This class is found within the System.Transactions assembly. This vastly simplifies writing transactional code because you can compse several transactional methods within a transaction scope, and the transaction will flow to each of these methods as necessary:

The following sequence of code initiates a transaction on a SQL Server connection:

using(TransactionScope scope = new TransactionScope (TransactionScopeOption.Required))
{
    using (SqlConnection conn = new SqlConnection(source))
    {
        // Do something in SQL
        // Then mark complete
        scope.Complete();
    }
}

Here, the transaction is explicitly marked as complete by using the scope.Complete() method. In the absence of this call, the transaction will be rolled back so that no changes are made to the database.

When you use a transaction scope, you can optionally choose the isolation level for commands executed within that transaction. The level determines how changes made in one database session are viewed by another.

转载于:https://www.cnblogs.com/liuzeyu/p/3143174.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值