首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 實作三層式架構完整教學:以客戶資料管理為例

     編輯:關於C語言

架構概覽

三層式架構將應用程式分為三個邏輯層次,各司其職、低耦合、高內聚:

層次職責本範例類別關鍵技術
表現層 (UI) 使用者介面、輸入驗證、結果顯示 FrmGUI (WinForm) 事件處理、DataBinding、例外捕捉
商業邏輯層 (BLL) 業務規則、資料驗證、流程協調 BOCustomer 屬性驗證、委派 DAL、回傳 DataSet
資料存取層 (DAL) SQL 執行、參數化、連線管理 DACustomer OleDb (Access)、SQL 組裝、DataAdapter

完整程式碼實作 (已重構優化)

1. 表現層 — FrmGUI.cs

using System;
using System.Data;
using System.Windows.Forms;

namespace ThreeTierDemo
{
    public partial class FrmGUI : Form
    {
        private BOCustomer _currentCustomer;

        public FrmGUI() => InitializeComponent();

        // 新增客戶
        private void btnAdd_Click(object sender, EventArgs e)
        {
            try
            {
                var customer = BuildCustomerFromUI();
                customer.Add(); // 呼叫 BLL
                MessageBox.Show("新增成功!");
                ClearForm();
            }
            catch (Exception ex)
            {
                MessageBox.Show($"錯誤:{ex.Message}");
            }
        }

        // 查詢客戶
        private void btnFind_Click(object sender, EventArgs e)
        {
            try
            {
                string id = txtID.Text.Trim();
                if (string.IsNullOrEmpty(id)) throw new Exception("請輸入客戶 ID");

                var bo = new BOCustomer();
                DataSet ds = bo.Find(id);
                
                if (ds.Tables[0].Rows.Count == 0)
                {
                    MessageBox.Show("查無資料");
                    return;
                }

                BindUI(ds.Tables[0].Rows[0]);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"錯誤:{ex.Message}");
            }
        }

        // 更新客戶
        private void btnUpdate_Click(object sender, EventArgs e)
        {
            try
            {
                var customer = BuildCustomerFromUI();
                customer.Update();
                MessageBox.Show("更新成功!");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"錯誤:{ex.Message}");
            }
        }

        private BOCustomer BuildCustomerFromUI()
        {
            return new BOCustomer
            {
                CusID = txtID.Text.Trim(),
                FName = txtFName.Text.Trim(),
                LName = txtLName.Text.Trim(),
                Tel = txtTel.Text.Trim(),
                Address = txtAddress.Text.Trim()
            };
        }

        private void BindUI(DataRow row)
        {
            txtFName.Text = row["CUS_F_NAME"].ToString();
            txtLName.Text = row["CUS_L_NAME"].ToString();
            txtTel.Text = row["CUS_TEL"].ToString();
            txtAddress.Text = row["CUS_ADDRESS"].ToString();
        }

        private void ClearForm()
        {
            foreach (Control c in Controls)
                if (c is TextBox tb) tb.Clear();
        }
    }
}

2. 商業邏輯層 — BOCustomer.cs

using System;
using System.Data;

namespace ThreeTierDemo
{
    public class BOCustomer
    {
        private readonly DACustomer _dal = new DACustomer();

        // 屬性與驗證
        private string _fName;
        public string FName
        {
            get => _fName;
            set
            {
                if (string.IsNullOrWhiteSpace(value))
                    throw new ArgumentException("名字不可為空");
                _fName = value.Trim();
            }
        }

        private string _lName;
        public string LName
        {
            get => _lName;
            set
            {
                if (string.IsNullOrWhiteSpace(value))
                    throw new ArgumentException("姓氏不可為空");
                _lName = value.Trim();
            }
        }

        public string CusID { get; set; } // ID 可由資料庫自動產生,這裡簡化

        private string _address;
        public string Address
        {
            get => _address;
            set => _address = string.IsNullOrWhiteSpace(value) 
                ? throw new ArgumentException("地址不可為空") : value.Trim();
        }

        private string _tel;
        public string Tel
        {
            get => _tel;
            set => _tel = string.IsNullOrWhiteSpace(value)
                ? throw new ArgumentException("電話不可為空") : value.Trim();
        }

        // 業務方法:委派給 DAL
        public void Add() => _dal.Add(this);
        public void Update() => _dal.Update(this);
        public DataSet Find(string id) => string.IsNullOrWhiteSpace(id) 
            ? throw new ArgumentException("查詢 ID 不可為空") 
            : _dal.Find(id);
    }
}

3. 資料存取層 — DACustomer.cs

using System;
using System.Data;
using System.Data.OleDb;

namespace ThreeTierDemo
{
    public class DACustomer
    {
        // 連線字串建議從設定檔讀取
        private const string ConnStr = 
            "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\\customer.mdb;";

