Back to: ADO.NET Core Tutorial For Beginners and Professionals
ADO.NET Core SqlDataAdapter Class with Examples
In this article, I will explain how to use the ADO.NET Core SQLDataAdapter class, offer detailed real-time examples, including synchronous and asynchronous operations, and demonstrate handling stored procedures. Please read our previous article discussing the ADO.NET Core SqlDataReader Class. In modern .NET Core applications, ADO.NET remains a robust option for data access. The SQLDataAdapter class is a bridge between a DataSet and SQL Server for retrieving and saving data.Ā
What is the ADO.NET Core SqlDataAdapter Class?
The SqlDataAdapter class in ADO.NET Core acts as a bridge between a DataSet or a DataTable and a SQL Server database. The SqlDataAdapter provides a set of methods and properties to fetch data from a database, populate it into a DataSet or DataTable, and synchronize changes back to the database. This class is especially valuable when working with disconnected data, enabling developers to manipulate data locally and then push those changes back to the data source in a structured manner by:
- Data from the database is filled into the DataSet/DataTable using the Fill method.
- Persisting changes in the DataSet/DataTable back to the database using the Update method.
Note: The adapter uses four command properties, SelectCommand, InsertCommand, UpdateCommand, and DeleteCommand, to execute SQL statements for various operations.
How Do We Create an Instance of the SqlDataAdapter Class in ADO.NET Core?
Creating an instance of the SqlDataAdapter in ADO.NET Core is straightforward. You can either use the default constructor and assign a SelectCommand later or supply the command text and connection to the constructor. You can even create a SqlCommand object and pass it on to the constructor. For a better understanding, please have a look at the following image. Here, conn is an instance of SqlConnection that connects to your SQL Server database.
Database and Dummy Data Setup in SQL Server
Please execute the following SQL script in SQL Server Management Studio (SSMS) or any SQL Server tool to create a sample database called ECommerceDB with two tables, Customers and Orders, and insert dummy data.
-- Create the demo database CREATE DATABASE ECommerceDB ; GO USE ECommerceDB ; GO -- Create the Customers table CREATE TABLE Customers ( CustomerID INT PRIMARY KEY IDENTITY(1,1), FirstName NVARCHAR(50), LastName NVARCHAR(50), Email NVARCHAR(100) ); GO -- Create the Orders table CREATE TABLE Orders ( OrderID INT PRIMARY KEY IDENTITY(1,1), CustomerID INT FOREIGN KEY REFERENCES Customers(CustomerID), OrderDate DATETIME, OrderAmount DECIMAL(10,2) ); GO -- Insert dummy data into Customers INSERT INTO Customers (FirstName, LastName, Email) VALUES ('John', 'Doe', 'john.doe@example.com'), ('Jane', 'Smith', 'jane.smith@example.com'), ('Alice', 'Johnson', 'alice.johnson@example.com'), ('Sara', 'Taylor', 'sara.taylor@example.com'), ('James', 'Deo', 'james.deo@example.com'); GO -- Insert dummy data into Orders INSERT INTO Orders (CustomerID, OrderDate, OrderAmount) VALUES (1, GETDATE(), 100.00), (2, GETDATE(), 150.50), (1, GETDATE(), 200.75), (3, GETDATE(), 80.25); -- No Orders for Sara and James as we will demonstrate the delete functionalities GO -- Stored Procedure to Return Customer Orders based on the CustomerID CREATE PROCEDURE GetCustomerOrders @CustomerID INT AS BEGIN -- Retrieve orders for the specified CustomerID SELECT OrderID, CustomerID, OrderDate, OrderAmount FROM Orders WHERE CustomerID = @CustomerID; END GO
The above script creates a relational database structure that will help illustrate how SqlDataAdapter works with multiple related tables.
What is the SqlDataAdapter Fill Method?
The Fill method of SqlDataAdapter executes the SelectCommand (defined in its SelectCommand property or passed via the constructor) against the database, and it then populates a DataTable or DataSet with the rows returned by the query. This method makes it simple to work with disconnected data. Internally, it:
- Opens the connection (if it is not already open).
- Executes the command to fetch data from the database.
- Loads the result into memory within the DataSet/DataTable.
- Closes the connection (if the adapter opened it).
Note: Internally, the data adapter creates a DbDataReader to fetch rows from the database. Then, it loads those rows into the in-memory DataTable. Once the data is in-memory, you can work with it in a disconnected fashion.
Using the Fill Method:
Let us see how to fill a DataTable with data from the database table using the SqlDataAdapter.Fill method. We want to retrieve all customers from the Customers table, display them on the console, and confirm how many rows were fetched using the Fill method.
using System.Data; using Microsoft.Data.SqlClient; namespace SqlDataAdapterExample { public class Program { static void Main(string[] args) { // Connection string to the ECommerceDB database. string connectionString = "Server=LAPTOP-6P5NK25R\\SQLSERVER2022DEV;Database=ECommerceDB;Trusted_Connection=True;TrustServerCertificate=True;"; // The query we want to execute string selectQuery = "SELECT CustomerID, FirstName, LastName, Email FROM Customers"; // Create an Empty DataTable to hold the data retrieved from the database. DataTable dtCustomers = new DataTable(); try { // Create a SqlConnection using the provided connection string. using (SqlConnection connection = new SqlConnection(connectionString)) { // Create a SqlDataAdapter with the select query and connection. using (SqlDataAdapter adapter = new SqlDataAdapter(selectQuery, connection)) { // Fill the DataTable with the results of the query int rowsFetched = adapter.Fill(dtCustomers); // Fill method returns the number of rows successfully added to the DataTable Console.WriteLine($"Number of rows fetched: {rowsFetched}"); // Display the fetched customer data. Console.WriteLine("Customers Data:"); // Loop through each DataRow in the DataTable and display the data. foreach (DataRow row in dtCustomers.Rows) { Console.WriteLine($"ID: {row["CustomerID"]}, Name: {row["FirstName"]} {row["FirstName"]}, Email: {row["Email"]}"); } } } } catch (Exception ex) { // Handle any errors that occur during database operations Console.WriteLine($"Error: {ex.Message}"); } Console.ReadKey(); } } }
Code Explanation:
- Connection String Setup: Connects to the ECommerceDB database hosted on SQL Server.
- DataTable Initialization: The DataTable (dtCustomers) is where the adapter fills the data.
- SqlDataAdapter Creation: The adapter is instantiated with a SELECT query and a connection.
- Fill Method: The adapterās Fill method fetches the data from the database and loads it into the DataTable.
- Use Data in Disconnected Mode: We loop through the rows in the DataTable and display them on the console.
Output:
What is the SqlDataAdapter Update Method?
The SqlDataAdapter Update method allows us to synchronize changes from our DataTable (in-memory) back to the SQL Server database. If we edit, add, or delete rows in the DataTable, calling Update will persist those changes in the actual database. Internally, the Update method processes each changed row in the DataTable and calls the appropriate command (InsertCommand, UpdateCommand, or DeleteCommand) to commit changes in the database.
How Does It Work Internally?
The Update method checks each row in the DataTable for changes (Modified, Added, Deleted, etc.).
- If a row is marked as Added, the InsertCommand is executed.
- If a row is marked as Modified, the UpdateCommand is executed.
- If a row is marked as Deleted, the DeleteCommand is executed.
It compares the original values with the current data and issues corresponding INSERT, UPDATE, or DELETE commands. When using the SqlCommandBuilder, the adapter can auto-generate these commands based on the SelectCommand.
Example Using Auto-Generated SQL Statements with SqlDataAdapter Update Method
Let us see one example to understand this concept. We will select data from the Customers table, modify one recordās FirstName, add a new record, and delete an existing record, all within the DataTable. Then, we will call the adapter.Update(…) to persist these changes back to the database. The following example uses SqlCommandBuilder to generate the INSERT, UPDATE, and DELETE commands automatically.
using System.Data; using Microsoft.Data.SqlClient; namespace SqlDataAdapterUpdateExample { public class Program { static void Main(string[] args) { // Connection string to the ECommerceDB database. string connectionString = "Server=LAPTOP-6P5NK25R\\SQLSERVER2022DEV;Database=ECommerceDB;Trusted_Connection=True;TrustServerCertificate=True;"; // The query we want to execute. string selectQuery = "SELECT CustomerID, FirstName, LastName, Email FROM Customers"; // Create an empty DataTable to hold the data. DataTable dtCustomers = new DataTable(); try { using (SqlConnection connection = new SqlConnection(connectionString)) { // Create a SqlDataAdapter using the SELECT command. using (SqlDataAdapter adapter = new SqlDataAdapter(selectQuery, connection)) { // Use SqlCommandBuilder to automatically generate INSERT, UPDATE, and DELETE commands. SqlCommandBuilder commandBuilder = new SqlCommandBuilder(adapter); // Fill the DataTable with current data from the database. adapter.Fill(dtCustomers); Console.WriteLine("Before changes:"); foreach (DataRow row in dtCustomers.Rows) { // Display each customer's details. Console.WriteLine($"ID: {row["CustomerID"]}, Name: {row["FirstName"]} {row["LastName"]}, Email: {row["Email"]}"); } // --- Modify Operation --- // Update the FirstName of the first row (if available). if (dtCustomers.Rows.Count > 0) { dtCustomers.Rows[0]["FirstName"] = "UpdatedFirstName"; } // --- Add Operation --- // Create a new row and set its field values. DataRow newRow = dtCustomers.NewRow(); newRow["FirstName"] = "NewFirstName"; newRow["LastName"] = "NewLastName"; newRow["Email"] = "new.customer@example.com"; dtCustomers.Rows.Add(newRow); // --- Delete Operation --- // Delete an existing row. if (dtCustomers.Rows.Count > 0) { //Deleting the Customer with CustomerID = 4 DataRow[] rowsToDelete = dtCustomers.Select("CustomerID = 4"); if (rowsToDelete.Length > 0) { rowsToDelete[0].Delete(); // Mark this row for deletion } } // Propagate all changes (update, insert, delete) back to the database. int affectedRows = adapter.Update(dtCustomers); Console.WriteLine("\nDatabase update executed. Rows affected: " + affectedRows); // Clear the DataTable and re-fill it to verify the changes. dtCustomers.Clear(); adapter.Fill(dtCustomers); Console.WriteLine("\nAfter changes:"); foreach (DataRow row in dtCustomers.Rows) { Console.WriteLine($"ID: {row["CustomerID"]}, Name: {row["FirstName"]} {row["LastName"]}, Email: {row["Email"]}"); } } } } catch (Exception ex) { // Handle any errors that occur during the database operations. Console.WriteLine($"Error: {ex.Message}"); } Console.ReadKey(); } } }
Code Explanation:
- SqlDataAdapter & DataTable: The SqlDataAdapter is created with a SELECT command to retrieve data from the Customers table. The data is then loaded into a DataTable, which acts as an in-memory representation of the database table.
- SqlCommandBuilder: A SqlCommandBuilder is used to automatically generate the INSERT, UPDATE, and DELETE commands based on the original SELECT command. This simplifies the process so that you do not need to manually write SQL statements for each operation.
- Modifying Data:
- Update Operation: The first rowās FirstName is modified from its original value to “UpdatedFirstName”.
- Add Operation: A new DataRow is created using dtCustomers.NewRow(). Its fields (e.g., FirstName, LastName, Email) are set to new values, and the row is added to the DataTable using Rows.Add(newRow).
- Delete Operation: An existing row (in this example, the second row) is marked for deletion using the Delete() method. Itās important that this row existed prior to the add operation to avoid deleting the new record if that is not intended.
- Update Method: The adapter.Update(dtCustomers) call looks at the changes (modified, new, and deleted rows) in the DataTable and executes the appropriate SQL commands against the database. The number of rows affected by these operations is returned and printed to the console.
- Verification: After propagating the changes, the DataTable is cleared and re-filled using the Fill method to verify and display the final state of the Customers table.
Output:
SqlDataAdapter SelectCommand, InsertCommand, UpdateCommand, and DeleteCommand Properties
We need to set these commands manually when we want full control over the SQL statements. However, if you do not set them, you can use SqlCommandBuilder to auto-generate them, as shown in the previous example. Then, the Update method of the SqlDataAdapter uses these commands to perform the Select, Update, Insert, and Delete operations.
- SelectCommand: The Fill method uses this command to select data from the database. It is typically a SELECT statement.
- InsertCommand: The Update method uses this command to insert new rows from the DataTable into the database.
- UpdateCommand: This command is used by the Update method to update modified rows in the database.
- DeleteCommand: This command is used by the Update method to delete rows from the database.
Example: Explicitly Defining Command Properties
Let us see an example to understand how to assign and use custom SelectCommand, InsertCommand, UpdateCommand, and DeleteCommand properties with a SqlDataAdapter in a .NET Core Console Application. The following example code is self-explained, so please read the comment lines for a better understanding.
using System.Data; using Microsoft.Data.SqlClient; namespace SqlDataAdapterCommandsExample { public class Program { static void Main(string[] args) { // Connection string to the ECommerceDB database. string connectionString = "Server=LAPTOP-6P5NK25R\\SQLSERVER2022DEV;Database=ECommerceDB;Trusted_Connection=True;TrustServerCertificate=True;"; // Create an Empty DataTable to hold the data. DataTable dtCustomers = new DataTable(); try { using (SqlConnection connection = new SqlConnection(connectionString)) { // Create the SqlDataAdapter and manually assign the SelectCommand. using (SqlDataAdapter adapter = new SqlDataAdapter()) { // Set up the SelectCommand. adapter.SelectCommand = new SqlCommand("SELECT CustomerID, FirstName, LastName, Email FROM Customers", connection); // Fill the DataTable with customers data. adapter.Fill(dtCustomers); Console.WriteLine("Initial Customer Data:"); foreach (DataRow row in dtCustomers.Rows) { Console.WriteLine($"ID: {row["CustomerID"]}, Name: {row["FirstName"]} {row["FirstName"]}, Email: {row["Email"]}"); } // Prepare InsertCommand. adapter.InsertCommand = new SqlCommand( "INSERT INTO Customers (FirstName, LastName, Email) VALUES (@FirstName, @LastName, @Email)", connection); adapter.InsertCommand.Parameters.Add("@FirstName", SqlDbType.NVarChar, 50, "FirstName"); adapter.InsertCommand.Parameters.Add("@LastName", SqlDbType.NVarChar, 50, "LastName"); adapter.InsertCommand.Parameters.Add("@Email", SqlDbType.NVarChar, 100, "Email"); // Prepare UpdateCommand. adapter.UpdateCommand = new SqlCommand( "UPDATE Customers SET FirstName=@FirstName, LastName=@LastName, Email=@Email WHERE CustomerID=@CustomerID", connection); adapter.UpdateCommand.Parameters.Add("@FirstName", SqlDbType.NVarChar, 50, "FirstName"); adapter.UpdateCommand.Parameters.Add("@LastName", SqlDbType.NVarChar, 50, "LastName"); adapter.UpdateCommand.Parameters.Add("@Email", SqlDbType.NVarChar, 100, "Email"); adapter.UpdateCommand.Parameters.Add("@CustomerID", SqlDbType.Int, 0, "CustomerID"); // Prepare DeleteCommand. adapter.DeleteCommand = new SqlCommand("DELETE FROM Customers WHERE CustomerID=@CustomerID", connection); adapter.DeleteCommand.Parameters.Add("@CustomerID", SqlDbType.Int, 0, "CustomerID"); // ----- Example operations ----- // 1. Insert a new record. DataRow newRow = dtCustomers.NewRow(); newRow["FirstName"] = "Robert"; newRow["LastName"] = "Brown"; newRow["Email"] = "robert.brown@example.com"; dtCustomers.Rows.Add(newRow); // 2. Update an existing record. For illustration, update the second record if available. if (dtCustomers.Rows.Count > 1) { dtCustomers.Rows[1]["Email"] = "updated.email@example.com"; } // 3) Delete an existing row if (dtCustomers.Rows.Count > 0) { //Deleting the Customer with CustomerID = 5 DataRow[] rowsToDelete = dtCustomers.Select("CustomerID = 5"); if (rowsToDelete.Length > 0) { rowsToDelete[0].Delete(); // Mark this row for deletion } } // Propagate changes to the database. int affectedRows = adapter.Update(dtCustomers); Console.WriteLine("\nUpdate executed. Rows affected: " + affectedRows); // Refill the DataTable to show the current state. dtCustomers.Clear(); adapter.SelectCommand.CommandText = "SELECT CustomerID, FirstName, LastName, Email FROM Customers"; adapter.Fill(dtCustomers); Console.WriteLine("\nCustomer Data After Insert/Update/Delete:"); foreach (DataRow row in dtCustomers.Rows) { Console.WriteLine($"ID: {row["CustomerID"]}, Name: {row["FirstName"]} {row["FirstName"]}, Email: {row["Email"]}"); } } } } catch (Exception ex) { // Handle any errors that occur during the database operations. Console.WriteLine($"Error: {ex.Message}"); } Console.ReadKey(); } } }
Code Explanation:
- Explicit Command Assignment: The adapterās command properties are manually set up for selection, insertion, updating, and deletion.
- Parameter Mapping: Each command uses parameters that map to the columns of the DataTable.
- Operations: The example performs an insert, an update, and a deletion on the DataTable, and then calls the adapter.Update to apply these changes to the database.
Output:
Understanding Parameter Mapping:
When you manually create your InsertCommand, UpdateCommand, or DeleteCommand for a SqlDataAdapter, you often need to map parameters from your DataTable columns to the corresponding parameters in the SQL command. You can do this by using the following method:
SqlParameter parameter = adapter.UpdateCommand.Parameters.Add( string parameterName, SqlDbType sqlDbType, int size, string sourceColumn );
Explanation of Each Parameter:
- parameterName: This is the name of the parameter as it appears in your SQL query or stored procedure. It should match the placeholder in your SQL statement. For example, if your command text is UPDATE Customers SET FirstName = @FirstName WHERE CustomerID = @CustomerID, then your parameter name might be @FirstName or @CustomerID.
- sqlDbType: This specifies the SQL Server data type (SqlDbType) of the parameter. Some examples include SqlDbType.VarChar, SqlDbType.NVarChar, SqlDbType.Int, SqlDbType.DateTime, etc. It tells ADO.NET how to handle the parameter value when sending it to the SQL Server.
- size: Typically used for data types that have a variable length (e.g., VarChar, NVarChar, Binary). It indicates the maximum size (in bytes or characters, depending on the type) of the parameter. For example, if your column is VARCHAR(50) in SQL Server, you might specify size = 50. For fixed-size data types (like Int or DateTime), you can pass 0 (zero) or omit the parameter if youāre calling a different overload.
- sourceColumn: This is the column name in your DataTable that you want to bind to this parameter. When the DataTable has changes and you call the adapter.Update(dataTable), ADO.NET looks at this sourceColumn to find the new/changed value in each row. That value is then assigned to the parameter, which will be used in the underlying SQL statement (INSERT, UPDATE, or DELETE).
Examples:
adapter.UpdateCommand.Parameters.Add("@FirstName", SqlDbType.VarChar, 50, "FirstName"); adapter.UpdateCommand.Parameters.Add("@CustomerID", SqlDbType.Int, 0, "CustomerID");
Performing Asynchronous Operations Using ADO.NET Core SqlDataAdapterĀ
While the SqlDataAdapter does not provide native asynchronous FillAsync or UpdateAsync methods (the operations are inherently synchronous), you can wrap these operations in a task (using Task.Run) to avoid blocking the UI thread in real-world applications.
Let us see how to perform an asynchronous-like operation for loading data from the Customers table using Task.Run to simulate asynchronous behavior. The following example code is self-explained, so please read the comment lines for a better understanding.
using System.Data; using Microsoft.Data.SqlClient; namespace SqlDataAdapterAsyncExample { class Program { static async Task Main(string[] args) { // Connection string to the ECommerceDB database. string connectionString = "Server=LAPTOP-6P5NK25R\\SQLSERVER2022DEV;Database=ECommerceDB;Trusted_Connection=True;TrustServerCertificate=True;"; // The query we want to execute string selectQuery = "SELECT CustomerID, FirstName, LastName, Email FROM Customers"; // Create an Empty DataTable to hold the data. DataTable dtCustomers = new DataTable(); try { using (SqlConnection connection = new SqlConnection(connectionString)) { // Create the data adapter. using (SqlDataAdapter adapter = new SqlDataAdapter(selectQuery, connection)) { // Asynchronously fill the DataTable by wrapping the adapter.Fill call in a Task. await Task.Run(() => adapter.Fill(dtCustomers)); // Output the results. Console.WriteLine("Asynchronously retrieved Customer Data:"); foreach (DataRow row in dtCustomers.Rows) { Console.WriteLine($"ID: {row["CustomerID"]}, Name: {row["FirstName"]} {row["FirstName"]}, Email: {row["Email"]}"); } } } } catch (Exception ex) { // Handle any errors that occur during the database operations. Console.WriteLine($"Error: {ex.Message}"); } Console.ReadKey(); } } }
Code Explanation:
- Asynchronous Wrapper: Since SqlDataAdapter.Fill is synchronous, and it is wrapped inside a Task.Run to offload the operation.
- Await Task.Run: The await keyword ensures that the method waits for the task to complete without blocking the main thread.
- Output: Retrieved customer data, which is then printed to the console.
Output:
Using Stored Procedures with ADO.NET Core SqlDataAdapter
Stored procedures encapsulate SQL queries on the server side, helping reduce SQL injection risk and promote code reuse. The SqlDataAdapter can work with stored procedures by setting its SelectCommand (or other commands) to call the procedure.
Let us see an example to understand how to retrieve orders for a specific customer using the stored procedure GetCustomerOrders with SqlDataAdapter. The following example code is self-explained, so please read the comment lines for a better understanding.
using System.Data; using Microsoft.Data.SqlClient; namespace SqlDataAdapterStoredProcedureExample { public class Program { static void Main(string[] args) { // Connection string to the ECommerceDB database. string connectionString = "Server=LAPTOP-6P5NK25R\\SQLSERVER2022DEV;Database=ECommerceDB;Trusted_Connection=True;TrustServerCertificate=True;"; // For example, retrieve orders for CustomerID 1. int customerId = 1; // Create an Empty DataTable to hold the orders data. DataTable dtOrders = new DataTable(); try { using (SqlConnection connection = new SqlConnection(connectionString)) { // Create a SqlDataAdapter. using (SqlDataAdapter adapter = new SqlDataAdapter()) { // Set the SelectCommand to use the stored procedure. SqlCommand cmd = new SqlCommand("GetCustomerOrders", connection); cmd.CommandType = CommandType.StoredProcedure; // Add parameter for CustomerID. cmd.Parameters.AddWithValue("@CustomerID", customerId); //Set the SelectCommand to the SqlCommand object adapter.SelectCommand = cmd; // Fill the DataTable with the orders returned by the stored procedure. adapter.Fill(dtOrders); // Output: Display the retrieved order data. Console.WriteLine("Orders for CustomerID {0}:", customerId); foreach (DataRow row in dtOrders.Rows) { Console.WriteLine($"OrderID: {row["OrderID"]} | OrderDate: {row["OrderDate"]} | OrderAmount: {row["OrderAmount"]}"); } } } } catch (Exception ex) { // Handle any errors that occur during the database operations. Console.WriteLine($"Error: {ex.Message}"); } Console.ReadKey(); } } }
Code Explanation:
- Stored Procedure Call: We create a SqlCommand specifying the stored procedure name and CommandType.StoredProcedure.
- Parameter Setup: The customer ID is passed using the @CustomerID parameter.
- DataAdapter: We create the SqlDataAdapter from the command and call the adapter.Fill(…) to retrieve the data.
- Data Retrieval: The adapter fills a DataTable with the result set from the stored procedure.
- Output: The retrieved orders are printed to the console.
Output:
When Should We Use ADO.NET Core SqlDataAdapter in Real-World Applications?
- Disconnected Data Scenarios: When you need to fetch data, process it offline (in memory), and later update the database, SqlDataAdapter fits well. It is useful when working in a disconnected environment (e.g., desktop applications) or when you need to batch update changes.
- Batch Updates: SqlDataAdapter can collect a series of modifications in a Data Set or Data Table and then submit them in one go using the Update method. This is effective in scenarios where you want to minimize round trips to the database.
In the next article, I will discuss the ADO.NET Core SqlCommandBuilderĀ class with Examples. In this article, I explain the ADO.NET CoreĀ SQLDataAdapter Class with Examples. I want your feedback. Please post your feedback, questions, or comments about this article.