Error Handling in ADO.NET

Effective error handling is crucial for robust applications. ADO.NET provides mechanisms to detect, report, and manage errors that can occur during data access operations.

Understanding ADO.NET Exceptions

When an error occurs in ADO.NET, it typically throws an exception. The most common exception type is System.Data.SqlClient.SqlException (or the equivalent for other data providers), which contains detailed information about the database error.

Other exceptions you might encounter include:

Handling Exceptions with try-catch Blocks

The standard .NET approach to exception handling is using try-catch blocks. This allows you to gracefully handle errors without terminating your application.


using System;
using System.Data;
using System.Data.SqlClient;

public class ErrorHandlingExample
{
    public static void Main(string[] args)
    {
        string connectionString = "Your_Connection_String_Here";
        string queryString = "SELECT * FROM NonExistentTable;"; // Intentionally bad query

        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            using (SqlCommand command = new SqlCommand(queryString, connection))
            {
                try
                {
                    connection.Open();
                    SqlDataReader reader = command.ExecuteReader();
                    while (reader.Read())
                    {
                        // Process data
                    }
                    reader.Close();
                }
                catch (SqlException ex)
                {
                    Console.WriteLine($"SQL Error: {ex.Message}");
                    foreach (SqlError error in ex.Errors)
                    {
                        Console.WriteLine($"  - Number: {error.Number}, Message: {error.Message}");
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"An unexpected error occurred: {ex.Message}");
                }
            }
        }
    }
}
            

The SqlException Object

The SqlException object provides valuable details about the error:

Important: Always catch specific exception types (like SqlException) before catching the general Exception class to handle errors more precisely.

Handling Data Errors (DataTable and DataSet)

When performing operations on DataTable and DataSet objects, errors can occur, especially when enforcing constraints or during data updates.

RowError Property

Each DataRow has a RowError property. You can set this property to provide a custom error message for a specific row. This is often used during validation before committing changes.


DataRow row = dataTable.Rows[0];
if (!isValidRow(row))
{
    row.RowError = "This row contains invalid data.";
}
            

Errors Collection

A DataTable has an Errors collection that can store DataError objects. This collection is populated when constraints are violated or other data-related errors occur.


try
{
    dataTable.EndEdit(); // Attempt to commit changes
}
catch (ConstraintException ce)
{
    Console.WriteLine($"Constraint Violation: {ce.Message}");
    foreach (DataRow row in dataTable.Rows)
    {
        if (row.HasErrors)
        {
            Console.WriteLine($"Row with RowState {row.RowState} has errors:");
            Console.WriteLine($"  RowError: {row.RowError}");
            // You can also iterate through column errors if available
        }
    }
}
            
Example: Using DataTable.RejectChanges() and DataTable.ClearErrors() to reset rows that have errors.

Optimistic Concurrency

Optimistic concurrency is a strategy where you assume that data conflicts are rare. When updating records, you check if the data has changed since it was last read. If it has, an update conflict occurs.

ADO.NET handles this with the DBConcurrencyException. When this exception is caught, you can implement strategies to resolve the conflict, such as:


try
{
    // Attempt to update the data using the DataAdapter
    dataAdapter.Update(dataSet.Tables["MyTable"]);
}
catch (DBConcurrencyException ex)
{
    Console.WriteLine("Concurrency conflict detected.");
    // Handle the conflict, e.g., by accessing ex.Row for details
    // You might need to refresh data and re-apply changes.
}
            
Tip: For optimistic concurrency, ensure your `DataAdapter` has appropriate `SelectCommand` to fetch original values and that the `UpdateCommand`, `InsertCommand`, and `DeleteCommand` include `WHERE` clauses that check original values (e.g., based on timestamp or version columns).

Best Practices for ADO.NET Error Handling