.NET ProgrammingUnit 619 min read
ADO.NET: Database Access, Connections & CRUD Operations
Unit 6 of .NET Programming covers ADO.NET fundamentals—connecting to databases, executing queries, handling data with DataSet, DataAdapter, and DataReader, and implementing CRUD operations in C. Learn real-world applications, performance trade-offs, and security best practices for database access in .NET.
TAKEAWAYS:
- ADO.NET provides disconnected (
DataSet) and connected (DataReader) models for database access, each suited to different scenarios (e.g., offline apps vs. high-performance queries). - Connection strings authenticate and route your app to databases (SQL Server, MySQL, etc.), and parameterized queries prevent SQL injection attacks.
- CRUD operations (Create, Read, Update, Delete) are implemented using
SqlCommandwithExecuteNonQuery()(for DML) andExecuteReader()/ExecuteScalar()(for queries). - Transactions ensure atomicity in multi-step database operations (e.g., transferring money between bank accounts).
- Stored procedures improve security and performance by offloading logic to the database server.
- Error handling with
try-catchblocks is critical for graceful failure in database operations (e.g., network timeouts or constraint violations).
1. Introduction to ADO.NET
ADO.NET is a data access technology in .NET for interacting with databases. It sits between your C# application and databases (SQL Server, Oracle, MySQL, etc.) and provides:
- Data access components:
Connection,Command,DataAdapter,DataReader,DataSet. - Two models:
- Connected model: Uses
SqlConnectionandSqlDataReaderfor real-time, forward-only data access (efficient for large datasets). - Disconnected model: Uses
DataSetandDataAdapterto cache data locally (ideal for offline apps or complex operations).
- Connected model: Uses
Key Classes in ADO.NET
| Class | Purpose | Example Use Case |
|---|---|---|
SqlConnection |
Manages a connection to the database. | Opening/closing a database link. |
SqlCommand |
Executes SQL commands or stored procedures. | Inserting a new record. |
SqlDataAdapter |
Bridges DataSet and database (fills DataSet or updates database). |
Syncing offline data with a server. |
SqlDataReader |
Reads data forward-only (memory-efficient). | Fetching a large report. |
DataSet |
In-memory cache of data (tables, relationships, constraints). | Building a multi-table report. |
DataTable |
Represents a single table in a DataSet. |
Displaying customer orders in a grid. |
2. Connecting to a Database
To interact with a database, you first establish a connection using a connection string. A connection string includes:
- Server name (e.g.,
localhostor127.0.0.1). - Database name.
- Authentication credentials (username/password or Windows authentication).
- Connection protocol (e.g.,
SqlClientfor SQL Server,MySqlConnectionfor MySQL).
Example Connection String for SQL Server
string connectionString =
"Server=localhost;Database=Northwind;User Id=sa;Password=yourpassword;";
Visual: Connection String Anatomy
graph LR
A["Connection String"] --> B["Server=localhost"]
A --> C["Database=Northwind"]
A --> D["User Id=sa"]
A --> E["Password=yourpassword"]
A --> F["TrustServerCertificate=True"] <!-- For dev environments -->
B & C & D & E & F --> G["SqlConnection.Open()"]Steps to Open a Connection
- Create a
SqlConnectionobject. - Assign the connection string.
- Call
Open()to establish the connection. - Use the connection to execute commands.
- Always close the connection (or use
usingblock) to free resources.
using System.Data.SqlClient;
string connectionString = "Server=localhost;Database=Northwind;...";
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open(); // Opens the connection
Console.WriteLine("Connected to database!");
// Perform database operations here
} // Connection automatically closed
Why Use using?
- Ensures
connection.Close()is called even if an exception occurs. - Prevents connection leaks, which can exhaust database resources.
3. Executing Commands: SqlCommand
SqlCommand executes SQL statements or stored procedures. Key methods:
ExecuteNonQuery(): ForINSERT,UPDATE,DELETE(returns row count affected).ExecuteReader(): ForSELECTqueries (returns aSqlDataReader).ExecuteScalar(): For single-value queries (e.g.,SELECT COUNT(*)).
Example: Inserting a Record
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
string query = "INSERT INTO Customers (CustomerName, Email) VALUES (@name, @email)";
SqlCommand command = new SqlCommand(query, connection);
// Add parameters to prevent SQL injection
command.Parameters.AddWithValue("@name", "John Doe");
command.Parameters.AddWithValue("@email", "john@example.com");
int rowsAffected = command.ExecuteNonQuery();
Console.WriteLine($"{rowsAffected} row(s) inserted.");
}
Visual: Parameterized Query vs. SQL Injection
graph TD
A["User Input: '; DROP TABLE Customers; --"] --> B["Unsafe Query"]
B --> C["SQL Injection Executed"]
A --> D["Safe Query with Parameters"]
D --> E["Parameterized: @name = 'Hacker'"]
E --> F["Query: INSERT INTO Customers (Name) VALUES (@name)"]
F --> G["Safe Execution"]Why Parameterized Queries?
- Prevents SQL injection: User input is treated as data, not executable SQL.
- Improves performance: Reuses execution plans for repeated queries.
4. Reading Data: SqlDataReader vs. DataSet
A. SqlDataReader (Connected Model)
- Forward-only, read-only cursor.
- Memory-efficient (streams data row-by-row).
- Best for: Large datasets or real-time processing (e.g., logging, reporting).
Example: Fetching Customers
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
string query = "SELECT CustomerID, CustomerName FROM Customers";
SqlCommand command = new SqlCommand(query, connection);
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
Console.WriteLine($"ID: {reader["CustomerID"]}, Name: {reader["CustomerName"]}");
}
reader.Close(); // Explicit close (optional if using 'using')
}
Visual: SqlDataReader Data Flow
sequenceDiagram
participant App as C# Application
participant DB as SQL Server
App->>DB: Open Connection
App->>DB: Execute Query (SELECT)
loop While reader.Read()
DB-->>App: Return Row 1
App->>App: Process Data
DB-->>App: Return Row 2
App->>App: Process Data
...
end
App->>DB: Close Reader
App->>DB: Close ConnectionB. DataSet (Disconnected Model)
- In-memory cache of data (tables, relationships, constraints).
- Supports offline work (e.g., syncing data later).
- Best for: Complex operations (e.g., multi-table joins, sorting, filtering).
Example: Filling a DataSet
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
string query = "SELECT * FROM Customers";
SqlDataAdapter adapter = new SqlDataAdapter(query, connection);
DataSet dataSet = new DataSet();
adapter.Fill(dataSet, "Customers"); // Fills "Customers" table in DataSet
// Access data like a DataTable
DataTable customersTable = dataSet.Tables["Customers"];
foreach (DataRow row in customersTable.Rows)
{
Console.WriteLine(row["CustomerName"]);
}
}
Visual: DataSet Structure
classDiagram
class DataSet {
+Tables: DataTableCollection
+Fill() void
+WriteXml() void
}
class DataTable {
+Rows: DataRowCollection
+Columns: DataColumnCollection
+Select() DataRow[]
}
class DataRow {
+Item[] Fields
+BeginEdit() void
+AcceptChanges() void
}
DataSet "1" --> "1..*" DataTable
DataTable "1" --> "1..*" DataRowComparison: SqlDataReader vs. DataSet
| Feature | SqlDataReader |
DataSet |
|---|---|---|
| Model | Connected | Disconnected |
| Memory Usage | Low (streams data) | High (caches all data) |
| Navigation | Forward-only | Full (previous/next) |
| Editing | Read-only | Supports updates |
| Performance | Faster for large datasets | Slower for large data |
| Use Case | Real-time reporting, logging | Offline apps, complex operations |
5. CRUD Operations in ADO.NET
A. Create (Insert)
string insertQuery = "INSERT INTO Orders (OrderID, CustomerID, OrderDate) VALUES (@id, @cid, @date)";
using (SqlCommand command = new SqlCommand(insertQuery, connection))
{
command.Parameters.AddWithValue("@id", 1001);
command.Parameters.AddWithValue("@cid", 1);
command.Parameters.AddWithValue("@date", DateTime.Now);
command.ExecuteNonQuery();
}
B. Read (Select)
string selectQuery = "SELECT * FROM Orders WHERE CustomerID = @cid";
using (SqlCommand command = new SqlCommand(selectQuery, connection))
{
command.Parameters.AddWithValue("@cid", 1);
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
Console.WriteLine($"OrderID: {reader["OrderID"]}, Date: {reader["OrderDate"]}");
}
}
C. Update
string updateQuery = "UPDATE Orders SET Status = @status WHERE OrderID = @id";
using (SqlCommand command = new SqlCommand(updateQuery, connection))
{
command.Parameters.AddWithValue("@status", "Shipped");
command.Parameters.AddWithValue("@id", 1001);
command.ExecuteNonQuery();
}
D. Delete
string deleteQuery = "DELETE FROM Orders WHERE OrderID = @id";
using (SqlCommand command = new SqlCommand(deleteQuery, connection))
{
command.Parameters.AddWithValue("@id", 1001);
command.ExecuteNonQuery();
}
Visual: CRUD Workflow
flowchart TD
A["Start"] --> B["Create: INSERT"]
B --> C["Read: SELECT"]
C --> D["Update: UPDATE"]
D --> E["Delete: DELETE"]
E --> F["Loop or End"]6. Transactions
Transactions ensure atomicity (all operations succeed or fail together). Use SqlTransaction:
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlTransaction transaction = connection.BeginTransaction();
try
{
string withdrawQuery = "UPDATE Accounts SET Balance = Balance - 100 WHERE AccountID = 1";
string depositQuery = "UPDATE Accounts SET Balance = Balance + 100 WHERE AccountID = 2";
using (SqlCommand cmd1 = new SqlCommand(withdrawQuery, connection, transaction))
using (SqlCommand cmd2 = new SqlCommand(depositQuery, connection, transaction))
{
cmd1.ExecuteNonQuery();
cmd2.ExecuteNonQuery();
transaction.Commit(); // All good!
}
}
catch (Exception ex)
{
transaction.Rollback(); // Undo changes
Console.WriteLine($"Error: {ex.Message}");
}
}
Real-World Example: Bank Transfer
- Scenario: Transfer ₹1000 from Account A to Account B.
- Problem: Without a transaction, Account A might be debited but Account B not credited (inconsistent state).
- Solution: Use a transaction to ensure both updates happen or neither does.
7. Stored Procedures
Stored procedures are precompiled SQL scripts stored in the database. Benefits:
- Security: Reduce exposure of database schema.
- Performance: Compiled once, executed faster.
- Reusability: Call from multiple applications.
Example: Calling a Stored Procedure
string spQuery = "EXEC GetCustomerOrders @customerId";
using (SqlCommand command = new SqlCommand(spQuery, connection))
{
command.Parameters.AddWithValue("@customerId", 1);
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
Console.WriteLine($"Order: {reader["OrderID"]}, Amount: {reader["Amount"]}");
}
}
Visual: Stored Procedure Flow
sequenceDiagram
participant App as C# App
participant DB as SQL Server
App->>DB: EXEC GetCustomerOrders @customerId
DB-->>App: Returns Order1, Order2, ...
App->>App: Display Orders8. Error Handling
Database operations can fail due to:
- Network issues (connection timeouts).
- Constraint violations (e.g., duplicate key).
- Syntax errors (invalid SQL).
Best Practices:
- Use
try-catch-finallyblocks. - Log errors for debugging.
- Provide user-friendly messages.
try
{
connection.Open();
// Database operations here
}
catch (SqlException ex)
{
Console.WriteLine($"Database Error: {ex.Number} - {ex.Message}");
// Log to a file or error tracking system
}
catch (Exception ex)
{
Console.WriteLine($"General Error: {ex.Message}");
}
finally
{
connection.Close(); // Ensure connection is closed
}
In the Real World
ADO.NET powers critical systems in Nepal and globally. Here’s how companies use these concepts:
eSewa (Nepal)
- Idea Used: Transactions and Stored Procedures
- How: When you transfer money from your eSewa wallet to another user, the system uses a transaction to deduct from your balance and add to the recipient’s. Stored procedures validate the transfer (e.g., sufficient balance, correct recipient ID) before executing.
Khalti (Nepal)
- Idea Used: Parameterized Queries and
SqlDataReader - How: Khalti’s backend fetches user transaction history using
SqlDataReaderfor real-time data streaming. Parameterized queries ensure hackers can’t inject malicious SQL (e.g., stealing all user data).
- Idea Used: Parameterized Queries and
NTC (Nepal Telecommunications)
- Idea Used:
DataSetfor Offline Sync - How: NTC’s mobile app caches customer complaints in a
DataSetwhen offline. When the user reconnects, the app syncs changes back to the central database usingDataAdapter.
- Idea Used:
Daraz (Global)
- Idea Used: CRUD Operations and Transactions
- How: When you place an order, Daraz’s system:
- Creates a new order record (CRUD).
- Updates inventory levels (transaction to avoid overselling).
- Reads your shipping address from the database.
- If inventory is insufficient, the transaction rolls back, and you see an "out of stock" error.
Banking Apps (Global)
- Idea Used: Stored Procedures for Security
- How: Banks use stored procedures for sensitive operations like password changes or fund transfers. This hides the underlying SQL logic, reducing the risk of exposure if the app’s code is leaked.
Exam Tip
ADO.NET is a high-weightage unit in TU exams. Focus on these common exam patterns:
Short Questions (2-5 marks)
- Define
SqlConnection,DataAdapter, orDataSet. - Difference between
ExecuteNonQuery()andExecuteReader(). - When to use transactions.
- Define
Programming Questions (10-15 marks)
- Write code to:
- Connect to a database and execute a query.
- Insert/update/delete records with parameters.
- Fill a
DataSetor useSqlDataReader. - Handle exceptions in database operations.
- Trace the output for given inputs (e.g., show the
DataTableafter filling).
- Write code to:
Scenario-Based Questions (5-10 marks)
- Design a database access layer for a scenario (e.g., "A hospital management system needs to log patient visits").
- Explain how you’d use transactions for a bank transfer.
- Compare
SqlDataReaderandDataSetfor a given use case.
Common Pitfalls to Avoid:
- Forgetting to close connections (leads to resource leaks).
- Using string concatenation for SQL queries (SQL injection risk).
- Not handling exceptions (e.g.,
SqlException). - Confusing
DataSet(disconnected) withSqlDataReader(connected).
Pro Tip:
- Memorize the syntax for connection strings,
SqlCommandmethods, andDataAdapterusage. - Practice tracing code execution (e.g., what’s in the
DataTableafteradapter.Fill()?). - Know the advantages/disadvantages of connected vs. disconnected models.
Worked Example: Order Processing System
Scenario: A retail app needs to:
- Add a new order to the database.
- Update inventory.
- Send a confirmation email (simulated here).
Solution:
public void ProcessOrder(Order order)
{
string connectionString = "Server=localhost;Database=RetailDB;...";
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlTransaction transaction = connection.BeginTransaction();
try
{
// 1. Insert order
string insertOrderQuery = "INSERT INTO Orders (OrderID, CustomerID, TotalAmount) VALUES (@id, @cid, @amount)";
using (SqlCommand cmd = new SqlCommand(insertOrderQuery, connection, transaction))
{
cmd.Parameters.AddWithValue("@id", order.OrderID);
cmd.Parameters.AddWithValue("@cid", order.CustomerID);
cmd.Parameters.AddWithValue("@amount", order.TotalAmount);
cmd.ExecuteNonQuery();
}
// 2. Update inventory (for each item in the order)
foreach (var item in order.Items)
{
string updateInventoryQuery = "UPDATE Products SET Stock = Stock - @quantity WHERE ProductID = @pid";
using (SqlCommand cmd = new SqlCommand(updateInventoryQuery, connection, transaction))
{
cmd.Parameters.AddWithValue("@quantity", item.Quantity);
cmd.Parameters.AddWithValue("@pid", item.ProductID);
cmd.ExecuteNonQuery();
}
}
// 3. Simulate email sending (in a real app, this would be async)
Console.WriteLine($"Order {order.OrderID} processed. Email sent to {order.CustomerID}.");
transaction.Commit();
Console.WriteLine("Transaction committed successfully.");
}
catch (Exception ex)
{
transaction.Rollback();
Console.WriteLine($"Error processing order: {ex.Message}");
throw; // Re-throw for the caller to handle
}
}
}
Trace for Input:
| Step | Action | Output/State |
|---|---|---|
| Start | connection.Open() |
Connection open, transaction begins. |
| Insert Order | cmd.ExecuteNonQuery() for INSERT INTO Orders |
New row added to Orders table. |
| Update Inventory | Loop: cmd.ExecuteNonQuery() for each product |
Stock quantities reduced for each product in the order. |
Console.WriteLine() (simulated) |
"Order 1001 processed. Email sent to 5." | |
| Commit | transaction.Commit() |
Changes saved permanently. |
| Exception Case | If Stock goes negative during update, SqlException is thrown. |
Transaction rolled back; no changes saved. |
Real-World Tie-In: This mirrors how Daraz or Amazon process orders:
- Atomicity: If inventory update fails, the order isn’t created (no partial orders).
- Performance: Transactions batch multiple operations for efficiency.
- Security: Parameterized queries prevent SQL injection from malicious order data.
Based on the TU BIM syllabus for .NET Programming (IT275), unit 6.
Discussion
Loading…