Connections: // Close The Connection Explicitly
Connections: // Close The Connection Explicitly
NET Overview
Extracts from Microsoft .NET Framework Common Tasks QuickStart – http://samples.gotdotnet.com/quickstart/howto /
Copyright 2002 Microsoft Corporation. All rights reserved.
ADO.NET is an evolution of the ADO data access model that directly addresses user requirements for developing scalable
applications. It was designed specifically for the web with scalability, statelessness, and XML in mind.
ADO.NET uses some ADO objects, such as the Connection and Command objects, and also introduces new objects.
Key new ADO.NET objects include the DataSet, DataReader, and DataAdapter.
The important distinction between this evolved stage of ADO.NET and previous data architectures is that there exists an
object -- the DataSet -- that is separate and distinct from any data stores. Because of that, the DataSet functions as a
standalone entity. You can think of the DataSet as an always disconnected recordset that knows nothing about the
source or destination of the data it contains. Inside a DataSet, much like in a database, there are tables, columns,
relationships, constraints, views, and so forth.
A DataAdapter is the object that connects to the database to fill the DataSet. Then, it connects back to the database
to update the data there, based on operations performed while the DataSet held the data. In the past, data processing
has been primarily connection-based. Now, in an effort to make multi-tiered apps more efficient, data processing is
turning to a message-based approach that revolves around chunks of information. At the center of this approach is the
DataAdapter, which provides a bridge to retrieve and save data between a DataSet and its source data store. It
accomplishes this by means of requests to the appropriate SQL commands made against the data store.
The XML-based DataSet object provides a consistent programming model that works with all models of data storage:
flat, relational, and hierarchical. It does this by having no 'knowledge' of the source of its data, and by representing the
data that it holds as collections and data types. No matter what the source of the data within the DataSet is, it is
manipulated through the same set of standard APIs exposed through the DataSet and its subordinate objects.
While the DataSet has no knowledge of the source of its data, the managed provider has detailed and specific
information. The role of the managed provider is to connect, fill, and persist the DataSet to and from data stores. The
OLE DB and SQL Server .NET Data Providers (System.Data.OleDb and System.Data.SqlClient) that are part of the .Net
Framework provide four basic objects: the Command, Connection, DataReader and DataAdapter. In the remaining
sections of this document, we'll walk through each part of the DataSet and the OLE DB/SQL Server .NET Data Providers
explaining what they are, and how to program against them.
The following sections will introduce you to some objects that have evolved, and some that are new. These objects are:
Connections. For connection to and managing transactions against a database.
Commands. For issuing SQL commands against a database.
DataReaders. For reading a forward-only stream of data records from a SQL Server data source.
DataSets. For storing, remoting and programming against flat data, XML data and relational data.
DataAdapters. For pushing data into a DataSet, and reconciling data against a database.
Note When dealing with connections to a database, there are two different options: SQL Server .NET Data Provider
(System.Data.SqlClient) and OLE DB .NET Data Provider (System.Data.OleDb). In these samples we will use the SQL
Server .NET Data Provider. These are written to talk directly to Microsoft SQL Server. The OLE DB .NET Data Provider is
used to talk to any OLE DB provider (as it uses OLE DB underneath).
Connections
Connections are used to 'talk to' databases, and are respresented by provider-specific classes such as SQLConnection.
Commands travel over connections and resultsets are returned in the form of streams which can be read by a
DataReader object, or pushed into a DataSet object.
The example below shows how to create a connection object. Connections can be opened explicitly by calling the Open
method on the connection, or will be opened implicitly when using a DataAdapter.
using System;
using System.Data.SqlClient;
Commands
Commands contain the information that is submitted to a database, and are represented by provider-specific classes such
as SQLCommand. A command can be a stored procedure call, an UPDATE statement, or a statement that returns
results. You can also use input and output parameters, and return values as part of your command syntax. The example
below shows how to issue an INSERT statement against the Northwind database.
using System;
using System.Data.SqlClient;
try {
myConnection.Open();
mySqlCleanup.ExecuteNonQuery(); // remove record that may have been entered previously.
mySqlCommand.ExecuteNonQuery();
Message = "New Record inserted into Customers table in northwind.";
} catch(Exception e) {
Message= "Couldn't insert record: " + e.ToString();
} finally {
myConnection.Close();
}
Console.Write(Message);
}
}
DataReaders
The DataReader object is somewhat synonymous with a read-only/forward-only cursor over data. The DataReader
API supports flat as well as hierarchical data. A DataReader object is returned after executing a command against a
database. The format of the returned DataReader object is different from a recordset.For example, you might use the
DataReader to show the results of a search list in a web page.
using System;
using System.Data;
using System.Data.SqlClient;
SqlConnection mySqlConnection =
new SqlConnection("server=(local)\\NetSDK;Trusted_Connection=yes;database=northwind");
SqlCommand mySqlCommand = new SqlCommand("select * from customers", mySqlConnection);
try {
mySqlConnection.Open();
myReader = mySqlCommand.ExecuteReader();
Console.Write("Customer ID ");
Console.WriteLine("Company Name");
while (myReader.Read()) {
Console.Write(myReader["CustomerID"].ToString() + " ");
Console.WriteLine(myReader["CompanyName"].ToString());
}
} catch(Exception e) {
Console.WriteLine(e.ToString());
} finally {
if (myReader != null)
myReader.Close();
if (mySqlConnection.State == ConnectionState.Open)
mySqlConnection.Close();
}
}
}
mySqlDataAdapter.InsertCommand.CommandText = "sp_InsertCustomer";
mySqlDataAdapter.InsertCommand.CommandType = CommandType.StoredProcedure;
mySqlDataAdapter.DeleteCommand.CommandText = "sp_DeleteCustomer";
mySqlDataAdapter.DeleteCommand.CommandType = CommandType.StoredProcedure;
mySqlDataAdapter.UpdateCommand.CommandText = "sp_UpdateCustomers";
mySqlDataAdapter.UpdateCommand.CommandType = CommandType.StoredProcedure;
mySqlDataAdapter.Update(myDataSet);
workParam = mySqlDataAdapter.InsertCommand.Parameters.Add(
"@RegionDescription", SqlDbType.NChar, 50);
workParam.SourceVersion = DataRowVersion.Current;
workParam.SourceColumn = "RegionDescription";
workParam = mySqlDataAdapter.UpdateCommand.Parameters.Add("@RegionDescription",
SqlDbType.NChar, 50);
workParam.SourceVersion = DataRowVersion.Current;
workParam.SourceColumn = "RegionDescription";
// Set the MissingSchemaAction property to AddWithKey because Fill will not cause
// primary key & unique key information to be retrieved unless AddWithKey is specified.
mySqlDataAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;
mySqlDataAdapter.Fill(myDataSet, "Region");
try {
mySqlDataAdapter.Update(myDataSet, "Region");
Console.Write("Updating DataSet succeeded!");
} catch(Exception e) {
Console.Write(e.ToString());
}
}
public void Cleanup() {
SqlConnection myConnection = new SqlConnection(
"server=(local)\\NetSDK;Trusted_Connection=yes;database=northwind");
try{
// Restore database to it's original condition so sample will work correctly.
myConnection.Open();
SqlCommand CleanupCommand =
new SqlCommand("DELETE FROM Region WHERE RegionID = '901'", myConnection);
CleanupCommand.ExecuteNonQuery();
} catch(Exception e) {
Console.Write(e.ToString());
} finally {
myConnection.Close();
}
}
}
The SqlDataAdapter and SqlCommand are very similar, except for the Fill and Update methods. The Fill method
populates a DataSet. The Update method takes changes from a DataSet and pushes them back into the database. This
is accomplished by four commands specified on the DataAdapter. These commands are: SelectCommand,
UpdateCommand, InsertCommand, and DeleteCommand. You can explicitly set these commands to control the
statements used at run time to resolve changes, including the use of stored procedures. For ad hoc scenarios, a
CommandBuilder object can generate these at run time based on a select statement. However, this run-time
generation requires an extra round trip to the server to gather required metadata, so explicitly providing the insert,
update, and delete commands at design time will result in better run-time performance.
NOTE: Visual Studio adds great value to establishing typed SqlDataAdapters and DataSets, and eventually creates Stored
Procedures for you. Explore this feature by using the ComponentDesigner and Database objects.
Once your SqlDataAdapter is established you can pass it a DataSet to populate:
myDataSet = new DataSet();
mySqlDataAdapter.Fill(myDataSet,"Customers");
The DataSet now holds the results of your query. In fact, the DataSet can hold results from multiple queries and even
relate them. Because it holds multiple results, the DataSet contains a collection of tables. Notice that the Fill method has
"Customers" as the second argument. This is the name of the table to fill in the DataSet. If that table does not exist, it is
created for you.
Because the data is stored in a collection of rows in the table, you can easily use a foreach statement to iterate through
the rows:
foreach (DataRow myDataRow in myDataSet.Tables["Customers"].Rows) {
Console.WriteLine(myDataRow["CustomerId"].ToString());
}
In fact, you can use foreach over the columns as well. The following example demonstrates placing together all the
code in this document.
using System;
using System.Data;
using System.Data.SqlClient;
// Create command builder. This line automatically generates the update commands for you,
// so you don't have to provide or create your own.
SqlCommandBuilder mySqlCommandBuilder = new SqlCommandBuilder(mySqlDataAdapter);
// Set the MissingSchemaAction property to AddWithKey because Fill will not cause primary
// key & unique key information to be retrieved unless AddWithKey is specified.
mySqlDataAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;
mySqlDataAdapter.Fill(myDataSet, "Customers");
myDataRow = myDataSet.Tables["Customers"].NewRow();
myDataRow["CustomerId"] = "NewID";
myDataRow["ContactName"] = "New Name";
myDataRow["CompanyName"] = "New Company Name";
myDataSet.Tables["Customers"].Rows.Add(myDataRow);
Note that the DataTable must return a DataRow through the NewRow method. The method returns a DataRow object
with the appropriate schema of the DataTable. The new DataRow is independent of the table until it is added to the
RowsCollection.
You can change data in a DataRow by accessing the DataRow. You can use the index of the row in the RowsCollection
accessed through the Rows property:
myDataSet.Tables["Customers"].Rows[0]["ContactName"]="Peach";
You can also access a specific row by the Primary Key value:
DataRow myDataRow1 = myDataSet.Tables["Customers"].Rows.Find("ALFKI");
myDataRow1["ContactName"]="Peach";
where "ALFKI" is the value of the Primary Key "CustomerID" in the "Customers" table. When using the SqlDataAdapter,
the Key is established from the database. You can also set the Key if you are not using the database through the
PrimaryKey property.
Use the Delete method to remove the Row. Note that a logical deletion occurs in the DataSet, which only results in a
hard deletion once the DataSet is updated to the database. Similarly you can use RejectChanges on the DataSet, in
which case the Row is restored.
myDataSet.Tables["Customers"].Rows[0].Delete();
The original and new values are maintained in the row. The RowChanging event allows you to access both original and
new values to decide whether you want the edit to proceed. Because we maintain original and new values, we can
establish scenarios such as optimistic locking and key changes.
Before submitting the update back to the database, you need to setup the InsertCommand, UpdateCommand, and
DeleteCommand to reconcile the changes to the database. For limited scenarios you can use the
SqlCommandBuilder to automatically generate those for you, as is shown in the following example:
SqlCommandBuilder mySqlCommandBuilder = new SqlCommandBuilder(mySqlDataAdapter);
To submit the data from the DataSet into the database, use the Update method on the SqlDataAdapter.
mySqlDataAdapter.Update(myDataSet, "Customers");
The following example demonstrates how to get data from a database using a SqlDataAdapter, modify the data within
the DataSet, and then submit the data back to the database through the SqlDataAdapter.
using System;
using System.Data;
using System.Data.SqlClient;
try{
DataSet myDataSet = new DataSet();
DataRow myDataRow;
// Create command builder. This line automatically generates the update commands for you,
// so you don't have to provide or create your own.
SqlCommandBuilder mySqlCommandBuilder = new SqlCommandBuilder(mySqlDataAdapter);
// Set the MissingSchemaAction property to AddWithKey because Fill will not cause primary
// key & unique key information to be retrieved unless AddWithKey is specified.
mySqlDataAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;
mySqlDataAdapter1.MissingSchemaAction = MissingSchemaAction.AddWithKey;
mySqlDataAdapter.Fill(myDataSet,"Customers");
Console.WriteLine("Loaded data from Customers table into dataset.");
mySqlDataAdapter1.Fill(myDataSet,"Orders");
Console.WriteLine("Loaded data from Orders table into dataset.");
// ADD RELATION
myDataSet.Relations.Add("CustOrders",myDataSet.Tables["Customers"].Columns["CustomerId"],
myDataSet.Tables["Orders"].Columns["CustomerId"]);
// EDIT
myDataSet.Tables["Customers"].Rows[0]["ContactName"]="Peach";
// ADD
myDataRow = myDataSet.Tables["Customers"].NewRow();
myDataRow["CustomerId"] ="NewID";
myDataRow["ContactName"] = "New Name";
myDataRow["CompanyName"] = "New Company Name";
myDataSet.Tables["Customers"].Rows.Add(myDataRow);
Console.WriteLine("Inserted new row into Customers.");
This returns an XSD compliant XML schema for the schema in your DataSet.
XML representation is also retrieved through the GetXml method.
Console.WriteLine(myDataSet.GetXml());
XML representation of dataset can be converted back to DataSet object through the ReadXml method.
The sample below loads data from a database, and then outputs the XSD Schema and XML data.
using System;
using System.Data;
using System.Data.SqlClient;
mySqlDataAdapter1.Fill(myDataSet,"Customers");
mySqlDataAdapter2.Fill(myDataSet,"Orders");
with a strongly typed DataSet, you write the following ADO.NET code:
CustomerDataSet.Customers("ALFKI").CustomerName = "Bob"
Additionally, the strongly typed DataSet provides access to row values as the correct, strongly typed value. With ADO,
variant types are used when assigning and retrieving data. If the value that you assigned was of the wrong type, ADO
would give you a run-time error. With ADO.NET, if the value is of type integer and you attempt to assign a string, you
will get a compile-time error.
Given an XML schema that complies with the XSD standard, you can generate a strongly typed DataSet using the
XSD.exe tool provided with the .NET Framework SDK. The syntax for generating a DataSet using this tool is:
xsd.exe /d /l:CS {XSDSchemaFileName.xsd}
The /d directive directs the tool to generate a DataSet; the /l: directive specifies the language to use -- either "C#" or
"VB."
The following example uses a strongly typed DataSet called "myCustDS" to load a list of customers from the Northwind
database. Once the data is loaded using the Fill method, the code loops through each customer in the "Customers" table
using the typed myDataRow object. Note the direct access to the "CustomerID" field, as opposed to accessing it
through the Fields collection.
Before running the following sample, you must run "nmake /a" in the "..\typeddata\DllSrc" directory from a console
window to create the TypedData.dll file.
using System;
using System.Data;
using System.Data.SqlClient;
using customerDataSet;
Console.WriteLine("CustomerID\n");