C# SQL Server 資料庫操作完整指南

SQL Server 是微軟生態系統中最重要的資料庫系統之一,與 C# 的整合度非常高。本文將深入介紹如何在 C# 中高效操作 SQL Server 資料庫,從基礎連線到進階功能,讓您成為資料庫操作的專家。

環境準備

安裝必要套件

在 .NET 專案中,您可以使用以下幾種方式連接 SQL Server:

方法一:使用內建的 System.Data.SqlClient

// .NET Framework 已內建,無需額外安裝

方法二:使用新版 Microsoft.Data.SqlClient(推薦)

Install-Package Microsoft.Data.SqlClient

方法三:使用 Entity Framework Core

Install-Package Microsoft.EntityFrameworkCore.SqlServer

本文主要介紹使用 Microsoft.Data.SqlClient 的方式,這是微軟推薦的新版驅動程式。

資料庫連線

連線字串設定

SQL Server 的連線字串有多種格式,以下是常見的幾種:

using Microsoft.Data.SqlClient;

// 基本連線字串(SQL Server 驗證)
string connectionString = "Server=localhost;Database=TestDB;User Id=sa;Password=yourpassword;";

// Windows 驗證
string connectionString = "Server=localhost;Database=TestDB;Integrated Security=true;";

// 包含加密和信任憑證
string connectionString = "Server=localhost;Database=TestDB;Integrated Security=true;Encrypt=true;TrustServerCertificate=true;";

建立連線

using Microsoft.Data.SqlClient;

public async Task TestConnectionAsync()
{
    string connectionString = "Server=localhost;Database=TestDB;Integrated Security=true;";
    
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        try
        {
            await connection.OpenAsync();
            Console.WriteLine("資料庫連線成功!");
            Console.WriteLine($"資料庫版本: {connection.ServerVersion}");
        }
        catch (SqlException ex)
        {
            Console.WriteLine($"連線失敗: {ex.Message}");
        }
    }
}

基本 CRUD 操作

查詢資料 (SELECT)

基本查詢

public async Task<List<User>> GetUsersAsync(int minAge = 0)
{
    var users = new List<User>();
    string sql = @"
        SELECT Id, Name, Email, Age, CreateDate 
        FROM Users 
        WHERE Age >= @MinAge 
        ORDER BY Name";
    
    using (var connection = new SqlConnection(connectionString))
    {
        await connection.OpenAsync();
        
        using (var command = new SqlCommand(sql, connection))
        {
            command.Parameters.AddWithValue("@MinAge", minAge);
            
            using (var reader = await command.ExecuteReaderAsync())
            {
                while (await reader.ReadAsync())
                {
                    users.Add(new User
                    {
                        Id = reader.GetInt32("Id"),
                        Name = reader.GetString("Name"),
                        Email = reader.IsDBNull("Email") ? null : reader.GetString("Email"),
                        Age = reader.GetInt32("Age"),
                        CreateDate = reader.GetDateTime("CreateDate")
                    });
                }
            }
        }
    }
    
    return users;
}

查詢單筆資料

public async Task<User> GetUserByIdAsync(int userId)
{
    string sql = "SELECT Id, Name, Email, Age, CreateDate FROM Users WHERE Id = @UserId";
    
    using (var connection = new SqlConnection(connectionString))
    {
        await connection.OpenAsync();
        
        using (var command = new SqlCommand(sql, connection))
        {
            command.Parameters.AddWithValue("@UserId", userId);
            
            using (var reader = await command.ExecuteReaderAsync())
            {
                if (await reader.ReadAsync())
                {
                    return new User
                    {
                        Id = reader.GetInt32("Id"),
                        Name = reader.GetString("Name"),
                        Email = reader.IsDBNull("Email") ? null : reader.GetString("Email"),
                        Age = reader.GetInt32("Age"),
                        CreateDate = reader.GetDateTime("CreateDate")
                    };
                }
            }
        }
    }
    
    return null;
}

新增資料 (INSERT)

基本新增

public async Task<int> CreateUserAsync(User user)
{
    string sql = @"
        INSERT INTO Users (Name, Email, Age, CreateDate) 
        VALUES (@Name, @Email, @Age, @CreateDate);
        SELECT CAST(SCOPE_IDENTITY() AS INT);";
    
    using (var connection = new SqlConnection(connectionString))
    {
        await connection.OpenAsync();
        
        using (var command = new SqlCommand(sql, connection))
        {
            command.Parameters.AddWithValue("@Name", user.Name);
            command.Parameters.AddWithValue("@Email", user.Email ?? (object)DBNull.Value);
            command.Parameters.AddWithValue("@Age", user.Age);
            command.Parameters.AddWithValue("@CreateDate", DateTime.Now);
            
            // 取得新增記錄的 ID
            var result = await command.ExecuteScalarAsync();
            return Convert.ToInt32(result);
        }
    }
}

