本文基於 ADO.NET Entity Framework 預發布版本撰寫,展示如何在採用 WCF + WPF + MVP (Passive View) 的經典 N 層架構中,無縫整合 Entity Framework。架構涵蓋:資料庫層、資料存取層 (EDM)、領域模型層、業務邏輯層、服務層 (WCF)、表現層、被動 UI 層 (WPF)。核心挑戰在於跨層傳遞實體時的序列化、變更追蹤斷點重連、以及並發控制。

DataContract 實體。ObjectContext 管理變更追蹤。[DataContract] 序列化實體,ObjectContext 不序列化、不跨層。UI 層拿到的是純 POCO-like 實體 (Detached 狀態)。Attach 實體回 ObjectContext、標記修改狀態、呼叫 SaveChanges。
建議命名慣例:
Customer、Order — 代表單一實體藍圖。Customers、Orders — 代表集合查詢入口。// LINQ 查詢語意清晰
var q = from c in context.Customers // EntitySet (複數)
select c; // 回傳 Customer (單數) 實體
List<Customer> list = q.ToList();
EDM 設計師中選取實體,在屬性視窗修改 Name (EntityType) 與 EntitySet Name:

// NWUI/CustomerSearchView.xaml.cs
private void btnSearch_Click(object sender, RoutedEventArgs e)
{
FindCustomerSearchResults?.Invoke(); // 引發事件,不直接呼叫服務
}
// NWPresentation/CustomerSearchPresenter.cs
public void view_FindCustomerSearchResults()
{
if (string.IsNullOrEmpty(view.CompanyNameCriteria)) return;
using (var svc = new NWServiceClient()) // WCF Proxy
{
// 服務回傳 IList<Customer> (DataContract 實體)
IList<Customer> customers = svc.FindCustomerList(view.CompanyNameCriteria);
view.CustomerSearchResultsList = customers; // 設定視圖屬性,觸發 WPF 資料繫結
}
}
// NWBusinessManagers/CustomerManager.cs
public class CustomerManager
{
private readonly NWEntities context = new NWEntities(); // ObjectContext 為類別欄位,生命週期跟隨 Manager
public List<Customer> FindCustomerList(string companyName)
{
var q = from c in context.Customers
where c.CompanyName.StartsWith(companyName)
select c;
return q.ToList(); // 執行查詢,實體附著於 context 追蹤
}
}
當使用者編輯客戶並儲存,實體經 WCF 傳回業務層時已處於 Detached 狀態:
// CustomerManager.cs
public void UpdateCustomer(Customer customer)
{
// 1. 重新附著到 ObjectContext
context.Attach(customer);
// 2. 標記所有屬性為 Modified (自訂擴充方法)
customer.SetAllModified(context);
// 3. 儲存變更,產生 UPDATE T-SQL
context.SaveChanges();
}
public static void SetAllModified<T>(this T entity, ObjectContext context)
where T : IEntityWithKey
{
var stateEntry = context.ObjectStateManager.GetObjectStateEntry(entity.EntityKey);
var propertyNames = stateEntry.CurrentValues.DataRecordInfo.FieldMetadata
.Select(pn => pn.FieldType.Name);
foreach (var propName in propertyNames)
stateEntry.SetModifiedProperty(propName);
}
// ClientWins:以用戶端值覆蓋資料庫 (Last-in-wins)
context.Refresh(RefreshMode.ClientWins, customer);
context.SaveChanges();
// StoreWins:放棄用戶端修改,重新載入資料庫最新值
context.Refresh(RefreshMode.StoreWins, customer);
在 EDM 中將關鍵屬性 (如 Timestamp、RowVersion 或業務欄位) 的 ConcurrencyMode 設為 Fixed。EF 會在 UPDATE/DELETE 的 WHERE 子句加入原始值比對。

try
{
context.SaveChanges();
}
catch (OptimisticConcurrencyException ex)
{
// 記錄日誌、採用 ClientWins 覆蓋或 StoreWins 重新載入提示使用者
context.Refresh(RefreshMode.ClientWins, customer); // Last-in-wins
context.SaveChanges();
}
context.Attach(customer);
context.DeleteObject(customer);
context.SaveChanges();
context.AddToCustomers(customer); // 或 context.AddObject("Customers", customer)
context.SaveChanges();
| 原則 | 說明 |
|---|---|
| 每個業務管理類別持有一個 ObjectContext | 在 Manager 建構函式建立,Dispose 時釋放。 |
| 不要長期保持同一 ObjectContext | 身份解析會導致快取資料過舊、效能下降、多執行緒衝突。 |
| Web/WCF 服務採用 Per-Call/Per-Request | 每次呼叫建立新 Context,用完即棄,確保資料最新。 |
完整可執行範例 (含 NorthwindEF 資料庫腳本、WPF UI、WCF 服務、業務層):
DataPoints2008_07.exe (3.5 MB)
Entity Framework 在 N 層架構中的核心價值在於:
掌握這些模式,即能在企業級分層應用中充分發揮 EF 的生產力優勢,同時維持架構乾淨、可測試、可維護。