17
1 Chapter 5: Database Access with ADO.NET Difference between ADO and ADO.NET ADO 1.It is a COM based library. 2.Classic ADO requires active connection with the data store. 3.Locking feature is available. 4.Data is stored in binary format. 5.XML integration is not possible. 6.It uses the object named Recordset to reference data from the data store. 7.Using Classic ADO, we can obtain information from one table or set of tables through join. We cannot fetch records from multiple tables independently. 8.Firewall might prevent execution of Classic ADO. 9.Classic ADO architecture includes client side cursor and server side cursor. 10.We cannot send multiple transactions using a single connection instance. ADO.NET 1.It is a CLR based library. 2.ADO.NET architecture works while the data store is disconnected. 3.Locking feature is not available. 4.Data is stored in XML. 5.XML integration is possible. 6.It uses Dataset Object for data access and representation. 7.Dataset object of ADO.NET includes collection of DataTables wherein each DataTable will contain records fetched from a particular table. Hence multiple table records are maintained independently. 8.ADO.NET has firewall proof and its execution will never be interrupted. 9.ADO.NET architecture doesn't include such cursors. 10.We can send multiple transactions using a single connection instance. Definition of ADO.NET ActiveX Data Object.NET (ADO.NET) is a software library in the .NET framework consisting of software components providing data access services. ADO.NET is designed to enable developers to write managed code for obtaining disconnected access to data sources, which can be relational or non-relational (such as XML or application data). This feature of ADO.NET helps to create data-sharing, distributed applications

Chapter 5: Database Access with ADO

  • Upload
    others

  • View
    4

  • Download
    0

Embed Size (px)

Citation preview

1

Chapter 5: Database Access with ADO.NET

Difference between ADO and ADO.NET

ADO

1.It is a COM based library.

2.Classic ADO requires active connection with the data store.

3.Locking feature is available.

4.Data is stored in binary format.

5.XML integration is not possible.

6.It uses the object named Recordset to reference data from the data store.

7.Using Classic ADO, we can obtain information from one table or set of tables through

join. We cannot fetch records from multiple tables independently.

8.Firewall might prevent execution of Classic ADO.

9.Classic ADO architecture includes client side cursor and server side cursor.

10.We cannot send multiple transactions using a single connection instance.

ADO.NET

1.It is a CLR based library.

2.ADO.NET architecture works while the data store is disconnected.

3.Locking feature is not available.

4.Data is stored in XML.

5.XML integration is possible.

6.It uses Dataset Object for data access and representation.

7.Dataset object of ADO.NET includes collection of DataTables wherein each DataTable

will contain records fetched from a particular table. Hence multiple table records are

maintained independently.

8.ADO.NET has firewall proof and its execution will never be interrupted.

9.ADO.NET architecture doesn't include such cursors.

10.We can send multiple transactions using a single connection instance.

Definition of ADO.NET

• ActiveX Data Object.NET (ADO.NET) is a software library in the .NET framework

consisting of software components providing data access services.

• ADO.NET is designed to enable developers to write managed code for obtaining

disconnected access to data sources, which can be relational or non-relational (such as

XML or application data).

• This feature of ADO.NET helps to create data-sharing, distributed applications

2

The Two Faces of ADO.NET

• The ADO.NET libraries can be used in two conceptually unique manners: connected or

disconnected.

• Connected architecture: the application remains connected with the database throughout the processing.

• Disconnected architecture: the application automatically connects/disconnects during

the processing. The application uses temporary data on the application side called a DataSet.

• When you are making use of the connected layer, your code base will explicitly connect to and

disconnect from the underlying data store.

• When you are using ADO.NET in this manner, you typically interact with the data store using

connection objects, command objects, and data reader objects.

• As you will see later in this chapter, data readers provide a way to pull records from a data store

using a forward-only, read-only approach (much like a fire-hose cursor).

• The disconnected layer, on the other hand, allows you to obtain a set of DataTable objects

(contained within a DataSet) that functions as a client-side copy of the external data.

• When you obtain a DataSet using a related data adapter object, the connection is automatically

opened and closed on your behalf.

Understanding ADO.NET Data Providers

• ADO.NET does not provide a single set of types that communicate with multiple database

management systems (DBMSs).

• Rather, ADO.NET supports multiple data providers, each of which is optimized to interact with a

specific DBMS.

• The first benefit of this approach is that a specific data provider can be programmed to access any

unique features of the DBMS.

• Another benefit is that a specific data provider is able to directly connect to the underlying engine

of the DBMS without an intermediate mapping layer standing between the tiers.