批次新增

public async Task<bool> CreateUsersAsync(List<User> users)
{
    string sql = @"
        INSERT INTO Users (Name, Email, Age, CreateDate) 
        VALUES (@Name, @Email, @Age, @CreateDate)";
    
    using (var connection = new SqlConnection(connectionString))
    {
        await connection.OpenAsync();
        
        using (var transaction = connection.BeginTransaction())
        {
            try
            {
                foreach (var user in users)
                {
                    using (var command = new SqlCommand(sql, connection, transaction))
                    {
                        command.Parameters.AddWithValue("@Name", user.Name);
                        command.Parameters.AddWithValue("@Email", user.Email ?? (object)DBNull.Value);
                        command.Parameters.AddWithValue("@Age", user.Age);
                        command.Parameters.AddWithValue("@CreateDate", DateTime.Now);
                        
                        await command.ExecuteNonQueryAsync();
                    }
                }
                
                transaction.Commit();
                return true;
            }
            catch
            {
                transaction.Rollback();
                throw;
            }
        }
    }
}

更新資料 (UPDATE)

public async Task<bool> UpdateUserAsync(User user)
{
    string sql = @"
        UPDATE Users 
        SET Name = @Name, Email = @Email, Age = @Age, UpdateDate = @UpdateDate
        WHERE Id = @Id";
    
    using (var connection = new SqlConnection(connectionString))
    {
        await connection.OpenAsync();
        
        using (var command = new SqlCommand(sql, connection))
        {
            command.Parameters.AddWithValue("@Id", user.Id);
            command.Parameters.AddWithValue("@Name", user.Name);
            command.Parameters.AddWithValue("@Email", user.Email ?? (object)DBNull.Value);
            command.Parameters.AddWithValue("@Age", user.Age);
            command.Parameters.AddWithValue("@UpdateDate", DateTime.Now);
            
            int rowsAffected = await command.ExecuteNonQueryAsync();
            return rowsAffected > 0;
        }
    }
}

刪除資料 (DELETE)

軟刪除(推薦)

public async Task<bool> SoftDeleteUserAsync(int userId)
{
    string sql = @"
        UPDATE Users 
        SET IsDeleted = 1, DeleteDate = @DeleteDate 
        WHERE Id = @UserId AND IsDeleted = 0";
    
    using (var connection = new SqlConnection(connectionString))
    {
        await connection.OpenAsync();
        
        using (var command = new SqlCommand(sql, connection))
        {
            command.Parameters.AddWithValue("@UserId", userId);
            command.Parameters.AddWithValue("@DeleteDate", DateTime.Now);
            
            int rowsAffected = await command.ExecuteNonQueryAsync();
            return rowsAffected > 0;
        }
    }
}

實體刪除

public async Task<bool> DeleteUserAsync(int userId)
{
    string sql = "DELETE FROM Users WHERE Id = @UserId";
    
    using (var connection = new SqlConnection(connectionString))
    {
        await connection.OpenAsync();
        
        using (var command = new SqlCommand(sql, connection))
        {
            command.Parameters.AddWithValue("@UserId", userId);
            
            int rowsAffected = await command.ExecuteNonQueryAsync();
            return rowsAffected > 0;
        }
    }
}

進階操作技巧

使用 DataTable 和 DataAdapter

當需要處理大量資料或進行複雜的資料操作時,DataTable 是很好的選擇:

public async Task<DataTable> GetUsersDataTableAsync(string searchTerm = "")
{
    string sql = @"
        SELECT Id, Name, Email, Age, CreateDate 
        FROM Users 
        WHERE (@SearchTerm = '' OR Name LIKE '%' + @SearchTerm + '%' OR Email LIKE '%' + @SearchTerm + '%')
        ORDER BY CreateDate DESC";
    
    using (var connection = new SqlConnection(connectionString))
    {
        using (var adapter = new SqlDataAdapter(sql, connection))
        {
            adapter.SelectCommand.Parameters.AddWithValue("@SearchTerm", searchTerm);
            
            var dataTable = new DataTable();
            await Task.Run(() => adapter.Fill(dataTable));
            
            return dataTable;
        }
    }
}

