DataAdapter Objects
The DataAdapter object serves as a bridge between a DataSet and a data source for retrieving and saving data. It provides a way to fill a DataSet with data from a data source and to send changes made to the DataSet back to the data source. The DataAdapter object handles the connection and command logic required to interact with the data source.
Overview
A DataAdapter abstracts the details of data retrieval and persistence. It uses four key components to interact with the data source:
SelectCommand: ACommandobject that retrieves records from the data source.InsertCommand: ACommandobject that inserts new records into the data source.UpdateCommand: ACommandobject that updates existing records in the data source.DeleteCommand: ACommandobject that deletes records from the data source.
When you call methods like Fill() on a DataAdapter, it uses the SelectCommand to populate a DataSet. When you call Update(), it examines the RowState of the rows in a DataTable within the DataSet and executes the appropriate command (InsertCommand, UpdateCommand, or DeleteCommand) for each modified row.
Key DataAdapter Classes
ADO.NET provides specific implementations of the DataAdapter interface for different data providers:
SqlDataAdapter: Used with SQL Server.OracleDataAdapter: Used with Oracle databases.OleDbDataAdapter: Used with OLE DB data sources (which can include various databases like Access, Excel, or even SQL Server via OLE DB providers).OdbcDataAdapter: Used with ODBC data sources.
Using the DataAdapter
Here's a typical workflow for using a DataAdapter:
- Create a
Connectionobject to connect to the data source. - Create a
Commandobject for selecting data, defining the SQL query. - Create a
DataAdapterobject (e.g.,SqlDataAdapter) and set itsSelectCommandproperty. - Create a
DataSetobject to hold the data. - Call the
Fill()method of theDataAdapter, passing theDataSetand the name of the table to populate. - Optionally, modify the data in the
DataSet. - If changes are made, create
Commandobjects for inserting, updating, and deleting data, and assign them to theInsertCommand,UpdateCommand, andDeleteCommandproperties of theDataAdapter, respectively. - Call the
Update()method of theDataAdapter, passing theDataSet.
Example:
using System;
using System.Data;
using System.Data.SqlClient;
public class DataAdapterExample
{
public static void Main(string[] args)
{
string connectionString = "Your_Connection_String_Here";
string selectSql = "SELECT CustomerID, CompanyName FROM Customers";
string insertSql = "INSERT INTO Customers (CompanyName) VALUES (@CompanyName); SELECT CAST(SCOPE_IDENTITY() AS INT);";
string updateSql = "UPDATE Customers SET CompanyName = @CompanyName WHERE CustomerID = @CustomerID";
string deleteSql = "DELETE FROM Customers WHERE CustomerID = @CustomerID";
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlDataAdapter adapter = new SqlDataAdapter();
// Select Command
adapter.SelectCommand = new SqlCommand(selectSql, connection);
// Insert Command
adapter.InsertCommand = new SqlCommand(insertSql, connection);
adapter.InsertCommand.Parameters.Add("@CompanyName", SqlDbType.NVarChar, 100, "CompanyName");
// For InsertCommand, we often need to capture the generated ID.
// This example assumes SQL Server's SCOPE_IDENTITY() for auto-generated keys.
adapter.InsertCommand.UpdatedRowSource = UpdateRowSource.OutputParameters;
// Update Command
adapter.UpdateCommand = new SqlCommand(updateSql, connection);
adapter.UpdateCommand.Parameters.Add("@CompanyName", SqlDbType.NVarChar, 100, "CompanyName");
adapter.UpdateCommand.Parameters.Add("@CustomerID", SqlDbType.Int, 0, "CustomerID");
// Delete Command
adapter.DeleteCommand = new SqlCommand(deleteSql, connection);
adapter.DeleteCommand.Parameters.Add("@CustomerID", SqlDbType.Int, 0, "CustomerID");
DataSet dataSet = new DataSet();
try
{
connection.Open();
adapter.Fill(dataSet, "Customers"); // Fill the DataSet
Console.WriteLine("--- Original Data ---");
foreach (DataRow row in dataSet.Tables["Customers"].Rows)
{
Console.WriteLine($"ID: {row["CustomerID"]}, Name: {row["CompanyName"]}");
}
// --- Example of modifying data ---
Console.WriteLine("\n--- Modifying Data ---");
// Add a new row
DataRow newRow = dataSet.Tables["Customers"].NewRow();
newRow["CompanyName"] = "New Tech Solutions";
dataSet.Tables["Customers"].Rows.Add(newRow);
Console.WriteLine("Added new row.");
// Update an existing row
if (dataSet.Tables["Customers"].Rows.Count > 0)
{
DataRow firstRow = dataSet.Tables["Customers"].Rows[0];
firstRow["CompanyName"] = firstRow["CompanyName"] + " (Updated)";
Console.WriteLine($"Updated row with ID: {firstRow["CustomerID"]}");
}
// --- Update the data source ---
Console.WriteLine("\n--- Updating Data Source ---");
int rowsAffected = adapter.Update(dataSet, "Customers");
Console.WriteLine($"Rows affected by update: {rowsAffected}");
// --- Refresh data to show changes ---
Console.WriteLine("\n--- Data After Update ---");
dataSet.Clear(); // Clear the existing dataset to re-fetch
adapter.Fill(dataSet, "Customers");
foreach (DataRow row in dataSet.Tables["Customers"].Rows)
{
Console.WriteLine($"ID: {row["CustomerID"]}, Name: {row["CompanyName"]}");
}
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
}
}
}
Handling Concurrency
When multiple users might be modifying the same data, concurrency issues can arise. The DataAdapter can be configured to detect and handle these situations. By default, it performs an optimistic concurrency check:
- Before an update or delete operation, the
DataAdapterchecks if the original values of the row in the database match the original values stored in theDataSet. - If they don't match, it means another user has modified the row, and an exception (typically
DBConcurrencyException) is thrown.
You can customize how concurrency conflicts are handled by implementing event handlers for the RowUpdating and RowUpdating events of the DataAdapter.
Key Methods and Properties
Commonly used members of the DataAdapter class:
Fill(DataSet): Populates aDataSetwith the results of executing theSelectCommand.Fill(DataTable): Populates a specificDataTablewithin aDataSet.Fill(DataSet, string): Populates aDataSetand names the resulting table.Update(DataSet): Sends changes made to aDataSetback to the data source.Update(DataTable): Sends changes made to a specificDataTableback to the data source.SelectCommand: Gets or sets theCommandused to retrieve records from the data source.InsertCommand: Gets or sets theCommandused to insert new records into the data source.UpdateCommand: Gets or sets theCommandused to update existing records in the data source.DeleteCommand: Gets or sets theCommandused to delete records from the data source.