領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)中導(dǎo)航屬性的最佳實(shí)踐與性能優(yōu)化指南
在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)中,領(lǐng)域?qū)邮菓?yīng)用程序的核心,它包含了業(yè)務(wù)邏輯以及對(duì)現(xiàn)實(shí)世界概念進(jìn)行建模的實(shí)體。領(lǐng)域建模的一個(gè)關(guān)鍵方面是使用導(dǎo)航屬性定義實(shí)體之間的關(guān)系。
在 C# 和 Entity Framework Core(EF Core)中,導(dǎo)航屬性允許您在實(shí)體之間遍歷關(guān)系。然而,使用不當(dāng)可能導(dǎo)致性能問題、緊耦合甚至循環(huán)引用。
本文探討了在領(lǐng)域?qū)又袑?shí)現(xiàn)導(dǎo)航屬性的最佳實(shí)踐,同時(shí)保持設(shè)計(jì)的清晰和可維護(hù)性。
理解導(dǎo)航屬性
EF Core 中的導(dǎo)航屬性定義了實(shí)體之間的關(guān)系,例如:
? 一對(duì)一(例如,用戶 ? 用戶檔案)
? 一對(duì)多(例如,訂單 ? 訂單項(xiàng))
? 多對(duì)多(例如,學(xué)生 ? 課程)
示例:
public classOrder
{
    publicint Id { get; set; }
    publicstring OrderNumber { get; set; }
    public ICollection<OrderItem> Items { get; set; } // 一對(duì)多
}
publicclassOrderItem
{
    publicint Id { get; set; }
    publicstring ProductName { get; set; }
    publicint OrderId { get; set; } // 外鍵
    public Order Order { get; set; } // 導(dǎo)航回 Order
}導(dǎo)航屬性的最佳實(shí)踐
謹(jǐn)慎使用延遲加載
EF Core 支持延遲加載,但如果使用不當(dāng),可能導(dǎo)致 N+1 查詢問題。
? 應(yīng)該做:
使用 virtual 關(guān)鍵字實(shí)現(xiàn)延遲加載(如果需要):
public virtual ICollection<OrderItem> Items { get; set; }? 應(yīng)避免:
在 Web 應(yīng)用程序中過度使用延遲加載(更推薦使用 .Include() 進(jìn)行預(yù)先加載)。
使用顯式加載以獲得更好的控制
考慮使用顯式加載代替延遲加載:
var order = dbContext.Orders.First();
dbContext.Entry(order).Collection(o => o.Items).Load();在不需要時(shí)避免雙向?qū)Ш?/span>
并非所有關(guān)系都需要雙向?qū)Ш健H绻?nbsp;OrderItem 不需要引用 Order,則可以省略:
public class OrderItem
{
    public int Id { get; set; }
    public string ProductName { get; set; }
    public int OrderId { get; set; } // 僅保留外鍵
    // public Order Order { get; set; } 不需要導(dǎo)航回 Order
}使用私有 Set 器實(shí)現(xiàn)不可變性
為了強(qiáng)制執(zhí)行領(lǐng)域規(guī)則,限制屬性修改:
public IReadOnlyCollection<OrderItem> Items { get; private set; } = new List<OrderItem>();小心處理聚合根
在 DDD 中,聚合根控制對(duì)子實(shí)體的訪問。避免暴露破壞封裝的導(dǎo)航屬性。
public class Order : AggregateRoot
{
    private readonly List<OrderItem> _items = new();
    public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
    public void AddItem(OrderItem item)
    {
        // 在添加前驗(yàn)證業(yè)務(wù)規(guī)則
        _items.Add(item);
    }
}性能考量
注意 N+1 查詢問題
延遲加載可能觸發(fā)多個(gè)數(shù)據(jù)庫(kù)查詢。請(qǐng)使用:
? 預(yù)先加載(.Include())
? 投影(.Select())僅加載所需的數(shù)據(jù)。
考慮使用 DTO 代替直接暴露實(shí)體
直接返回領(lǐng)域?qū)嶓w可能導(dǎo)致數(shù)據(jù)過度獲取。為 API 使用 DTO(數(shù)據(jù)傳輸對(duì)象):
public class OrderDto
{
    public int Id { get; set; }
    public List<OrderItemDto> Items { get; set; }
}避免循環(huán)引用
如果 Order 引用 User 并且 User 引用 Order,JSON 序列化可能會(huì)失敗。
? 解決方案:
? 在一個(gè)導(dǎo)航屬性上使用 [JsonIgnore]。
? 配置 EF Core 忽略一側(cè)的關(guān)系:
modelBuilder.Entity<Order>()
    .HasOne(o => o.User)
    .WithMany()
    .OnDelete(DeleteBehavior.Restrict);測(cè)試導(dǎo)航屬性
確保導(dǎo)航屬性在單元測(cè)試中按預(yù)期工作:
[Fact]
public void Order_Should_Have_Items()
{
    var order = new Order();
    order.AddItem(new OrderItem("Product1"));
    Assert.Single(order.Items);
}即使沒有 EF Core,為何還要使用導(dǎo)航屬性?
導(dǎo)航屬性不僅僅是 ORM(EF Core)的一個(gè)功能——它們是一種領(lǐng)域建模工具。
使用導(dǎo)航屬性的關(guān)鍵理由:
? 表現(xiàn)力:清晰定義領(lǐng)域?qū)嶓w之間的關(guān)系。
? 封裝性:控制實(shí)體如何交互(例如,使用 Order.AddItem() 而不是直接操作列表)。
? 業(yè)務(wù)邏輯強(qiáng)制執(zhí)行:確保不變性(例如,一個(gè) OrderItem 不能沒有 Order 而存在)。
? 可測(cè)試性:更容易在沒有數(shù)據(jù)庫(kù)的情況下模擬和測(cè)試領(lǐng)域行為。
何時(shí)應(yīng)避免使用導(dǎo)航屬性(在沒有 EF Core 的情況下)?
? 如果性能至關(guān)重要(例如,在高負(fù)載系統(tǒng)中,對(duì)象遍歷成本高昂)。
? 如果使用微服務(wù)架構(gòu)(更傾向于通過 ID 進(jìn)行松耦合引用)。
? 如果使用 NoSQL 數(shù)據(jù)庫(kù)(其關(guān)系處理方式不同)。















 
 
 











 
 
 
 