• SimplySimply put, a data provider is a set of types defined in a given namespace that understand

how to communicate with a specific data source.

• Regardless of which data provider you make use of, each defines a set of class types that provide

core functionality.

3

Figure 22-1 illustrates the big picture behind ADO.NET data providers. Note that in the diagram,

the “Client Assembly” can literally be any type of .NET application: console program, Windows Forms

application, ASP.NET web page, XML web service, .NET code library, and so on.

4

Microsoft-Supplied Data Providers

• As of version 2.0, Microsoft’s .NET distribution ships with numerous data providers, including a

provider for Oracle, SQL Server, and ODBC-style connectivity.

• Table 22-2 documents the namespace and containing assembly for each Microsoft ADO.NET data

provider.

• The OLE DB data provider, which is composed of the types defined in the System.Data.OleDb

namespace, allows you to access data located in any data store that supports the classic COM-

based OLE DB protocol.

• Using this provider, you may communicate with any OLE DB–compliant database simply by

tweaking the “Provider” segment of your connection string.

• Be aware, however, that the OLE DB provider interacts with various COM objects behind the

scenes, which can affect the performance of your application.

• By and large, the OLE DB data provider is only useful if you are interacting with a DBMS that does

not define a specific .NET data provider.

• The Microsoft SQL Server data provider offers direct access to Microsoft SQL Server data stores,

and only SQL Server data stores (version 7.0 and greater).

• The System.Data.SqlClient namespace contains the types used by the SQL Server provider and

offers the same basic functionality as the OLE DB provider.

• The key difference is that the SQL Server provider bypasses the OLE DB layer and thus gives

numerous performance benefits. As well, the Microsoft SQL Server data provider allows you to

gain access to the unique features of this particular DBMS.

5

Third-Party Data Providers

• In addition to the data providers that ship from Microsoft, numerous third-party data providers

exist for various open source and commercial databases.

• Table 22-3 documents where to obtain managed providers for several databases that do not

directly ship with Microsoft .NET 2.0

Understanding the Connected Layer of ADO.NET

• the connected layer of ADO.NET allows you to interact with a database using the connection,

command, and data reader objects of your data provider.

• When you wish to connect to a database and read the records using a data reader object, you

need to perform the following steps:

1. Allocate, configure, and open your connection object.

2. Allocate and configure a command object, specifying the connection object as a constructor argument

or via the Connection property.

3. Call ExecuteReader() on the configured command object.

4. Process each record using the Read() method of the data reader.

• In the example program, The goal is to open a connection (via the SqlConnection object) and

submit a SQL query (via the SqlCommandobject) to obtain all records within the Inventory table

of the Cars database.

• At this point, you will use a SqlDataReader to print out the results using the type indexer.

• Here is the complete code within Main(), with analysis to follow:

class Program

{

static void Main(string[] args)

{

Console.WriteLine("***** Fun with Data Readers *****\n");

// Create an open a connection.

SqlConnection cn = new SqlConnection();

cn.ConnectionString =

"uid=sa;pwd=;Initial Catalog=Cars; Data Source=(local)";

6

cn.Open();

// Create a SQL command object.

string strSQL = "Select * From Inventory";

SqlCommand myCommand = new SqlCommand(strSQL, cn);

// Obtain a data reader a la ExecuteReader().

SqlDataReader myDataReader;

myDataReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection);

// Loop over the results.

while (myDataReader.Read())

{

Console.WriteLine("-> Make: {0}, PetName: {1}, Color: {2}.",

myDataReader["Make"].ToString().Trim(),

myDataReader["PetName"].ToString().Trim(),

myDataReader["Color"].ToString().Trim());

}

// Because we specified CommandBehavior.CloseConnection, we

// don't need to explicitly call Close() on the connection.

myDataReader.Close();

}

}

Working with Connection Objects

• The first step to take when working with a data provider is to establish a session with the data

source using the connection object (which, as you recall, derives from DbConnection).

• .NET connection types are provided with a formatted connection string, which contains a number

of name/value pairs separated by semicolons.

• This information is used to identify the name of the machine you wish to connect to, required

security settings, the name of the database on that machine, and other data provider–specific

information.

• As you can infer from the preceding code, the Initial Catalog name refers to the database you are

attempting to establish a session with (Pubs, Northwind, Cars, etc.).

• The Data Source name identifies the name of the machine that maintains the database (for

simplicity, I have assumed no specific password is required for local system administrators).

• Once your construction string has been established, a call to Open() establishes your connection

with the DBMS.

• In addition to the ConnectionString, Open(), and Close() members, a connection object provides