預存程序操作

呼叫預存程序

public async Task<List<User>> GetUsersByAgeRangeAsync(int minAge, int maxAge)
{
    var users = new List<User>();
    
    using (var connection = new SqlConnection(connectionString))
    {
        await connection.OpenAsync();
        
        using (var command = new SqlCommand("sp_GetUsersByAgeRange", connection))
        {
            command.CommandType = CommandType.StoredProcedure;
            
            // 輸入參數
            command.Parameters.AddWithValue("@MinAge", minAge);
            command.Parameters.AddWithValue("@MaxAge", maxAge);
            
            // 輸出參數
            var totalCountParam = new SqlParameter("@TotalCount", SqlDbType.Int)
            {
                Direction = ParameterDirection.Output
            };
            command.Parameters.Add(totalCountParam);
            
            using (var reader = await command.ExecuteReaderAsync())
            {
                while (await reader.ReadAsync())
                {
                    users.Add(new User
                    {
                        Id = reader.GetInt32("Id"),
                        Name = reader.GetString("Name"),
                        Email = reader.IsDBNull("Email") ? null : reader.GetString("Email"),
                        Age = reader.GetInt32("Age"),
                        CreateDate = reader.GetDateTime("CreateDate")
                    });
                }
            }
            
            // 取得輸出參數值
            int totalCount = Convert.ToInt32(totalCountParam.Value);
            Console.WriteLine($"總共找到 {totalCount} 筆符合條件的資料");
        }
    }
    
    return users;
}

交易處理

基本交易

public async Task<bool> TransferUserDataAsync(int fromUserId, int toUserId, decimal amount)
{
    using (var connection = new SqlConnection(connectionString))
    {
        await connection.OpenAsync();
        
        using (var transaction = connection.BeginTransaction())
        {
            try
            {
                // 扣除來源帳戶金額
                string deductSql = "UPDATE UserAccounts SET Balance = Balance - @Amount WHERE UserId = @UserId";
                using (var deductCommand = new SqlCommand(deductSql, connection, transaction))
                {
                    deductCommand.Parameters.AddWithValue("@Amount", amount);
                    deductCommand.Parameters.AddWithValue("@UserId", fromUserId);
                    await deductCommand.ExecuteNonQueryAsync();
                }
                
                // 增加目標帳戶金額
                string addSql = "UPDATE UserAccounts SET Balance = Balance + @Amount WHERE UserId = @UserId";
                using (var addCommand = new SqlCommand(addSql, connection, transaction))
                {
                    addCommand.Parameters.AddWithValue("@Amount", amount);
                    addCommand.Parameters.AddWithValue("@UserId", toUserId);
                    await addCommand.ExecuteNonQueryAsync();
                }
                
                // 記錄交易歷史
                string logSql = @"
                    INSERT INTO TransactionLog (FromUserId, ToUserId, Amount, TransactionDate) 
                    VALUES (@FromUserId, @ToUserId, @Amount, @TransactionDate)";
                using (var logCommand = new SqlCommand(logSql, connection, transaction))
                {
                    logCommand.Parameters.AddWithValue("@FromUserId", fromUserId);
                    logCommand.Parameters.AddWithValue("@ToUserId", toUserId);
                    logCommand.Parameters.AddWithValue("@Amount", amount);
                    logCommand.Parameters.AddWithValue("@TransactionDate", DateTime.Now);
                    await logCommand.ExecuteNonQueryAsync();
                }
                
                // 提交交易
                transaction.Commit();
                Console.WriteLine("轉帳成功");
                return true;
            }
            catch (Exception ex)
            {
                // 回滾交易
                transaction.Rollback();
                Console.WriteLine($"轉帳失敗,已回滾:{ex.Message}");
                return false;
            }
        }
    }
}

Bulk Copy 大量資料操作

對於大量資料的插入,SqlBulkCopy 是最有效的方式:

