2012-11-19 16 views
7

我是新來的實體框架,並試圖學習如何使用Code First從數據庫加載實體。如何防止實體框架中相關實體的循環加載代碼優先

我的模型包含一個用戶:

public class User 
{ 
    public int UserID { get; set; } 

    [Required] 
    public string Name { get; set; } 

    // Navigation Properties 
    public virtual ICollection<AuditEntry> AuditEntries { get; set; } 
} 

每個用戶都可以有一組審計條目,每個條目都包含一個簡單的信息:

public class AuditEntry 
{ 
    public int AuditEntryID { get; set; } 

    [Required] 
    public string Message { get; set; } 

    // Navigation Properties 
    public int UserID { get; set; } 
    public virtual User User { get; set; } 
} 

我有一個的DbContext剛剛暴露出兩個表格:

public DbSet<User> Users { get; set; } 
public DbSet<AuditEntry> AuditEntries { get; set; } 

我想要做的是加載一個包含混亂的AuditEntry對象列表年齡和包含UserID和Name屬性的相關User對象。

List<AuditEntry> auditEntries = db.AuditEntries.ToList(); 

因爲我有我的導航屬性標記爲虛,我還沒有禁用延遲加載,我得到一個無限深對象圖(每個AuditEntry有一個用戶對象,它包含了AuditEntries的列表,每個其中包含一個用戶對象,其中包含一個審覈條目列表等)

如果我然後想要序列化對象(例如作爲結果發送到Web API),這是沒有用的。我試過關閉延遲加載(通過從我的導航屬性中刪除模型中的虛擬關鍵字,或者通過將this.Configuration.LazyLoadingEnabled = false;添加到我的DBContext中)。正如預期的那樣,這將導致User設置爲null的AuditEntry對象的平面列表。

懶加載的時候,我一直在努力,渴望加載用戶像這樣:

var auditentries = db.AuditEntries.Include(a => a.User); 

但像以前那樣會導致同一個深/循環的結果。

如何在沒有將反向引用/導航屬性加載回原始對象並創建循環的情況下加載一個深度級別(例如,包括用戶的ID和名稱)?

+0

'Include'應該做到這一點。你確定'User.AuditEntries'不是延遲加載嗎? –

+0

如果在上下文處理後序列化對象會怎麼樣? –

+0

我認爲最初但我不認爲它是延遲加載 - 從我的所有導航屬性中刪除虛擬關鍵字並明確禁用延遲加載似乎沒有幫助。 我認爲這是因爲我的導航屬性是雙向的,例如,用戶具有一個AuditEntries列表,而AuditEntries定義它所屬的用戶。我想查詢AuditEntries幷包含用戶信息,但我不希望每個頂級AuditEntry對象都包含該用戶的所有其他AuditEntry的列表。 這很棘手,因爲我正在努力描述發生了什麼! –

回答

2

多的黑客後,我想出了用動態返回類型和投射在我的LINQ查詢的以下潛在的解決方案:下面的SQL這似乎是明智的

public dynamic GetAuditEntries() 
{ 
    var result = from a in db.AuditEntries 
       select new 
       { 
        a.AuditEntryID, 
        a.Message, 
        User = new 
        { 
         a.User.UserID, 
         a.User.Username 
        } 
       }; 

    return result; 
} 

這將產生(內部):

SELECT 
[Extent1].[AuditEntryID] AS [AuditEntryID], 
[Extent1].[Message] AS [Message], 
[Extent1].[UserID] AS [UserID], 
[Extent2].[Username] AS [Username] 
FROM [dbo].[AuditEntries] AS [Extent1] 
INNER JOIN [dbo].[Users] AS [Extent2] ON [Extent1].[UserID] = [Extent2].[UserID] 

這產生了我以後的結果,但它似乎有點冗長(特別是對於比我的例子更復雜的現實生活模型),我質疑這會對性能產生的影響。

優勢

  • 這給了我很大的靈活性在我返回對象的確切內容。由於我通常在客戶端執行大部分UI交互/模板,所以我經常發現自己必須創建多個版本的模型對象。我通常需要一定的粒度,用戶可以在其中查看哪些屬性(例如,我可能不希望在AJAX請求中將每個用戶的電子郵件地址發送給低特權用戶的瀏覽器)

  • 它允許實體框架智能地構建查詢並僅選擇我選擇投影的字段。例如,在每個頂級AuditEntry對象內,我想查看User.UserID和User.Username,但不是User.AuditEntries。

缺點

  • 從我的Web API返回的類型不再是強類型的,所以我不能創建基於此API的強類型MVC視圖。碰巧這對我的特殊情況不是問題。

  • 以這種方式從大型/複雜模型手動投影可能會導致大量代碼,看起來像很多工作,並有可能在API中引入錯誤。這必須經過仔細測試。

  • API方法與模型的結構緊密結合,並且由於這不再基於我的POCO類完全自動化,所以對模型所做的任何更改都必須反映在加載它們的代碼中。

包含方法?

我仍然對使用.Include()方法有點困惑。我知道這種方法將指定相關實體應該與指定實體一起「急切加載」。然而,由於指導原則似乎應該將導航屬性放在關係的兩側並標記爲虛擬,因此Include方法似乎會導致創建一個對其有用性產生重大負面影響的循環(特別是在序列化時) 。

在我的情況下,「樹」看起來有點像:

AuditEntry 
    User 
     AuditEntries * n 
      User * n 
       etc 

我會聽到這種方式,這種方式或其他見解使用動態的影響任何意見很感興趣。

+0

嗨馬特威爾遜, 感謝您分享的知識。 我正在構建一些完全無類型的dbset訪問。 公共牛逼的Findentity (對象ID列表包括) 哪裏需要包括所有的人都沒有循環引用。 對此有任何幫助...... –

+0

而不是'new {...}'您是否嘗試過'新的AuditEntry {...}'等等?由於類似的原因,我已經能夠使用類似的模式並取得了一些成功。看起來,當你明確地創建實例時,EntityFramework並不在乎它在解碼結果集時實例化哪些類。 – SingleNegationElimination