a number of members that let you configure attritional settings regarding your connection, such

as timeout settings and transactional information.

• Table 22-6 lists some (but not all) members of the DbConnection base class.

7

• As you can see, the properties of the DbConnection type are typically read-only in nature and are

only useful when you wish to obtain the characteristics of a connection at runtime.

• When you wish to override default settings, you must alter the construction string itself.

• For example, the connection string sets the connection timeout setting from 15 seconds to 30

seconds (via the Connect Timeout segment of the connection string):

static void Main(string[] args)

{

SqlConnection cn = new SqlConnection();

cn.ConnectionString =

"uid=sa;pwd=;Initial Catalog=Cars;" +

"Data Source=(local);Connect Timeout=30";

cn.Open();

// New helper function (see below).

ShowConnectionStatus(cn);

...

}

• In the preceding code, notice you have now passed your connection object as a parameter to a

new static helper method in the Program class named ShowConnectionStatus(), implemented as

so:

static void ShowConnectionStatus(DbConnection cn)

{

// Show various stats about current connection object.

Console.WriteLine("***** Info about your connection *****");

Console.WriteLine("Database location: {0}", cn.DataSource);

Console.WriteLine("Database name: {0}", cn.Database);

Console.WriteLine("Timeout: {0}", cn.ConnectionTimeout);

Console.WriteLine("Connection state: {0}\n", cn.State.ToString());

8

}

• While most of these properties are self-explanatory, the State property is worth special mention.

• Although this property may be assigned any value of the ConnectionState enumeration

public enum System.Data.ConnectionState

{

Broken, Closed,

Connecting, Executing,

Fetching, Open

}

• the only valid ConnectionState values are ConnectionState.Open and ConnectionState.Closed

(the remaining members of this enum are reserved for future use). Also, understand that it is

always safe to close a connection whose connection state is currently ConnectionState.Closed.

Working with .NET 2.0 ConnectionStringBuilders

• the Microsoft-supplied ADO.NET data providers now support connection string builder objects,

which allow you to establish the name/value pairs using strongly typed properties.

• Consider the following Main() method:

static void Main(string[] args)

{

// Create a connection string via the builder object.

SqlConnectionStringBuilder cnStrBuilder =

new SqlConnectionStringBuilder();

cnStrBuilder.UserID = "sa";

cnStrBuilder.Password = "";

cnStrBuilder.InitialCatalog = "Cars";

cnStrBuilder.DataSource = "(local)";

cnStrBuilder.ConnectTimeout = 30;

SqlConnection cn = new SqlConnection();

cn.ConnectionString = cnStrBuilder.ConnectionString;

cn.Open();

ShowConnectionStatus(cn);

...

}

• In this iteration, you create an instance of SqlConnectionStringBuilder, set the properties

accordingly, and obtain the internal string via the ConnectionString property.

• Also note that you make use of the default constructor of the type.

• If you so choose, you can also create an instance of your data provider’s connection string builder

object by passing in an existing connection string as a starting point (which can be helpful when

you are reading these values dynamically from an app.config file).

• Once you have hydrated the object with the initial string data, you can change specific name/value

pairs using the related properties, for example:

static void Main(string[] args)

9

{

Console.WriteLine("***** Fun with Data Readers *****\n");

// Assume you really obtained cnStr from a *.config file.

string cnStr = "uid=sa;pwd=;Initial Catalog=Cars;" +

"Data Source=(local);Connect Timeout=30";

SqlConnectionStringBuilder cnStrBuilder =

new SqlConnectionStringBuilder(cnStr);

cnStrBuilder.UserID = "sa";

cnStrBuilder.Password = "";

cnStrBuilder.InitialCatalog = "Cars";

cnStrBuilder.DataSource = "(local)";

// Change timeout value.

cnStrBuilder.ConnectTimeout = 5;

...

}

Working with Command Objects

• The SqlCommand type (which derives from DbCommand) is an OO representation of a SQL query,

table name, or stored procedure.

• The type of command is specified using the CommandType property, which may take any value

from the CommandType enum:

public enum System.Data.CommandType

{

StoredProcedure,

TableDirect,

Text // Default value.

}

• When creating a command object, you may establish the SQL query as a constructor parameter

or directly via the CommandText property.

• Also when you are creating a command object, you need to specify the connection to be used.

Again, you may do so as a constructor parameter or via the Connection property:

static void Main(string[] args)

{

SqlConnection cn = new SqlConnection();

...

// Create command object via ctor args.

string strSQL = "Select * From Inventory";

SqlCommand myCommand = new SqlCommand(strSQL, cn);

// Create another command object via properties.

SqlCommand testCommand = new SqlCommand();

testCommand.Connection = cn;

testCommand.CommandText = strSQL;

...

10

}