public async Task<bool> BulkInsertUsersAsync(DataTable usersData)
{
    using (var connection = new SqlConnection(connectionString))
    {
        await connection.OpenAsync();
        
        using (var bulkCopy = new SqlBulkCopy(connection))
        {
            bulkCopy.DestinationTableName = "Users";
            bulkCopy.BatchSize = 1000;
            bulkCopy.BulkCopyTimeout = 300;
            
            // 對應欄位
            bulkCopy.ColumnMappings.Add("Name", "Name");
            bulkCopy.ColumnMappings.Add("Email", "Email");
            bulkCopy.ColumnMappings.Add("Age", "Age");
            bulkCopy.ColumnMappings.Add("CreateDate", "CreateDate");
            
            try
            {
                await bulkCopy.WriteToServerAsync(usersData);
                Console.WriteLine($"成功批次插入 {usersData.Rows.Count} 筆資料");
                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"批次插入失敗:{ex.Message}");
                return false;
            }
        }
    }
}

連線池和效能優化

連線池設定

public class DatabaseConfig
{
    public static string GetOptimizedConnectionString()
    {
        return @"
            Server=localhost;
            Database=TestDB;
            Integrated Security=true;
            Pooling=true;
            Min Pool Size=5;
            Max Pool Size=100;
            Connection Lifetime=300;
            Command Timeout=30;
            Application Name=MyApp;";
    }
}

使用 SqlConnectionStringBuilder

public static string BuildConnectionString(string server, string database, bool useWindowsAuth = true)
{
    var builder = new SqlConnectionStringBuilder
    {
        DataSource = server,
        InitialCatalog = database,
        IntegratedSecurity = useWindowsAuth,
        Pooling = true,
        MinPoolSize = 5,
        MaxPoolSize = 100,
        ConnectionTimeout = 30,
        CommandTimeout = 60,
        ApplicationName = "MyApplication"
    };
    
    if (!useWindowsAuth)
    {
        builder.UserID = "username";
        builder.Password = "password";
    }
    
    return builder.ConnectionString;
}

非同步最佳實務

public class UserService
{
    private readonly string _connectionString;
    
    public UserService(string connectionString)
    {
        _connectionString = connectionString;
    }
    
    public async Task<List<User>> GetUsersWithPagingAsync(int pageNumber, int pageSize)
    {
        var users = new List<User>();
        
        string sql = @"
            SELECT Id, Name, Email, Age, CreateDate
            FROM Users
            WHERE IsDeleted = 0
            ORDER BY CreateDate DESC
            OFFSET @Offset ROWS
            FETCH NEXT @PageSize ROWS ONLY";
        
        using (var connection = new SqlConnection(_connectionString))
        {
            await connection.OpenAsync();
            
            using (var command = new SqlCommand(sql, connection))
            {
                command.Parameters.AddWithValue("@Offset", (pageNumber - 1) * pageSize);
                command.Parameters.AddWithValue("@PageSize", pageSize);
                
                using (var reader = await command.ExecuteReaderAsync())
                {
                    while (await reader.ReadAsync())
                    {
                        users.Add(MapUserFromReader(reader));
                    }
                }
            }
        }
        
        return users;
    }
    
    private static User MapUserFromReader(SqlDataReader reader)
    {
        return new User
        {
            Id = reader.GetInt32("Id"),
            Name = reader.GetString("Name"),
            Email = reader.IsDBNull("Email") ? null : reader.GetString("Email"),
            Age = reader.GetInt32("Age"),
            CreateDate = reader.GetDateTime("CreateDate")
        };
    }
}

錯誤處理和日誌記錄

完整的錯誤處理

public async Task<OperationResult<User>> CreateUserSafeAsync(User user)
{
    try
    {
        // 驗證輸入
        if (string.IsNullOrWhiteSpace(user.Name))
        {
            return OperationResult<User>.Failure("使用者姓名不能為空");
        }
        
        if (user.Age < 0 || user.Age > 150)
        {
            return OperationResult<User>.Failure("年齡必須在 0-150 之間");
        }
        
        // 檢查重複
        if (await IsEmailExistsAsync(user.Email))
        {
            return OperationResult<User>.Failure("電子信箱已存在");
        }
        
        // 執行新增
        int userId = await CreateUserAsync(user);
        user.Id = userId;
        
        return OperationResult<User>.Success(user, "使用者建立成功");
    }
    catch (SqlException ex) when (ex.Number == 2) // Timeout
    {
        return OperationResult<User>.Failure("資料庫連線逾時,請稍後再試");
    }
    catch (SqlException ex) when (ex.Number == 2547) // Primary key violation
    {
        return OperationResult<User>.Failure("資料已存在");
    }
    catch (SqlException ex)
    {
        // 記錄詳細錯誤但回傳簡化訊息
        Console.WriteLine($"SQL 錯誤 {ex.Number}: {ex.Message}");
        return OperationResult<User>.Failure("資料庫操作失敗");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"未預期的錯誤: {ex}");
        return OperationResult<User>.Failure("系統發生錯誤,請聯絡管理員");
    }
}

