Database Interaction Using ADO.NET and Stored Procedures

Learning Objectives

  • Understand how to use ADO.NET for database interaction in .NET Core.
  • Learn to perform CRUD operations with stored procedures in SQL Server.
  • Learn best practices for SQL connection management and working with commands and parameters.

ADO.NET Basics

What is ADO.NET? ADO.NET is a data access technology that allows .NET applications to interact with databases. It provides the means to connect, execute commands, and retrieve results from databases such as SQL Server.

  • In this chapter, we'll use System.Data.SqlClient and Microsoft.Data.SqlClient to interact with SQL Server.
  • ADO.NET is lightweight and high-performance, and it's a good alternative to Entity Framework (EF) for those who prefer direct database interaction.

Setting Up SQL Connection

To connect to a SQL Server database, we need to use SqlConnection:

using System.Data.SqlClient;

string connectionString = "Your_Connection_String_Here";
using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();
    // Execute database commands
}

Real-Life Example: In an e-commerce application, use SqlConnection to fetch product details directly from the database.

Working with Stored Procedures

What are Stored Procedures? A stored procedure is a prepared SQL code that can be saved and reused. Instead of sending raw SQL commands, you call a stored procedure, which can help reduce SQL injection risks and improve maintainability.

Creating a Stored Procedure in SQL Server:

CREATE PROCEDURE GetProductById
    @ProductId INT
AS
BEGIN
    SELECT * FROM Products WHERE Id = @ProductId;
END

Calling a Stored Procedure Using ADO.NET: We use SqlCommand to execute stored procedures.

using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();
    using (SqlCommand command = new SqlCommand("GetProductById", connection))
    {
        command.CommandType = CommandType.StoredProcedure;
        command.Parameters.AddWithValue("@ProductId", 1);

        using (SqlDataReader reader = command.ExecuteReader())
        {
            while (reader.Read())
            {
                Console.WriteLine($"Product Name: {reader["Name"]}, Price: {reader["Price"]}");
            }
        }
    }
}

Real-Life Example: Use stored procedures in a financial application to execute transactions safely without exposing raw SQL to the application.

SQL Connection Management

Managing Connections and Commands

  • Always open a connection right before executing a command and close it immediately afterward.
  • Use using statements to ensure connections are closed automatically, even in case of an error.

Example with Best Practices:

using (SqlConnection connection = new SqlConnection(connectionString))
{
    try
    {
        connection.Open();
        using (SqlCommand command = new SqlCommand("AddNewProduct", connection))
        {
            command.CommandType = CommandType.StoredProcedure;
            command.Parameters.AddWithValue("@Name", "New Product");
            command.Parameters.AddWithValue("@Price", 100.0m);
            command.ExecuteNonQuery();
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"An error occurred: {ex.Message}");
    }
}

Real-Life Example: In a user management system, use connection management best practices to create new user accounts and ensure the connection is properly closed.

Examples

Simple Example: Fetching Data with a Stored Procedure

Create a stored procedure to get a product by ID and call it using ADO.NET.

Code Snippet:

using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();
    using (SqlCommand command = new SqlCommand("GetProductById", connection))
    {
        command.CommandType = CommandType.StoredProcedure;
        command.Parameters.AddWithValue("@ProductId", 1);
        using (SqlDataReader reader = command.ExecuteReader())
        {
            while (reader.Read())
            {
                Console.WriteLine($"Product: {reader["Name"]}, Price: {reader["Price"]}");
            }
        }
    }
}

Real-Life Example: Fetch product information from an e-commerce database to display to customers.

Simple Example: Adding Data Using a Stored Procedure

Create a stored procedure for adding a new product and call it from your code.

Code Snippet:

using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();
    using (SqlCommand command = new SqlCommand("AddNewProduct", connection))
    {
        command.CommandType = CommandType.StoredProcedure;
        command.Parameters.AddWithValue("@Name", "Tablet");
        command.Parameters.AddWithValue("@Price", 300.0m);
        command.ExecuteNonQuery();
    }
}

Real-Life Example: In an inventory management system, add new products to the database.

Simple Example: Updating Data Using a Stored Procedure

Create a stored procedure for updating a product's price and call it from your code.

Code Snippet:

using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();
    using (SqlCommand command = new SqlCommand("UpdateProductPrice", connection))
    {
        command.CommandType = CommandType.StoredProcedure;
        command.Parameters.AddWithValue("@ProductId", 1);
        command.Parameters.AddWithValue("@Price", 1200.0m);
        command.ExecuteNonQuery();
    }
}

Real-Life Example: In a financial application, adjust the price of a product during a promotional campaign.

Key Takeaways

  • ADO.NET allows you to directly interact with a SQL Server database, providing a high degree of control.
  • Stored procedures are used for executing database commands more efficiently and securely.
  • Connection management is crucial to ensure that database resources are used correctly and avoid potential issues like connection leaks.

Practical Questions

  1. How can you use a stored procedure in ADO.NET to fetch data from a SQL Server database?
  2. What are the best practices for managing SQL connections in ADO.NET?
  3. How can you use SqlCommand to execute a stored procedure that updates a record in a database?

New Concepts in .NET 8

New in .NET 8:

  • Improved integration for ADO.NET to support Native AOT Compilation, allowing you to create smaller and faster executables. This is particularly useful when deploying APIs to cloud environments where cold-start performance is crucial.

Real-World Application