• Realize that at this point, you have not literally submitted the SQL query to the Cars database,but

rather prepped the state of the command type for future use.

• Table 22-7 highlights some additional members of the DbCommand type.

Working with Data Readers

• Once you have established the active connection and SQL command, the next step is to submit

the query to the data source.

• As you might guess, you have a number of ways to do so.

• The DbDataReader type (which implements IDataReader) is the simplest and fastest way to obtain

information from a data store.

• Recall that data readers represent a read-only, forward-only stream of data returned one record

at a time.

• Given this, it should stand to reason that data readers are useful only when submitting SQL

selection statements to the underlying data store.

• Data readers are useful when you need to iterate over large amounts of data very quickly and

have no need to maintain an in-memory representation.

• For example, if you request 20,000 records from a table to store in a text file, it would be rather

memory-intensive to hold this information in a DataSet.

• A better approach is to create a data reader that spins over each record as rapidly as possible.

• Data reader objects maintain an open connection to their data source until you explicitly close

the session.

11

• Data reader objects are obtained from the command object via a call to ExecuteReader().

• When invoking this method, you may optionally instruct the reader to automatically close down

the related connection object by specifying CommandBehavior.CloseConnection.

• The following use of the data reader leverages the Read() method to determine when you have

reached the end of your records (via a false return value).

• For each incoming record, you are making use of the type indexer to print out the make, pet name,

and color of each automobile.

• Also note that you call Close() as soon as you are finished processing the records, to free up the

connection object:

static void Main(string[] args)

{

...

// Obtain a data reader a la ExecuteReader().

SqlDataReader myDataReader;

myDataReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection);

// Loop over the results.

while (myDataReader.Read())

{

Console.WriteLine("-> Make: {0}, PetName: {1}, Color: {2}.",

myDataReader["Make"].ToString().Trim(),

myDataReader["PetName"].ToString().Trim(),

myDataReader["Color"].ToString().Trim());

}

myDataReader.Close();

ShowConnectionStatus(cn);

}

• The indexer of a data reader object has been overloaded to take either a string (representing the

name of the column) or an integer (representing the column’s ordinal position).

• Thus, you could clean up the current reader logic (and avoid hard-coded string names) with the

following update(note the use of the FieldCount property):

while (myDataReader.Read())

{

Console.WriteLine("***** Record *****");

for (int i = 0; i < myDataReader.FieldCount; i++)

{

Console.WriteLine("{0} = {1} ",

myDataReader.GetName(i),

myDataReader.GetValue(i).ToString().Trim());

}

Console.WriteLine();

}

12

• If you compile and run your project, you should be presented with a list of all automobiles in the

Inventory table of the Cars database (see Figure 22-7).

Obtaining Multiple Result Sets Using a Data Reader

• Data reader objects are able to obtain multiple result sets from a single command object.

• For example, if you are interested in obtaining all rows from the Inventory table as well as all rows

from the Customers table, you are able to specify both SQL select statements using a semicolon

delimiter:

string theSQL = "Select * From Inventory;Select * from Customers";

• Once you obtain the data reader, you are able to iterate over each result set via the NextResult()

method.

• Do be aware that you are always returned the first result set automatically.

• Thus, if you wish to read over the rows of each table, you will be able to build the following

iteration construct:

do

{

while(myDataReader.Read())

{

// Read the info of the current result set.

}

}while(myDataReader.NextResult());

13

• So, at this point, you should be more aware of the functionality data reader objects bring to the

table.

Modifying Tables Using Command Objects

• when you wish to submit SQL commands that result in the modification of a given table, you will

call the ExecuteNonQuery() method of your command object.

• This single method will perform inserts, updates, and deletes based on the format of your

command text.

• To illustrate how to modify an existing database using nothing more than a call to

ExecuteNonQuery(), you will now build a new console application (CarsInventoryUpdater) that

allows the caller to modify the Inventory table of the Cars database.

• Like in other examples in this text, the Main() method is responsible for prompting the user for a

specific course of action and executing that request via a switch statement. This program will

allow the user to enter the following commands:

• I: Inserts a new record into the Inventory table

• U: Updates an existing record in the Inventory table

• D: Deletes an existing record from the Inventory table

• L: Displays the current inventory using a data reader

• S: Shows these options to the user

• Q: Quits the program

• Each possible option is handled by a unique static method within the Program class.

• For the purpose of completion, here is the implementation of Main(), which I assume requires no