private async Task<bool> IsEmailExistsAsync(string email)
{
    if (string.IsNullOrWhiteSpace(email)) return false;
    
    string sql = "SELECT COUNT(1) FROM Users WHERE Email = @Email AND IsDeleted = 0";
    
    using (var connection = new SqlConnection(_connectionString))
    {
        await connection.OpenAsync();
        using (var command = new SqlCommand(sql, connection))
        {
            command.Parameters.AddWithValue("@Email", email);
            var count = await command.ExecuteScalarAsync();
            return Convert.ToInt32(count) > 0;
        }
    }
}

操作結果封裝

public class OperationResult<T>
{
    public bool IsSuccess { get; set; }
    public string Message { get; set; }
    public T Data { get; set; }
    public List<string> Errors { get; set; } = new List<string>();
    
    public static OperationResult<T> Success(T data, string message = "操作成功")
    {
        return new OperationResult<T>
        {
            IsSuccess = true,
            Data = data,
            Message = message
        };
    }
    
    public static OperationResult<T> Failure(string message, List<string> errors = null)
    {
        return new OperationResult<T>
        {
            IsSuccess = false,
            Message = message,
            Errors = errors ?? new List<string>()
        };
    }
}

實體類別設計

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public int Age { get; set; }
    public DateTime CreateDate { get; set; }
    public DateTime? UpdateDate { get; set; }
    public bool IsDeleted { get; set; }
    public DateTime? DeleteDate { get; set; }
    
    // 驗證方法
    public List<string> Validate()
    {
        var errors = new List<string>();
        
        if (string.IsNullOrWhiteSpace(Name))
            errors.Add("姓名不能為空");
            
        if (Name?.Length > 100)
            errors.Add("姓名長度不能超過 100 字元");
            
        if (!string.IsNullOrWhiteSpace(Email) && !IsValidEmail(Email))
            errors.Add("電子信箱格式不正確");
            
        if (Age < 0 || Age > 150)
            errors.Add("年齡必須在 0-150 之間");
            
        return errors;
    }
    
    private bool IsValidEmail(string email)
    {
        try
        {
            var addr = new System.Net.Mail.MailAddress(email);
            return addr.Address == email;
        }
        catch
        {
            return false;
        }
    }
}

完整的資料存取層範例

public interface IUserRepository
{
    Task<List<User>> GetAllAsync();
    Task<User> GetByIdAsync(int id);
    Task<int> CreateAsync(User user);
    Task<bool> UpdateAsync(User user);
    Task<bool> DeleteAsync(int id);
    Task<List<User>> SearchAsync(string searchTerm);
    Task<(List<User> Users, int TotalCount)> GetPagedAsync(int pageNumber, int pageSize);
}

public class UserRepository : IUserRepository
{
    private readonly string _connectionString;
    
    public UserRepository(string connectionString)
    {
        _connectionString = connectionString;
    }
    
    public async Task<List<User>> GetAllAsync()
    {
        var users = new List<User>();
        string sql = @"
            SELECT Id, Name, Email, Age, CreateDate, UpdateDate, IsDeleted 
            FROM Users 
            WHERE IsDeleted = 0 
            ORDER BY CreateDate DESC";
        
        using (var connection = new SqlConnection(_connectionString))
        {
            await connection.OpenAsync();
            using (var command = new SqlCommand(sql, connection))
            using (var reader = await command.ExecuteReaderAsync())
            {
                while (await reader.ReadAsync())
                {
                    users.Add(MapUserFromReader(reader));
                }
            }
        }
        
        return users;
    }
    
    public async Task<User> GetByIdAsync(int id)
    {
        string sql = @"
            SELECT Id, Name, Email, Age, CreateDate, UpdateDate, IsDeleted 
            FROM Users 
            WHERE Id = @Id AND IsDeleted = 0";
        
        using (var connection = new SqlConnection(_connectionString))
        {
            await connection.OpenAsync();
            using (var command = new SqlCommand(sql, connection))
            {
                command.Parameters.AddWithValue("@Id", id);
                using (var reader = await command.ExecuteReaderAsync())
                {
                    if (await reader.ReadAsync())
                    {
                        return MapUserFromReader(reader);
                    }
                }
            }
        }
        
        return null;
    }
    
