ADO.NET Errors and Exceptions

Last Updated: October 26, 2023

Introduction

Understanding and handling errors is a critical aspect of developing robust ADO.NET applications. ADO.NET, like other .NET Framework components, uses the exception-handling mechanism to report errors that occur during data access operations.

This document outlines the common types of errors you might encounter when working with ADO.NET and provides guidance on how to effectively handle them using C# and VB.NET.

Common ADO.NET Exceptions

Several exception classes are commonly encountered in ADO.NET. The most fundamental is the System.Exception class, but more specific exceptions provide richer information.

  • System.Data.Common.DbException: The base class for all exceptions thrown by ADO.NET data providers.
  • System.Data.SqlClient.SqlException: Specific exceptions thrown by the SQL Server .NET data provider.
  • System.Data.OleDb.OleDbException: Specific exceptions thrown by the OLE DB .NET data provider.
  • System.Data.Odbc.OdbcException: Specific exceptions thrown by the ODBC .NET data provider.
  • System.ArgumentException: Thrown when a method is passed an invalid argument.
  • System.InvalidOperationException: Thrown when an operation is performed on an object that is in an invalid state.
  • System.OutOfMemoryException: Thrown when the system runs out of memory.

Handling Exceptions

The standard .NET exception handling block, consisting of try, catch, and finally, is used to manage ADO.NET exceptions.

The try-catch Block

Wrap your ADO.NET code in a try block and catch specific exceptions in catch blocks. This allows you to gracefully handle errors without crashing your application.

Best Practice: Always catch the most specific exception first. For example, catch SqlException before DbException.

Example (C#)


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

public class DataAccess
{
    public void GetData(string connectionString)
    {
        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            try
            {
                connection.Open();
                string query = "SELECT * FROM NonExistentTable"; // Intentional error

                using (SqlCommand command = new SqlCommand(query, connection))
                {
                    using (SqlDataReader reader = command.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            // Process data
                        }
                    }
                }
            }
            catch (SqlException sqlEx)
            {
                Console.WriteLine($"SQL Error: {sqlEx.Message}");
                // Log the error, display a user-friendly message, etc.
            }
            catch (InvalidOperationException invOpEx)
            {
                Console.WriteLine($"Operation Error: {invOpEx.Message}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"An unexpected error occurred: {ex.Message}");
            }
            finally
            {
                if (connection.State == ConnectionState.Open)
                {
                    connection.Close();
                }
            }
        }
    }
}
                        

Example (VB.NET)


Imports System
Imports System.Data
Imports System.Data.SqlClient

Public Class DataAccess
    Public Sub GetData(connectionString As String)
        Dim connection As SqlConnection = Nothing
        Try
            connection = New SqlConnection(connectionString)
            connection.Open()
            Dim query As String = "SELECT * FROM NonExistentTable" ' Intentional error

            Dim command As New SqlCommand(query, connection)
            Dim reader As SqlDataReader = command.ExecuteReader()

            While reader.Read()
                ' Process data
            End While

            reader.Close()
            command.Dispose()

        Catch sqlEx As SqlException
            Console.WriteLine($"SQL Error: {sqlEx.Message}")
            ' Log the error, display a user-friendly message, etc.
        Catch invOpEx As InvalidOperationException
            Console.WriteLine($"Operation Error: {invOpEx.Message}")
        Catch ex As Exception
            Console.WriteLine($"An unexpected error occurred: {ex.Message}")
        Finally
            If Not connection Is Nothing AndAlso connection.State = ConnectionState.Open Then
                connection.Close()
                connection.Dispose()
            End If
        End Try
    End Sub
End Class
                        

The finally Block

The finally block ensures that cleanup code (like closing database connections or releasing resources) is executed regardless of whether an exception occurred or not. Using the using statement (C#) or Using block (VB.NET) is often a more concise way to ensure resource disposal.

Exception Properties

ADO.NET exceptions, especially provider-specific ones like SqlException, offer properties that provide valuable details about the error:

  • Message: A description of the error.
  • Number / ErrorCode: A numeric error code specific to the data provider.
  • LineNumber: The line number in the SQL batch where the error occurred (for SQL Server).
  • Procedure: The name of the stored procedure or function where the error occurred (for SQL Server).
  • Server: The name of the server where the error occurred.
  • Source: The name of the object that raised the error.
Tip: Inspecting these properties can significantly aid in diagnosing and resolving database-related issues.

Handling Data Set Errors

When using DataSet objects, errors can arise during data loading or manipulation. The DataSet itself has an Errors property that can hold an exception, and rows can have an RowError property.

Example (C#)


// Assuming 'ds' is a DataSet that has been populated
if (ds.HasErrors)
{
    foreach (DataTable dt in ds.Tables)
    {
        foreach (DataRow dr in dt.Rows)
        {
            if (dr.HasErrors)
            {
                Console.WriteLine($"Row error in table '{dt.TableName}': {dr.RowError}");
                // You can also access specific column errors
                foreach (DataColumn col in dt.Columns)
                {
                    if (dr.GetColumnError(col) != string.Empty)
                    {
                        Console.WriteLine($"  Column '{col.ColumnName}' error: {dr.GetColumnError(col)}");
                    }
                }
            }
        }
    }
}
                        

Best Practices for Error Handling

  • Be Specific: Catch specific exceptions when possible.
  • Log Errors: Implement a robust logging mechanism to record errors for debugging and auditing.
  • Provide User Feedback: Display clear and helpful messages to the user without revealing sensitive system details.
  • Use using Statements: Ensure proper disposal of resources like connections and commands.
  • Handle Concurrency Issues: Be aware of potential errors arising from multiple users accessing data simultaneously.
  • Validate Input: Prevent errors by validating user input before executing database operations.