further comment:

static void Main(string[] args)

{

Console.WriteLine("***** Car Inventory Updater *****");

bool userDone = false;

string userCommand = "";

SqlConnection cn = new SqlConnection();

cn.ConnectionString =

"uid=sa;pwd=;Initial Catalog=Cars;" +

"Data Source=(local);Connect Timeout=30";

cn.Open();

ShowInstructions();

do

{

Console.Write("Please enter your command: ");

userCommand = Console.ReadLine();

14

Console.WriteLine();

switch (userCommand.ToUpper())

{

case "I":

InsertNewCar(cn);

break;

case "U":

UpdateCarPetName(cn);

break;

case "D":

DeleteCar(cn);

break;

case "L":

ListInventory(cn);

break;

case "S":

ShowInstructions();

break;

case "Q":

userDone = true;

break;

default:

Console.WriteLine("Bad data! Try again");

break;

}

} while (!userDone);

cn.Close();

}

• The ShowInstructions() method does what you would expect:

private static void ShowInstructions()

{

Console.WriteLine();

Console.WriteLine("I: Inserts a new car.");

Console.WriteLine("U: Updated an existing car.");

Console.WriteLine("D: Deletes an existing car.");

Console.WriteLine("L: List current inventory.");

Console.WriteLine("S: Show these instructions.");

Console.WriteLine("Q: Quits program.");

}

• As mentioned, ListInventory() prints out the current rows of the Inventory table using a data

reader object (the code is identical to the previous CarsDataReader example):

private static void ListInventory(SqlConnection cn)

15

{

string strSQL = "Select * From Inventory";

SqlCommand myCommand = new SqlCommand(strSQL, cn);

SqlDataReader myDataReader;

myDataReader = myCommand.ExecuteReader();

while (myDataReader.Read())

{

for (int i = 0; i < myDataReader.FieldCount; i++)

{

Console.Write("{0} = {1} ",

myDataReader.GetName(i),

myDataReader.GetValue(i).ToString().Trim());

}

Console.WriteLine();

}

myDataReader.Close();

}

Inserting New Records

• Inserting a new record into the Inventory table is as simple as formatting the SQL insert

statement(based on user input) and calling ExecuteNonQuery().

private static void InsertNewCar(SqlConnection cn)

{

// Gather info about new car.

Console.Write("Enter CarID: ");

int newCarID = int.Parse(Console.ReadLine());

Console.Write("Enter Make: ");

string newCarMake = Console.ReadLine();

Console.Write("Enter Color: ");

string newCarColor = Console.ReadLine();

Console.Write("Enter PetName: ");

string newCarPetName = Console.ReadLine();

// Format and execute SQL statement.

string sql = string.Format("Insert Into Inventory" +

"(CarID, Make, Color, PetName) Values" +

"('{0}', '{1}', '{2}', '{3}')", newCarID, newCarMake,

newCarColor, newCarPetName);

SqlCommand cmd = new SqlCommand(sql, cn);

cmd.ExecuteNonQuery();

}

16

Deleting Existing Records

• Deleting an existing record is just as simple as inserting a new record.

• Unlike the code listing for InsertNewCar(), I will show one important try/catch scope that handles

the possibility of attempting to delete a car that is currently on order for an individual in the

Customers table:

private static void DeleteCar(SqlConnection cn)

{

// Get ID of car to delete, then do so.

Console.Write("Enter CarID of car to delete: ");

int carToDelete = int.Parse(Console.ReadLine());

string sql = string.Format("Delete from Inventory where CarID = '{0}'",

carToDelete);

SqlCommand cmd = new SqlCommand(sql, cn);

try { cmd.ExecuteNonQuery(); }

catch { Console.WriteLine("Sorry! That car is on order!"); }

}

Updating Existing Records

• If you followed the code behind DeleteCar() and InsertNewCar(), then UpdateCarPetName() is a

nobrainer (again, try/catch logic has been removed for clarity):

private static void UpdateCarPetName(SqlConnection cn)

{

// Get ID of car to modify and new pet name.

Console.Write("Enter CarID of car to modify: ");

string newPetName = "";

int carToUpdate = carToUpdate = int.Parse(Console.ReadLine());

Console.Write("Enter new pet name: ");

newPetName = Console.ReadLine();

// Now update record.

string sql =

string.Format("Update Inventory Set PetName = '{0}' Where CarID = '{1}'",

newPetName, carToUpdate);

SqlCommand cmd = new SqlCommand(sql, cn);

cmd.ExecuteNonQuery();

}

With this, our application is finished. Figure 22-8 shows a test run.

17