        private const string Table = "tblCustomer";
        private const string ColID = "CUS_ID";
        private const string ColLName = "CUS_L_NAME";
        private const string ColFName = "CUS_F_NAME";
        private const string ColTel = "CUS_TEL";
        private const string ColAddr = "CUS_ADDRESS";

        public void Add(BOCustomer bo)
        {
            string sql = $@"INSERT INTO {Table} ({ColID}, {ColLName}, {ColFName}, {ColAddr}, {ColTel})
                           VALUES (?, ?, ?, ?, ?)";

            using (var conn = new OleDbConnection(ConnStr))
            using (var cmd = new OleDbCommand(sql, conn))
            {
                cmd.Parameters.AddWithValue("@id", bo.CusID);
                cmd.Parameters.AddWithValue("@lname", bo.LName);
                cmd.Parameters.AddWithValue("@fname", bo.FName);
                cmd.Parameters.AddWithValue("@addr", bo.Address);
                cmd.Parameters.AddWithValue("@tel", bo.Tel);

                conn.Open();
                cmd.ExecuteNonQuery();
            }
        }

        public void Update(BOCustomer bo)
        {
            string sql = $@"UPDATE {Table} SET 
                           {ColLName}=?, {ColFName}=?, {ColAddr}=?, {ColTel}=?
                           WHERE {ColID}=?";

            using (var conn = new OleDbConnection(ConnStr))
            using (var cmd = new OleDbCommand(sql, conn))
            {
                cmd.Parameters.AddWithValue("@lname", bo.LName);
                cmd.Parameters.AddWithValue("@fname", bo.FName);
                cmd.Parameters.AddWithValue("@addr", bo.Address);
                cmd.Parameters.AddWithValue("@tel", bo.Tel);
                cmd.Parameters.AddWithValue("@id", bo.CusID);

                conn.Open();
                cmd.ExecuteNonQuery();
            }
        }

        public DataSet Find(string id)
        {
            string sql = $"SELECT * FROM {Table} WHERE {ColID} = ?";
            var ds = new DataSet();

            using (var conn = new OleDbConnection(ConnStr))
            using (var adapter = new OleDbDataAdapter(sql, conn))
            {
                adapter.SelectCommand.Parameters.AddWithValue("@id", id);
                adapter.Fill(ds, Table);
            }
            return ds;
        }
    }
}

架構優勢實證分析

1. 重用性 — BLL 可被多種 UI 呼叫

// WinForm
var bo = new BOCustomer(); bo.Add();

// ASP.NET WebForm / MVC Controller
public ActionResult Create(CustomerViewModel vm)
{
    var bo = new BOCustomer { FName = vm.FName, ... };
    bo.Add();
    return RedirectToAction("Index");
}

// WPF / Blazor / Web API 同樣可用

2. 可維護性 — 變更資料庫不影響 BLL/UI

  • Access → SQL Server:只需修改 DACustomer 連線字串與 OleDb → SqlClient
  • 資料表欄位異動:只調整 DAL SQL 與參數對應
  • 新增快取層:在 BLL 與 DAL 間插入 CachedCustomerRepository

3. 可測試性 — 單元測試 BLL 可 Mock DAL

[Test]
public void Add_ShouldCallDalAdd_WhenValidCustomer()
{
    // Arrange
    var mockDal = new Mock();
    var bo = new BOCustomer(mockDal.Object) // 需修改 BLL 採用介面注入
    {
        CusID = "1", FName = "John", LName = "Doe", Tel = "123", Address = "NY"
    };

    // Act
    bo.Add();

    // Assert
    mockDal.Verify(d => d.Add(It.Is(c => c.CusID == "1")), Times.Once);
}

4. 平行開發 — 三小組同時作業

  • UI 組:設計介面、綁定模擬資料
  • BLL 組:實作業務規則、單元測試
  • DAL 組:優化 SQL、建立索引、測試效能

進階重構方向

  1. 介面抽象:定義 ICustomerRepository,BLL 依賴介面而非具體 DAL
  2. 依賴注入:使用 Autofac/內建 DI 容器管理生命週期
  3. Entity/DTO 分離:BLL 使用領域實體,DAL 使用資料傳輸物件
  4. 工作單元模式:跨多個 Repository 的交易管理
  5. 非同步支援:Task<DataSet> FindAsync() 避免阻塞 UI 執行緒
  6. Dapper/EF Core:替換手寫 ADO.NET,提升開發效能與型別安全

結語

三層式架構雖非銀彈,但對於中小型專案、團隊協作、長期維護系統,提供了清晰的關注點分離與演進路徑。關鍵在於:嚴格遵守層次呼叫規則 (UI→BLL→DAL,不可跳層或反向依賴),並隨專案成長適時引入介面抽象與依賴注入,平滑演進至整潔架構或領域驅動設計。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved