ADO.NET provides a rich set of classes for interacting with data sources. A fundamental aspect of this interaction is executing commands, which can range from simple queries to complex stored procedures. This document explores the various ways to execute commands using ADO.NET, focusing on the core objects involved and best practices.
The Role of IDbCommand
The IDbCommand
interface, and its concrete implementations like SqlCommand
(for SQL Server), MySqlCommand
(for MySQL), and OracleCommand
(for Oracle), represent an SQL statement or stored procedure to be executed against a data source. Key properties and methods include:
CommandText
: The SQL statement or stored procedure name.Connection
: TheIDbConnection
object representing the connection to the data source.CommandType
: Specifies whether theCommandText
is aText
,StoredProcedure
, orTableDirect
.Parameters
: A collection ofIDbDataParameter
objects to pass arguments to the command.ExecuteNonQuery()
: Executes a Transact-SQL statement that does not return a result set (e.g., INSERT, UPDATE, DELETE). Returns the number of rows affected.ExecuteReader()
: Executes a query that returns a result set. Returns anIDataReader
object.ExecuteScalar()
: Executes a query that returns a single value (e.g., COUNT(*)).ExecuteXmlReader()
: Executes a query that returns XML data.
Executing Non-Query Commands
For operations that modify data but do not return a result set, ExecuteNonQuery()
is the appropriate method.
using System;
using System.Data;
using System.Data.SqlClient; // Or your specific provider
public class DataUpdater
{
private string connectionString = "YourConnectionStringHere";
public void UpdateProductPrice(int productId, decimal newPrice)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
string query = "UPDATE Products SET Price = @Price WHERE ProductID = @ProductID";
SqlCommand command = new SqlCommand(query, connection);
// Add parameters to prevent SQL injection
command.Parameters.AddWithValue("@Price", newPrice);
command.Parameters.AddWithValue("@ProductID", productId);
try
{
connection.Open();
int rowsAffected = command.ExecuteNonQuery();
Console.WriteLine($"{rowsAffected} row(s) updated.");
}
catch (Exception ex)
{
Console.WriteLine($"Error updating product price: {ex.Message}");
}
}
}
}
Always use parameterized queries with Parameters.AddWithValue()
or by specifying the data type explicitly to prevent SQL injection vulnerabilities.
Executing Queries that Return Data
When you need to retrieve data from the database, ExecuteReader()
is commonly used. It returns an IDataReader
, which is a forward-only, read-only stream of data.
using System;
using System.Data;
using System.Data.SqlClient;
public class DataReaderExample
{
private string connectionString = "YourConnectionStringHere";
public void GetCustomerNames()
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
string query = "SELECT CustomerID, CompanyName FROM Customers WHERE Country = @Country";
SqlCommand command = new SqlCommand(query, connection);
command.Parameters.AddWithValue("@Country", "USA");
try
{
connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
Console.WriteLine($"ID: {reader["CustomerID"]}, Name: {reader["CompanyName"]}");
}
}
else
{
Console.WriteLine("No customers found for the specified country.");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error retrieving customer names: {ex.Message}");
}
}
}
}
The IDataReader
object allows you to iterate through the result set row by row using the Read()
method. You can access column values by index or by column name.
Executing Commands that Return a Single Value
For queries that are expected to return a single column and a single row, such as aggregate functions (e.g., COUNT()
, SUM()
), ExecuteScalar()
is highly efficient.
using System;
using System.Data;
using System.Data.SqlClient;
public class ScalarExample
{
private string connectionString = "YourConnectionStringHere";
public int GetProductCount()
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
string query = "SELECT COUNT(*) FROM Products";
SqlCommand command = new SqlCommand(query, connection);
try
{
connection.Open();
object result = command.ExecuteScalar();
if (result != null && result != DBNull.Value)
{
return Convert.ToInt32(result);
}
return 0;
}
catch (Exception ex)
{
Console.WriteLine($"Error getting product count: {ex.Message}");
return -1; // Indicate error
}
}
}
}
Executing Stored Procedures
To execute a stored procedure, set the CommandType
to StoredProcedure
and set the CommandText
to the name of the stored procedure. Parameters should be added appropriately.
using System;
using System.Data;
using System.Data.SqlClient;
public class StoredProcedureExample
{
private string connectionString = "YourConnectionStringHere";
public void GetOrderDetails(int orderId)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlCommand command = new SqlCommand("usp_GetOrderDetails", connection); // Stored procedure name
command.CommandType = CommandType.StoredProcedure;
// Add input parameter
command.Parameters.AddWithValue("@OrderID", orderId);
try
{
connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
Console.WriteLine($"Product: {reader["ProductName"]}, Quantity: {reader["Quantity"]}");
}
}
else
{
Console.WriteLine($"No details found for Order ID: {orderId}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error executing stored procedure: {ex.Message}");
}
}
}
}
When dealing with stored procedures that return multiple result sets, you can call ExecuteReader()
multiple times on the same IDataReader
object as long as the previous result set has been fully consumed.
Transactions
For operations that must be atomic (either all succeed or all fail), ADO.NET supports transactions. This ensures data integrity.
using System;
using System.Data;
using System.Data.SqlClient;
public class TransactionExample
{
private string connectionString = "YourConnectionStringHere";
public void TransferFunds(int fromAccountId, int toAccountId, decimal amount)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlTransaction transaction = null;
try
{
transaction = connection.BeginTransaction();
// Debit from account
string debitQuery = "UPDATE Accounts SET Balance = Balance - @Amount WHERE AccountID = @AccountID";
SqlCommand debitCommand = new SqlCommand(debitQuery, connection, transaction);
debitCommand.Parameters.AddWithValue("@Amount", amount);
debitCommand.Parameters.AddWithValue("@AccountID", fromAccountId);
debitCommand.ExecuteNonQuery();
// Credit to account
string creditQuery = "UPDATE Accounts SET Balance = Balance + @Amount WHERE AccountID = @AccountID";
SqlCommand creditCommand = new SqlCommand(creditQuery, connection, transaction);
creditCommand.Parameters.AddWithValue("@Amount", amount);
creditCommand.Parameters.AddWithValue("@AccountID", toAccountId);
creditCommand.ExecuteNonQuery();
// Commit the transaction
transaction.Commit();
Console.WriteLine("Funds transferred successfully.");
}
catch (Exception ex)
{
// Rollback the transaction if any error occurred
if (transaction != null)
{
transaction.Rollback();
}
Console.WriteLine($"Transaction failed: {ex.Message}");
}
}
}
}
Conclusion
Understanding how to execute commands effectively in ADO.NET is crucial for any .NET developer working with databases. By utilizing the appropriate methods like ExecuteNonQuery()
, ExecuteReader()
, and ExecuteScalar()
, and by employing parameterized queries and transactions, you can build robust, secure, and efficient data-driven applications.