架構概覽
| 層級 | 類別 | 職責 |
| 表現層 (UI) | FrmGUI | Windows Form,僅負責介面互動,呼叫 BLL |
| 商業邏輯層 (BLL) | BOCustomer | 實體屬性驗證、業務規則、協調 DAL,回傳 DataSet |
| 資料存取層 (DAL) | DACustomer | SQL 組裝、連線管理、OleDb 命令執行,不含業務邏輯 |
優勢
- 組件可重複使用:BLL/DAL 獨立編譯為 DLL,多專案共用。
- 遷移彈性:更換資料庫(Access→SQL Server→Oracle)只需修改 DAL,BLL/UI 不動。
- 維護容易:業務規則變更只更新 BLL,客戶端無需重新部署整套系統。
- 平行開發:UI、BLL、DAL 由不同人員同時開發,介面契約固定。
程式碼實作
DAL:DACustomer
using System.Data;
using System.Data.OleDb;
public class DACustomer
{
private const string ConnStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\\Data\\customer.mdb";
private const string Table = "tblCustomer";
private OleDbConnection GetConn() => new OleDbConnection(ConnStr);
public void Add(BOCustomer c)
{
string sql = $"INSERT INTO {Table} (CUS_ID, CUS_L_NAME, CUS_F_NAME, CUS_ADDRESS, CUS_TEL) " +
$"VALUES ('{c.CusID}', '{c.LName}', '{c.FName}', '{c.Address}', '{c.Tel}')";
ExecuteNonQuery(sql);
}
public void Update(BOCustomer c)
{
string sql = $"UPDATE {Table} SET CUS_L_NAME='{c.LName}', CUS_F_NAME='{c.FName}', " +
$"CUS_ADDRESS='{c.Address}', CUS_TEL='{c.Tel}' WHERE CUS_ID='{c.CusID}'";
ExecuteNonQuery(sql);
}
public DataSet Find(string id)
{
string sql = $"SELECT * FROM {Table} WHERE CUS_ID='{id}'";
using (OleDbConnection conn = GetConn())
using (OleDbDataAdapter da = new OleDbDataAdapter(sql, conn))
{
DataSet ds = new DataSet();
da.Fill(ds, Table);
return ds;
}
}
private void ExecuteNonQuery(string sql)
{
using (OleDbConnection conn = GetConn())
{
conn.Open();
new OleDbCommand(sql, conn).ExecuteNonQuery();
}
}
}
BLL:BOCustomer
using System;
using System.Data;
public class BOCustomer
{
private readonly DACustomer _dal = new DACustomer();
// 屬性含驗證
private string _cusID, _fName, _lName, _address, _tel;
public string CusID { get => _cusID; set { if (string.IsNullOrEmpty(value)) throw new Exception("請輸入 ID"); _cusID = value; } }
public string FName { get => _fName; set { if (string.IsNullOrEmpty(value)) throw new Exception("請輸入名字"); _fName = value; } }
public string LName { get => _lName; set { if (string.IsNullOrEmpty(value)) throw new Exception("請輸入姓氏"); _lName = value; } }
public string Address { get => _address; set { if (string.IsNullOrEmpty(value)) throw new Exception("請輸入地址"); _address = value; } }
public string Tel { get => _tel; set { if (string.IsNullOrEmpty(value)) throw new Exception("請輸入電話"); _tel = value; } }
public void Add() => _dal.Add(this);
public void Update() => _dal.Update(this);
public DataSet Find(string id) => string.IsNullOrEmpty(id) ? throw new Exception("請輸入查詢 ID") : _dal.Find(id);
}
UI:FrmGUI 片段
private void btnAdd_Click(object sender, EventArgs e)
{
try
{
var c = new BOCustomer { CusID = txtID.Text, FName = txtFName.Text, LName = txtLName.Text, Address = txtAddr.Text, Tel = txtTel.Text };
c.Add();
MessageBox.Show("新增成功");
}
catch (Exception ex) { MessageBox.Show(ex.Message); }
}
private void btnFind_Click(object sender, EventArgs e)
{
try
{
DataSet ds = new BOCustomer().Find(txtID.Text);
if (ds.Tables[0].Rows.Count > 0)
{
DataRow r = ds.Tables[0].Rows[0];
txtFName.Text = r["CUS_F_NAME"].ToString();
txtLName.Text = r["CUS_L_NAME"].ToString();
txtAddr.Text = r["CUS_ADDRESS"].ToString();
txtTel.Text = r["CUS_TEL"].ToString();
}
}
catch (Exception ex) { MessageBox.Show(ex.Message); }
}
改進方向
- 參數化 SQL 防注入(
OleDbParameter)。
- 引入 Repository 模式與依賴注入,BLL 不直接 new DAL。
- 使用 Entity Framework / Dapper 等 ORM 取代手寫 SQL。
- 導入 DTO 取代 DataSet,強型別傳遞資料。