    public async Task<int> CreateAsync(User user)
    {
        string sql = @"
            INSERT INTO Users (Name, Email, Age, CreateDate, IsDeleted) 
            VALUES (@Name, @Email, @Age, @CreateDate, 0);
            SELECT CAST(SCOPE_IDENTITY() AS INT);";
        
        using (var connection = new SqlConnection(_connectionString))
        {
            await connection.OpenAsync();
            using (var command = new SqlCommand(sql, connection))
            {
                command.Parameters.AddWithValue("@Name", user.Name);
                command.Parameters.AddWithValue("@Email", user.Email ?? (object)DBNull.Value);
                command.Parameters.AddWithValue("@Age", user.Age);
                command.Parameters.AddWithValue("@CreateDate", DateTime.Now);
                
                var result = await command.ExecuteScalarAsync();
                return Convert.ToInt32(result);
            }
        }
    }
    
    public async Task<bool> UpdateAsync(User user)
    {
        string sql = @"
            UPDATE Users 
            SET Name = @Name, Email = @Email, Age = @Age, UpdateDate = @UpdateDate
            WHERE Id = @Id AND IsDeleted = 0";
        
        using (var connection = new SqlConnection(_connectionString))
        {
            await connection.OpenAsync();
            using (var command = new SqlCommand(sql, connection))
            {
                command.Parameters.AddWithValue("@Id", user.Id);
                command.Parameters.AddWithValue("@Name", user.Name);
                command.Parameters.AddWithValue("@Email", user.Email ?? (object)DBNull.Value);
                command.Parameters.AddWithValue("@Age", user.Age);
                command.Parameters.AddWithValue("@UpdateDate", DateTime.Now);
                
                int rowsAffected = await command.ExecuteNonQueryAsync();
                return rowsAffected > 0;
            }
        }
    }
    
    public async Task<bool> DeleteAsync(int id)
    {
        string sql = @"
            UPDATE Users 
            SET IsDeleted = 1, DeleteDate = @DeleteDate 
            WHERE Id = @Id AND IsDeleted = 0";
        
        using (var connection = new SqlConnection(_connectionString))
        {
            await connection.OpenAsync();
            using (var command = new SqlCommand(sql, connection))
            {
                command.Parameters.AddWithValue("@Id", id);
                command.Parameters.AddWithValue("@DeleteDate", DateTime.Now);
                
                int rowsAffected = await command.ExecuteNonQueryAsync();
                return rowsAffected > 0;
            }
        }
    }
    
    public async Task<List<User>> SearchAsync(string searchTerm)
    {
        var users = new List<User>();
        string sql = @"
            SELECT Id, Name, Email, Age, CreateDate, UpdateDate, IsDeleted 
            FROM Users 
            WHERE IsDeleted = 0 
            AND (Name LIKE @SearchTerm OR Email LIKE @SearchTerm)
            ORDER BY CreateDate DESC";
        
        using (var connection = new SqlConnection(_connectionString))
        {
            await connection.OpenAsync();
            using (var command = new SqlCommand(sql, connection))
            {
                command.Parameters.AddWithValue("@SearchTerm", $"%{searchTerm}%");
                using (var reader = await command.ExecuteReaderAsync())
                {
                    while (await reader.ReadAsync())
                    {
                        users.Add(MapUserFromReader(reader));
                    }
                }
            }
        }
        
        return users;
    }
    
    public async Task<(List<User> Users, int TotalCount)> GetPagedAsync(int pageNumber, int pageSize)
    {
        var users = new List<User>();
        int totalCount = 0;
        
        // 先取得總數
        string countSql = "SELECT COUNT(*) FROM Users WHERE IsDeleted = 0";
        
        // 分頁查詢
        string dataSql = @"
            SELECT Id, Name, Email, Age, CreateDate, UpdateDate, IsDeleted 
            FROM Users 
            WHERE IsDeleted = 0 
            ORDER BY CreateDate DESC
            OFFSET @Offset ROWS
            FETCH NEXT @PageSize ROWS ONLY";
        
        using (var connection = new SqlConnection(_connectionString))
        {
            await connection.OpenAsync();
            
            // 取得總數
            using (var countCommand = new SqlCommand(countSql, connection))
            {
                totalCount = Convert.ToInt32(await countCommand.ExecuteScalarAsync());
            }
            
            // 取得分頁資料
            using (var dataCommand = new SqlCommand(dataSql, connection))
            {
                dataCommand.Parameters.AddWithValue("@Offset", (pageNumber - 1) * pageSize);
                dataCommand.Parameters.AddWithValue("@PageSize", pageSize);
                
                using (var reader = await dataCommand.ExecuteReaderAsync())
                {
                    while (await reader.ReadAsync())
                    {
                        users.Add(MapUserFromReader(reader));
                    }
                }
            }
        }
        
        return (users, totalCount);
    }
    
