Skip to main content

ADO.NET

ADO.NET is the foundational data access technology in .NET that provides low-level access to data sources. It offers maximum control and performance but requires more code and careful resource management.

Official Definition

ADO.NET (ActiveX Data Objects for .NET) is a set of classes that expose data access services for .NET Framework and .NET Core programmers. It provides a bridge between the front-end controls and the back-end database.

Usage

ADO.NET is included in .NET by default. For SQL Server, install the Microsoft.Data.SqlClient package:

dotnet add package Microsoft.Data.SqlClient

Basic components include Connection, Command, DataReader, and DataAdapter classes.

Use Cases

  • Maximum performance requirements
  • Fine-grained control over database operations
  • Legacy system integration
  • Complex data processing scenarios
  • Bulk data operations
  • Custom connection pooling requirements

When to Use vs When Not to Use

Use ADO.NET when:

  • Absolute maximum performance is required
  • Working with legacy databases or systems
  • Need precise control over connection management
  • Implementing custom data access patterns
  • Building data access frameworks
  • Processing large datasets efficiently

Don't use ADO.NET when:

  • Development speed is prioritized
  • Team lacks database expertise
  • Building standard CRUD applications
  • Need object-relational mapping features
  • Want to avoid boilerplate code

Market Alternatives and Market Adoption

ADO.NET remains the foundation for all .NET data access technologies. While higher-level tools like Entity Framework and Dapper are more commonly used for application development, ADO.NET is still essential for framework development and high-performance scenarios.

Pros and Cons

Pros:

  • Maximum performance and control
  • Direct access to all database features
  • Minimal memory footprint
  • No abstraction overhead
  • Supports all data types
  • Battle-tested and stable

Cons:

  • Verbose and repetitive code
  • Manual resource management required
  • No object-relational mapping
  • Prone to SQL injection if not careful
  • Requires extensive database knowledge
  • Time-consuming development

Sample Usage

// Package reference: <PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.1" />
using Microsoft.Data.SqlClient;
using System.Data;

// Models
public class Room
{
public int Id { get; set; }
public string RoomNumber { get; set; } = string.Empty;
public string RoomType { get; set; } = string.Empty;
public decimal PricePerNight { get; set; }
public bool IsAvailable { get; set; }
}

public class RoomRepository
{
private readonly string _connectionString;

public RoomRepository(string connectionString)
{
_connectionString = connectionString;
}

// Query with DataReader
public async Task<Room?> GetRoomByIdAsync(int roomId)
{
using var connection = new SqlConnection(_connectionString);
await connection.OpenAsync();

using var command = new SqlCommand(
"SELECT Id, RoomNumber, RoomType, PricePerNight, IsAvailable FROM Rooms WHERE Id = @Id",
connection);

command.Parameters.Add(new SqlParameter("@Id", SqlDbType.Int) { Value = roomId });

using var reader = await command.ExecuteReaderAsync();

if (await reader.ReadAsync())
{
return new Room
{
Id = reader.GetInt32("Id"),
RoomNumber = reader.GetString("RoomNumber"),
RoomType = reader.GetString("RoomType"),
PricePerNight = reader.GetDecimal("PricePerNight"),
IsAvailable = reader.GetBoolean("IsAvailable")
};
}

return null;
}

// Query multiple records
public async Task<List<Room>> GetAvailableRoomsAsync()
{
var rooms = new List<Room>();

using var connection = new SqlConnection(_connectionString);
await connection.OpenAsync();

using var command = new SqlCommand(
"SELECT Id, RoomNumber, RoomType, PricePerNight, IsAvailable FROM Rooms WHERE IsAvailable = 1",
connection);

using var reader = await command.ExecuteReaderAsync();

while (await reader.ReadAsync())
{
rooms.Add(new Room
{
Id = reader.GetInt32("Id"),
RoomNumber = reader.GetString("RoomNumber"),
RoomType = reader.GetString("RoomType"),
PricePerNight = reader.GetDecimal("PricePerNight"),
IsAvailable = reader.GetBoolean("IsAvailable")
});
}

return rooms;
}

// Insert with transaction
public async Task<int> CreateRoomAsync(Room room)
{
using var connection = new SqlConnection(_connectionString);
await connection.OpenAsync();

using var transaction = connection.BeginTransaction();

try
{
using var command = new SqlCommand(
@"INSERT INTO Rooms (RoomNumber, RoomType, PricePerNight, IsAvailable)
VALUES (@RoomNumber, @RoomType, @PricePerNight, @IsAvailable);
SELECT CAST(SCOPE_IDENTITY() as int);",
connection, transaction);

command.Parameters.AddRange(new[]
{
new SqlParameter("@RoomNumber", SqlDbType.VarChar, 10) { Value = room.RoomNumber },
new SqlParameter("@RoomType", SqlDbType.VarChar, 50) { Value = room.RoomType },
new SqlParameter("@PricePerNight", SqlDbType.Decimal) { Value = room.PricePerNight },
new SqlParameter("@IsAvailable", SqlDbType.Bit) { Value = room.IsAvailable }
});

var newId = (int)await command.ExecuteScalarAsync();

await transaction.CommitAsync();
return newId;
}
catch
{
await transaction.RollbackAsync();
throw;
}
}

// Bulk operations with DataAdapter
public async Task<List<Room>> GetRoomsWithDataSetAsync()
{
using var connection = new SqlConnection(_connectionString);
var adapter = new SqlDataAdapter("SELECT * FROM Rooms", connection);
var dataSet = new DataSet();

adapter.Fill(dataSet, "Rooms");

var rooms = new List<Room>();
foreach (DataRow row in dataSet.Tables["Rooms"].Rows)
{
rooms.Add(new Room
{
Id = (int)row["Id"],
RoomNumber = row["RoomNumber"].ToString()!,
RoomType = row["RoomType"].ToString()!,
PricePerNight = (decimal)row["PricePerNight"],
IsAvailable = (bool)row["IsAvailable"]
});
}

return rooms;
}

// Stored procedure execution
public async Task UpdateRoomAvailabilityAsync(int roomId, bool isAvailable)
{
using var connection = new SqlConnection(_connectionString);
await connection.OpenAsync();

using var command = new SqlCommand("sp_UpdateRoomAvailability", connection)
{
CommandType = CommandType.StoredProcedure
};

command.Parameters.AddRange(new[]
{
new SqlParameter("@RoomId", SqlDbType.Int) { Value = roomId },
new SqlParameter("@IsAvailable", SqlDbType.Bit) { Value = isAvailable }
});

await command.ExecuteNonQueryAsync();
}
}

// Usage example
var connectionString = "Server=localhost;Database=HotelDB;Integrated Security=true;";
var roomRepository = new RoomRepository(connectionString);

// Create and retrieve a room
var newRoom = new Room
{
RoomNumber = "101",
RoomType = "Standard",
PricePerNight = 99.99m,
IsAvailable = true
};

var roomId = await roomRepository.CreateRoomAsync(newRoom);
var retrievedRoom = await roomRepository.GetRoomByIdAsync(roomId);
var availableRooms = await roomRepository.GetAvailableRoomsAsync();