Using Database Connections

 
Chapter 9 - Data Access with .NET
bySimon Robinsonet al.
Wrox Press 2002
  

In order 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 immediately familiar with the .NET connection classes, OleDbConnection and SqlConnection :

click to expand

The following code snippet illustrates how to create, open , and close a connection to the Northwind database. In the examples within this chapter I use the Northwind database, which is installed with the .NET Framework SDK samples:

   using System.Data.SqlClient;     string source = "server=(local)\NetSDK;" +     "uid=QSUser;pwd=QSPassword;" +     "database=Northwind";     SqlConnection conn = new SqlConnection(source);     conn.Open();     // Do something useful     conn.Close();   

The connection string should be very familiar to you if you've ever used ADO or OLE DB before - indeed, you should be able to cut and paste from your old code if you use the OleDb provider. In the example connection string, the parameters used are as follows . The parameters are delimited by a semicolon in the connection string.

  • server=(local)\\NetSDK - This denotes the database server to connect to. SQL Server permits a number of separate database server processes to be running on the same machine, so here we're connecting to the NetSDK processes on the local machine.

  • uid=QSUser - This parameter describes the database user . You can also use User ID .

  • pwd=QSPassword - And this is the password for that user. The .NET SDK comes with a set of sample databases, and this user/password combination is added during the installation of the .NET samples. You can also use Password .

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

The example opens a database connection using the defined connection string, and then closes that connection. Once the connection has been opened, you can issue commands against the data source, and when you're finished, the connection can be closed.

SQL Server has another mode of authentication - it can use Windows integrated security, so that the credentials supplied at logon are passed through to SQL Server. This is catered for by removing the uid and pwd portions of the connection string , and adding in Integrated Security=SSPI .

In the download code available for this chapter, you will find a file Login.cs that simplifies the examples in this chapter. It is linked to all the example code, and includes database connection information used for the examples; you can alter this to supply your own server name , user, and password as appropriate. This by default uses Windows integrated security; however, you can change the username and password as appropriate.

Now that we know how to open connections, before we move on we should consider some good practices concerning the handling of connections.

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 actively release resources as early as possible.

This is all too apparent when writing code that accesses a database, as 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, considerably hurting application performance. Closing database connections should be considered mandatory, so this section shows how to structure your code so as to minimize the risk of leaving a resource open.

There are two main ways to ensure that database connections and the like are released after use.

Option One - try/catch/finally

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

   try     {     // Open the connection     conn.Open();     // Do something useful     }     catch (Exception ex)     {     // Do something about 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 connection - it is all too easy to forget to add in the finally clause, so something less prone to vagaries in coding style might be worthwhile.

Also, you may find that you open a number of resources (say two database connections and a file) within a given method, so the cascading of try catch finally 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#, .NET's method of clearing up objects after they are no longer referenced using nondeterministic destruction became a topic of very heated discussion. 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, as the destructor was the ideal place to close the resource if the user had forgotten to do so. A C++ destructor is called in any and every situation when an object goes out of scope - so for instance if an exception was raised and not caught, all objects with destructors would have them called.

With C# and the other managed languages, there is no concept of automatic, deterministic destruction - instead there is the garbage collector, which will dispose 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 cause 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 (discussed in Chapter 2) are cleared up immediately the block exits.

 string source = "server=(local)\NetSDK;" +                 "uid=QSUser;pwd=QSPassword;" +                  "database=Northwind";   using (SqlConnection conn = new SqlConnection (source))     {     // Open the connection     conn.Open () ;     // Do something useful     }   

The using clause was introduced in Chapter 2. The object within the using clause must implement the IDisposable interface, or a compilation error will be flagged if the object does not support this interface. The Dispose() method will automatically be called on exiting the using block.

Looking at the IL code for the Dispose() method of SqlConnection (and OleDbConnection ), both of these check the current state of the connection object, and if 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 practice to utilize the using () statement, as 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 I would suggest you 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 (Exception e)     {     // Do something with the exception here...     }   

Here I have explicitly called Close() which isn't strictly necessary as 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 may have more code in the rest of the block and there's 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 case will ensure that the database connection is always closed. This produces easier to read code than having to ensure you close a connection within an exception clause.

One last word - 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 utilize the using() statement and guarantee that the resource will be cleared 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 a transaction. A transaction in ADO.NET is begun by calling one of the BeginTransaction() methods on the database connection object. These methods return an object that implements the IDbTransaction interface, defined within System.Data .

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

 string source = "server=(local)\NetSDK;" +                 "uid=QSUser;pwd=QSPassword;" +                  "database=Northwind"; SqlConnection conn = new SqlConnection(source); conn.Open();   SqlTransaction tx = conn.BeginTransaction();     // Execute some commands, then commit the transaction     tx.Commit();   conn.Close(); 

When you begin a transaction, you can choose the isolation level for commands executed within that transaction. The level determines how isolated your transaction is from others occurring on the database server. Certain database engines may support fewer than the four presented here. The options are as follows:

Isolation Level

Description

ReadCommitted

The default for SQL Server. This level ensures that data written by one transaction will only be accessible in a second transaction after the first commits.

ReadUncommitted

This permits your transaction to read data within the database, even data that has not yet been committed by another transaction. As an example, if two users were accessing the same database, and the first inserted some data without concluding their transaction (by means of a Commit or Rollback ), then the second user with their isolation level set to ReadUncommitted could read the data.

RepeatableRead

This level, which extends the ReadCommitted level, ensures that if the same statement is issued within the transaction, regardless of other potential updates made to the database, the same data will always be returned. This level does require extra locks to be held on the data, which could adversely affect performance.

This level guarantees that, for each row in the initial query, no changes can be made to that data. It does however permit "phantom" rows to show up - these are completely new rows that another transaction may have inserted while your transaction is running.

Serializable

This is the most "exclusive" transaction level, which in effect serializes access to data within the database. With this isolation level, phantom rows can never show up, so a SQL statement issued within a serializable transaction will always retrieve the same data.

The negative performance impact of a Serializable transaction should not be underestimated - if you don't absolutely need to use this level of isolation, it is advisable to stay away from it.

The SQL Server default isolation level, ReadCommitted , is a good compromise between data coherence and data availability, as fewer locks are required on data than in RepeatableRead or Serializable modes. However, there are situations where the isolation level should be increased, and so within .NET you can simply begin a transaction with a different level from the default. There are no hard and fast rules as to which levels to pick - that comes with experience.

One last word on transactions - if you are currently using a database that does not support transactions, it is well worth changing to a database that does!

  


Professional C#. 2nd Edition
Performance Consulting: A Practical Guide for HR and Learning Professionals
ISBN: 1576754359
EAN: 2147483647
Year: 2002
Pages: 244

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net