    private static User MapUserFromReader(SqlDataReader reader)
    {
        return new User
        {
            Id = reader.GetInt32("Id"),
            Name = reader.GetString("Name"),
            Email = reader.IsDBNull("Email") ? null : reader.GetString("Email"),
            Age = reader.GetInt32("Age"),
            CreateDate = reader.GetDateTime("CreateDate"),
            UpdateDate = reader.IsDBNull("UpdateDate") ? null : reader.GetDateTime("UpdateDate"),
            IsDeleted = reader.GetBoolean("IsDeleted")
        };
    }
}

使用 Entity Framework Core(替代方案)

如果您偏好使用 ORM,Entity Framework Core 是很好的選擇:

設定 DbContext

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
    {
    }
    
    public DbSet<User> Users { get; set; }
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>(entity =>
        {
            entity.HasKey(e => e.Id);
            entity.Property(e => e.Name).HasMaxLength(100).IsRequired();
            entity.Property(e => e.Email).HasMaxLength(200);
            entity.Property(e => e.CreateDate).HasDefaultValueSql("GETDATE()");
            entity.HasQueryFilter(e => !e.IsDeleted); // 全域軟刪除過濾器
        });
        
        base.OnModelCreating(modelBuilder);
    }
}

EF Core 服務

public class UserServiceEF
{
    private readonly ApplicationDbContext _context;
    
    public UserServiceEF(ApplicationDbContext context)
    {
        _context = context;
    }
    
    public async Task<List<User>> GetAllUsersAsync()
    {
        return await _context.Users
            .OrderByDescending(u => u.CreateDate)
            .ToListAsync();
    }
    
    public async Task<User> GetUserByIdAsync(int id)
    {
        return await _context.Users.FindAsync(id);
    }
    
    public async Task<User> CreateUserAsync(User user)
    {
        _context.Users.Add(user);
        await _context.SaveChangesAsync();
        return user;
    }
    
    public async Task<bool> UpdateUserAsync(User user)
    {
        _context.Users.Update(user);
        var result = await _context.SaveChangesAsync();
        return result > 0;
    }
    
    public async Task<bool> DeleteUserAsync(int id)
    {
        var user = await _context.Users.FindAsync(id);
        if (user != null)
        {
            user.IsDeleted = true;
            user.DeleteDate = DateTime.Now;
            var result = await _context.SaveChangesAsync();
            return result > 0;
        }
        return false;
    }
}

常見問題與最佳實務

1. SQL 注入防護

錯誤示範:

// 危險!容易遭受 SQL 注入攻擊
string sql = $"SELECT * FROM Users WHERE Name = '{userName}'";

正確做法:

// 安全:使用參數化查詢
string sql = "SELECT * FROM Users WHERE Name = @UserName";
command.Parameters.AddWithValue("@UserName", userName);

2. 連線管理

錯誤示範:

// 錯誤:沒有正確釋放連線
SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
// 忘記關閉連線

正確做法:

// 正確:使用 using 語句自動釋放資源
using (var connection = new SqlConnection(connectionString))
{
    await connection.OpenAsync();
    // 自動關閉連線
}

3. 非同步程式設計

錯誤示範:

// 錯誤:在非同步方法中使用同步操作
public async Task<List<User>> GetUsersAsync()
{
    // 這會阻塞執行緒
    var result = command.ExecuteReader();
    return users;
}

正確做法:

// 正確:使用非同步方法
public async Task<List<User>> GetUsersAsync()
{
    using (var reader = await command.ExecuteReaderAsync())
    {
        // 非阻塞操作
    }
}

4. 大量資料處理

對於大量資料,使用適當的技術:

// 對於大量查詢,使用 IAsyncEnumerable
public async IAsyncEnumerable<User> GetAllUsersStreamAsync()
{
    using (var connection = new SqlConnection(_connectionString))
    {
        await connection.OpenAsync();
        
        string sql = "SELECT Id, Name, Email, Age FROM Users WHERE IsDeleted = 0";
        using (var command = new SqlCommand(sql, connection))
        using (var reader = await command.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                yield return MapUserFromReader(reader);
            }
        }
    }
}

