在 NBear 快速開發框架早期範例中,業務邏輯常直接分散於 Service 或 Facade 層,僅透過強類型實體介面操作資料。隨著業務複雜度提升,這種「貧血模型」導致領域知識外洩、重複程式碼激增、單元測試困難。NBear v1.6 引入 NBear.Domain 命名空間,提供 DomainModel 與 GuidKeyDomainModel 兩大基類,讓開發者能以最小成本建立「充血模型」,將實體屬性存取、關聯導航、驗證邏輯、事務控制封裝於領域類別內部,實現關注點分離。
DomainModel<IEntityType, IEntityViewType, DomainType> 實作 IDomainModel 介面,內建三大能力:
entityValue(目前值)與 entityValue2(原始值)配合 Gateway.CompareEntities 實現「僅更新修改欄位」的精準 SQL 生成。Save() 自動判斷新增/更新,Find/FindAll/Delete 靜態方法支援鍵值與排序查詢,LoadFromEntity 負責 View 實體到 Domain 實體的雙向轉換。DbTransaction 多載,配合 Gateway.BeginTransaction/Commit/Rollback 實現跨實體原子操作。GuidKeyDomainModel 覆寫 DoCreate,以 Guid.NewGuid() 產生主鍵,適合分散式系統或合併複製場景。
使用 NBear.Tools.EntityGen 對範例資料庫 TestRelation.mdb 產生 Entities.cs,定義 Group、Message、User、UserGroup 等實體與對應 View 介面(vGroup、vMessage、vUser),View 介面包含關聯欄位(如 UserID)以利單表查詢。
public class Group : DomainModel<Entities.Group, Entities.vGroup, Group> {
private OneToMany<...> groupToUsers;
public override void LoadFromEntity(Entities.vGroup view) {
base.LoadFromEntity(view);
groupToUsers = new OneToMany<...>(Gateway.Db, "GroupID", entityValue, view);
}
public User[] FindUsersInGroup(string orderBy) =>
groupToUsers == null ? null : User.EntityViewArrayToDomainArray(groupToUsers.SelectMany(null, orderBy));
public void AddUserToGroup(object userID) { /* ... */ }
public void DeleteUserFromGroup(object userID) { /* ... */ }
}
OneToMany 封裝關聯實體(UserGroup)的查詢、新增、刪除,業務層僅需呼叫 AddUserToGroup 即可完成中間表維護,完全隱藏 SQL 細節。
GatewayManager.DefaultGateway = new Gateway(DatabaseType.MsAccess, connStr);
var group = Group.Find(1);
var users = group.FindUsersInGroup("ID desc");
group.EntityValue.Description = "modified";
group.Save(); // 僅產生 UPDATE Description WHERE ID=1
using (var tran = User.Gateway.BeginTransaction()) {
try {
user.EntityValue.Salary = 99999; user.Save(tran);
var msg = new Message { EntityValue = { Title="new msg", Content="salary changed", FromUserID=user.ID, ToUserID=3, SendTime=DateTime.Now } };
msg.Save(tran);
tran.Commit();
} catch { tran.Rollback(); throw; }
finally { User.Gateway.CloseTransaction(tran); }
}
程式碼清晰展現「領域模型封裝業務規則、應用層協調事務邊界」的分層優勢。
EntityViewValue 存取 View 專屬欄位(如 vUser.GroupID),避免額外 Join 查詢。Gateway.BulkInsert 繞過領域層,效能優先。