Datasets and DataTables in ADO.NET
ADO.NET provides a rich set of classes for working with data. Among the most fundamental are DataSet and DataTable, which allow you to represent data in memory in a structured and relational manner. These classes are crucial for scenarios where you need to load data from a source, manipulate it offline, and then potentially update the source.
The DataSet Class
A DataSet object represents a complete collection of data, including related tables, constraints, and relationships. Think of it as an in-memory representation of a relational database. A DataSet can contain one or more DataTable objects, and these tables can be linked via DataRelation objects.
Key features of a DataSet:
- Multiple Tables: Can hold a collection of
DataTableobjects. - Schema Definition: Defines the structure of the data it contains.
- Constraints: Supports primary keys, foreign keys, and unique constraints.
- Relations: Allows you to define relationships between tables, enabling easy navigation.
- Row States: Tracks the state of each row (e.g.,
Added,Modified,Deleted,Unchanged), which is vital for updates. - Change Tracking: Records changes made to the data.
The DataTable Class
A DataTable object represents a single table of data in memory. It consists of a collection of DataRow objects and a DataColumn collection that defines the schema (columns and their data types).
Key features of a DataTable:
- Columns: Defined by
DataColumnobjects, specifying name, data type, and constraints. - Rows: Represented by
DataRowobjects, holding the actual data values for each record. - Primary Key: Can have a primary key defined to uniquely identify rows.
- Constraints: Supports unique constraints on columns.
- Filtering and Sorting: Allows you to filter and sort rows.
- Accept/Reject Changes: Methods to manage pending changes.
Working with DataSet and DataTable
You typically populate a DataSet or DataTable using a DataAdapter. The DataAdapter acts as a bridge between the DataSet/DataTable and a data source.
Example: Creating and Populating a DataTable
Here's a basic example of how to create a DataTable and add some data to it programmatically:
using System;
using System.Data;
public class DataTableExample
{
public static void Main(string[] args)
{
// Create a new DataTable
DataTable customersTable = new DataTable("Customers");
// Define columns
DataColumn idColumn = new DataColumn("CustomerID", typeof(int));
idColumn.AutoIncrement = true;
idColumn.AutoIncrementSeed = 1;
idColumn.AutoIncrementStep = 1;
idColumn.ReadOnly = true;
customersTable.Columns.Add(idColumn);
DataColumn nameColumn = new DataColumn("Name", typeof(string));
customersTable.Columns.Add(nameColumn);
DataColumn cityColumn = new DataColumn("City", typeof(string));
customersTable.Columns.Add(cityColumn);
// Set primary key
customersTable.PrimaryKey = new DataColumn[] { customersTable.Columns["CustomerID"] };
// Add rows
DataRow row1 = customersTable.NewRow();
row1["Name"] = "Alice Smith";
row1["City"] = "New York";
customersTable.Rows.Add(row1);
DataRow row2 = customersTable.NewRow();
row2["Name"] = "Bob Johnson";
row2["City"] = "Los Angeles";
customersTable.Rows.Add(row2);
// Add another row directly
customersTable.Rows.Add(new object[] { null, "Charlie Brown", "Chicago" });
// Display data
Console.WriteLine("--- Customer Data ---");
foreach (DataRow row in customersTable.Rows)
{
Console.WriteLine($"ID: {row["CustomerID"]}, Name: {row["Name"]}, City: {row["City"]}");
}
// Find a row
DataRow foundRow = customersTable.Rows.Find(2);
if (foundRow != null)
{
Console.WriteLine($"\nFound row with ID 2: {foundRow["Name"]}, {foundRow["City"]}");
}
}
}
Example: Using a DataSet with Multiple Tables
A DataSet is ideal for representing related data from multiple tables. You can load them and define relations.
using System;
using System.Data;
public class DataSetExample
{
public static void Main(string[] args)
{
// Create a DataSet
DataSet companyData = new DataSet("Company");
// Create Products table
DataTable productsTable = new DataTable("Products");
productsTable.Columns.Add("ProductID", typeof(int)).AutoIncrement = true;
productsTable.Columns.Add("ProductName", typeof(string));
productsTable.PrimaryKey = new DataColumn[] { productsTable.Columns["ProductID"] };
// Create Orders table
DataTable ordersTable = new DataTable("Orders");
ordersTable.Columns.Add("OrderID", typeof(int)).AutoIncrement = true;
ordersTable.Columns.Add("ProductID", typeof(int)); // Foreign key
ordersTable.Columns.Add("OrderDate", typeof(DateTime));
ordersTable.PrimaryKey = new DataColumn[] { ordersTable.Columns["OrderID"] };
// Add tables to the DataSet
companyData.Tables.Add(productsTable);
companyData.Tables.Add(ordersTable);
// Populate Products table
productsTable.Rows.Add(null, "Laptop");
productsTable.Rows.Add(null, "Keyboard");
productsTable.Rows.Add(null, "Mouse");
// Populate Orders table
// Assuming ProductID 1 is Laptop, 2 is Keyboard, 3 is Mouse
ordersTable.Rows.Add(null, 1, DateTime.Now.AddDays(-5)); // Laptop order
ordersTable.Rows.Add(null, 2, DateTime.Now.AddDays(-3)); // Keyboard order
ordersTable.Rows.Add(null, 1, DateTime.Now.AddDays(-1)); // Another Laptop order
// Define a relation between Products and Orders
DataColumn parentColumn = companyData.Tables["Products"].Columns["ProductID"];
DataColumn childColumn = companyData.Tables["Orders"].Columns["ProductID"];
companyData.Relations.Add("ProductOrders", parentColumn, childColumn);
// Accessing related data
Console.WriteLine("--- Orders for Laptops ---");
DataRow laptopRow = companyData.Tables["Products"].Rows.Find(1); // Find the Laptop row
if (laptopRow != null)
{
// Get related child rows (Orders)
DataRow[] laptopOrders = laptopRow.GetChildRows("ProductOrders");
foreach (DataRow orderRow in laptopOrders)
{
Console.WriteLine($"Order ID: {orderRow["OrderID"]}, Date: {orderRow["OrderDate"]}");
}
}
}
}
DataTable.Select() for efficient filtering and querying of data already loaded into memory.
DataRowState and Change Tracking
DataRow objects maintain a state indicating whether they are new, modified, deleted, or unchanged. This is crucial for synchronizing changes back to the data source using a DataAdapter.
DataRowState.Added: The row was added to theDataTable.DataRowState.Modified: The row's data was changed since it was loaded or its changes were last accepted.DataRowState.Deleted: The row was deleted from theDataTable.DataRowState.Unchanged: The row has not been modified since it was loaded or its changes were last accepted.
The AcceptChanges() method commits all pending changes (added, modified, deleted rows) and sets their state to Unchanged. The RejectChanges() method discards all pending changes.