5. 連線字串安全

// 使用設定檔而非硬編碼
public class DatabaseSettings
{
    public string ConnectionString { get; set; }
    
    public static DatabaseSettings FromConfiguration(IConfiguration config)
    {
        return new DatabaseSettings
        {
            ConnectionString = config.GetConnectionString("DefaultConnection")
        };
    }
}

// appsettings.json
{
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=TestDB;Integrated Security=true;"
  }
}

效能監控和調優

1. 查詢執行時間監控

public async Task<T> ExecuteWithLoggingAsync<T>(Func<Task<T>> operation, string operationName)
{
    var stopwatch = Stopwatch.StartNew();
    try
    {
        var result = await operation();
        stopwatch.Stop();
        
        if (stopwatch.ElapsedMilliseconds > 1000) // 超過 1 秒的查詢
        {
            Console.WriteLine($"慢查詢警告:{operationName} 執行時間 {stopwatch.ElapsedMilliseconds}ms");
        }
        
        return result;
    }
    catch (Exception ex)
    {
        stopwatch.Stop();
        Console.WriteLine($"查詢失敗:{operationName},執行時間 {stopwatch.ElapsedMilliseconds}ms,錯誤:{ex.Message}");
        throw;
    }
}

2. 連線池監控

public class ConnectionPoolMonitor
{
    public static void LogConnectionPoolStats(string connectionString)
    {
        var builder = new SqlConnectionStringBuilder(connectionString);
        
        // 這些資訊在生產環境中很有用
        Console.WriteLine($"應用程式名稱: {builder.ApplicationName}");
        Console.WriteLine($"連線逾時: {builder.ConnectTimeout}");
        Console.WriteLine($"命令逾時: {builder.CommandTimeout}");
        Console.WriteLine($"最小連線池大小: {builder.MinPoolSize}");
        Console.WriteLine($"最大連線池大小: {builder.MaxPoolSize}");
    }
}

單元測試

為您的資料存取層撰寫單元測試:

[TestClass]
public class UserRepositoryTests
{
    private string _testConnectionString;
    private UserRepository _userRepository;
    
    [TestInitialize]
    public void Setup()
    {
        _testConnectionString = "Server=(localdb)\\mssqllocaldb;Database=TestDB;Integrated Security=true;";
        _userRepository = new UserRepository(_testConnectionString);
    }
    
    [TestMethod]
    public async Task CreateUser_ShouldReturnUserId_WhenUserIsValid()
    {
        // Arrange
        var user = new User
        {
            Name = "測試使用者",
            Email = "test@example.com",
            Age = 25
        };
        
        // Act
        int userId = await _userRepository.CreateAsync(user);
        
        // Assert
        Assert.IsTrue(userId > 0);
        
        // Cleanup
        await _userRepository.DeleteAsync(userId);
    }
    
    [TestMethod]
    public async Task GetUserById_ShouldReturnNull_WhenUserNotExists()
    {
        // Act
        var result = await _userRepository.GetByIdAsync(99999);
        
        // Assert
        Assert.IsNull(result);
    }
}

結論

SQL Server 與 C# 的整合提供了強大且靈活的資料處理能力。透過本文介紹的技術和最佳實務,您可以:

關鍵要點總結:

  • 使用 Microsoft.Data.SqlClient 獲得最新功能和效能
  • 始終使用參數化查詢防止 SQL 注入
  • 善用 using 語句管理資源生命週期
  • 採用非同步程式設計提升應用程式回應性
  • 實作適當的錯誤處理和日誌記錄
  • 使用交易確保資料一致性
  • 針對大量資料使用 SqlBulkCopy
  • 適當設定連線池參數優化效能

進階建議:

  • 考慮使用 Entity Framework Core 簡化開發
  • 實作 Repository 模式提高程式碼可測試性
  • 監控查詢效能並優化慢查詢
  • 在 CI/CD 流程中包含資料庫相關測試
  • 定期審查和更新連線字串安全性

掌握這些技能後,您就能夠開發出高效、安全且可維護的資料存取層。記住,良好的資料庫設計和適當的索引策略同樣重要,這些將直接影響您的應用程式效能。

繼續學習和實踐,您將成為 C# 資料庫操作的專家!

Last modification:July 29, 2025
If you think my article is useful to you, please feel free to appreciate