傳統資料存取方法常在內部自行建立/關閉 SqlConnection 與 SqlTransaction:
public void UpdateOrder(Order order)
{
using (var conn = new SqlConnection(connStr))
using (var cmd = conn.CreateCommand())
{
conn.Open();
cmd.CommandText = "UPDATE Orders SET ...";
cmd.ExecuteNonQuery();
}
}
public void UpdateOrderDetails(List<OrderDetail> details)
{
using (var conn = new SqlConnection(connStr)) // 另一個連線!
using (var cmd = conn.CreateCommand())
{
conn.Open();
// ...
}
}
當業務邏輯需要 UpdateOrder 與 UpdateOrderDetails 在同一交易中執行時,必須大幅重構方法簽名,傳遞 conn、tran 參數,違反開放封閉原則。
引入環境上下文物件,由呼叫端控制連線與交易生命週期,資料存取方法僅從上下文取得連線:
public interface IDataContext : IDisposable
{
IDbConnection Connection { get; }
IDbTransaction Transaction { get; }
void Commit();
void Rollback();
}
public class SqlDataContext : IDataContext
{
public IDbConnection Connection { get; private set; }
public IDbTransaction Transaction { get; private set; }
public SqlDataContext(string connectionString)
{
Connection = new SqlConnection(connectionString);
Connection.Open();
}
public void BeginTransaction()
{
Transaction = Connection.BeginTransaction();
}
public void Commit() => Transaction?.Commit();
public void Rollback() => Transaction?.Rollback();
public void Dispose()
{
Transaction?.Dispose();
Connection?.Dispose();
}
}
public class OrderRepository
{
private readonly IDataContext _ctx;
public OrderRepository(IDataContext ctx) => _ctx = ctx;
public void UpdateOrder(Order order)
{
using (var cmd = _ctx.Connection.CreateCommand())
{
cmd.Transaction = _ctx.Transaction;
cmd.CommandText = "UPDATE Orders SET Status=@Status WHERE Id=@Id";
// 參數設定...
cmd.ExecuteNonQuery();
}
}
public void UpdateDetails(List<OrderDetail> details)
{
using (var cmd = _ctx.Connection.CreateCommand())
{
cmd.Transaction = _ctx.Transaction;
// 批次處理...
}
}
}
public class OrderService
{
private readonly string _connStr;
public void ProcessOrder(Order order, List<OrderDetail> details)
{
using (var ctx = new SqlDataContext(_connStr))
{
ctx.BeginTransaction();
try
{
var repo = new OrderRepository(ctx);
repo.UpdateOrder(order);
repo.UpdateDetails(details);
ctx.Commit();
}
catch
{
ctx.Rollback();
throw;
}
}
}
}
DataContext 可實作參考計數,內層 using 僅增加計數,最外層 Dispose 才真正關閉連線AsyncLocal<IDataContext> 或 CallContext,實現 AmbientContext 模式,方法完全無參數即可取得當前連線(類似 Entity Framework 的 DbContext)| 維度 | 傳統內建連線 | 上下文模式 |
|---|---|---|
| 交易邊界控制 | 分散在各方法 | 集中在業務邏輯層 |
| 方法複用性 | 低(簽名固定) | 高(無連線參數) |
| 測試性 | 難以 Mock | 易於注入測試雙件 |
| 連線池效率 | 頻繁開關 | 單一連線跨多方法 |