These concepts are highly applicable in building real-world, scalable APIs where direct database access with high performance is required. You could use ADO.NET to interact directly with your database in scenarios where EF's abstraction is not necessary or might introduce performance overhead, such as:

  • E-commerce systems: Directly retrieving product information.
  • User management systems: Safely storing user credentials.
  • Financial applications: Executing secure transactions via stored procedures.

e-commerce scenario example

This example demonstrates an e-commerce scenario where we have a product API that handles fetching product details. It follows a layered approach:

  • Controller: Manages the incoming HTTP request and returns a response.
  • Service (Business Logic Layer): Contains the business logic, including calling the database repository.
  • Repository (Data Access Layer): Uses ADO.NET to interact with the SQL Server database.

Step-by-Step Full Example

Step 1: Create the Product Model

Define a Product model that will be used to represent the product data.

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Step 2: Create the Repository Layer for Database Interaction

The repository will handle the direct interaction with the database using ADO.NET.

using System.Data.SqlClient;

public interface IProductRepository
{
    Product GetProductById(int productId);
}

public class ProductRepository : IProductRepository
{
    private readonly string _connectionString;

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

    public Product GetProductById(int productId)
    {
        Product product = null;

        using (SqlConnection connection = new SqlConnection(_connectionString))
        {
            connection.Open();
            using (SqlCommand command = new SqlCommand("GetProductById", connection))
            {
                command.CommandType = System.Data.CommandType.StoredProcedure;
                command.Parameters.AddWithValue("@ProductId", productId);

                using (SqlDataReader reader = command.ExecuteReader())
                {
                    if (reader.Read())
                    {
                        product = new Product
                        {
                            Id = (int)reader["Id"],
                            Name = reader["Name"].ToString(),
                            Price = (decimal)reader["Price"]
                        };
                    }
                }
            }
        }

        return product;
    }
}

Stored Procedure: We are calling a stored procedure named GetProductById to get the product details.Connection Management: The connection is managed using a using statement to ensure it is closed properly.

Step 3: Create the Service Layer for Business Logic

The service layer will handle any business logic required for product retrieval.

public interface IProductService
{
    Product GetProductById(int productId);
}

public class ProductService : IProductService
{
    private readonly IProductRepository _productRepository;

    public ProductService(IProductRepository productRepository)
    {
        _productRepository = productRepository;
    }

    public Product GetProductById(int productId)
    {
        // Add any additional business logic here if needed
        return _productRepository.GetProductById(productId);
    }
}

The service simply calls the repository to get the product but can also have additional business logic if needed.

Step 4: Create the Product Controller

The controller will handle the HTTP request and interact with the service to get the product data.

using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
    private readonly IProductService _productService;

    public ProductController(IProductService productService)
    {
        _productService = productService;
    }

    [HttpGet("{id}")]
    public IActionResult GetProduct(int id)
    {
        var product = _productService.GetProductById(id);
        if (product == null)
        {
            return NotFound("Product not found");
        }
        return Ok(product);
    }
}

Routing: The controller route is defined as api/product, and the action route is /{id}, allowing clients to call GET /api/product/{id}.IActionResult: Returns either 200 OK with the product data or 404 Not Found if the product doesn’t exist.

Step 5: Configure Dependency Injection in Program.cs

Add the necessary services to the dependency injection container and configure the application.

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllers();

// Add connection string from appsettings.json or environment variable
string connectionString = builder.Configuration.GetConnectionString("DefaultConnection");

// Register repository and service for dependency injection
builder.Services.AddScoped<IProductRepository>(provider => new ProductRepository(connectionString));
builder.Services.AddScoped<IProductService, ProductService>();

var app = builder.Build();

// Configure the HTTP request pipeline.
app.UseRouting();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});

app.Run();

Connection String: The connection string is retrieved from the configuration and passed to the repository.DI Registration:

ProductRepository and ProductService are registered with the DI container so they can be injected into the controller.

Step 6: SQL Stored Procedure Example

Ensure you have a stored procedure in your SQL Server database to retrieve product details by ID.

CREATE PROCEDURE GetProductById
    @ProductId INT
AS
BEGIN
    SELECT Id, Name, Price FROM Products WHERE Id = @ProductId;
END

Summary

This example follows an end-to-end flow:

  1. Controller (ProductController):
    • Receives a request (GET /api/product/{id}) and calls the service layer.
    • Returns 200 OK with product data or 404 Not Found if the product does not exist.
  2. Service Layer (ProductService):
    • Acts as a bridge between the controller and repository.
    • Contains business logic (in this case, it’s simple, but it can be expanded).
  3. Repository Layer (ProductRepository):
    • Connects to SQL Server using ADO.NET.
    • Executes the stored procedure GetProductById to fetch the product from the database.
    • Manages SQL connections, commands, and parameters safely.

Real-Life Use Case

Imagine you have an e-commerce application:

  • A client wants to fetch product details by providing a product ID.
  • They call your endpoint (GET /api/product/{id}).
  • The controller sends the request to the business layer (ProductService).
  • The business layer calls the data access layer (ProductRepository), which queries the SQL Server database via a stored procedure.
  • The response is returned through the controller as a JSON object to the client.

This pattern follows separation of concerns, where:

  • Controllers are responsible for handling HTTP requests and responses.
  • The service layer deals with business logic.
  • The repository handles data access.

This approach is suitable for real-world projects where scalability, maintainability, and reusability are key considerations.

ניווט במאמר

מאמרים אחרונים

Weekly Tutorial