[ASP.NET][Web API] 解決 ASP.NET Web API Json 物件循環參考錯誤


一般我們在開法 ASP.NET Web API 時,如果是使用 Entity Framework 技術來操作資料庫的話,當兩個 Entity 之間包含導覽屬性(Navigation Property)時,而當我們輸出的格式為 JSON 物件時,會出現一個例外,錯誤訊息為:「'ObjectContent`1' 類型無法序列化內容類型 'application/json; charset=utf-8' 的回應主體。」,而小弟參考了 Will 保哥以及 Bruce 兩位前輩的文章後,整理出兩種小弟覺得比較可行的替代與解決方案。

前言

一般我們在開法 ASP.NET Web API 時,如果是使用 Entity Framework 技術來操作資料庫的話,當兩個 Entity 之間包含導覽屬性(Navigation Property)時,而當我們輸出的格式為 JSON 物件時,會出現一個例外,錯誤訊息為:「'ObjectContent`1' 類型無法序列化內容類型 'application/json; charset=utf-8' 的回應主體。」,而小弟參考了 Will 保哥以及 Bruce 兩位前輩的文章後,整理出兩種小弟覺得比較可行的替代與解決方案。

了解問題

這張圖裡包含了兩張資料表 Orders 與 Order_Details ,兩者之間存在著一對多的關係,而預設 Entity Framework 會自動幫我們的關聯資料表加入導覽屬性(Navigation Property),接著我們往下一張圖看下去:


        public IEnumerable<Orders> GetOrders()
        {
            return db.Orders;
        }

這段程式碼為 ValuesController 裡的一個 Function ,當我們請求時會返回 Orders 所有資料,但當我們輸入網址 /api/Values/ 請求時卻發生了這樣的錯誤:

這個問題發生的原因為,當我們請求某個特定的 Enity 時會取出該 Entity 的所有屬性內容,當然包誇了導覽屬性的資料,而究竟這個問題如何照成呢?以目前的案例來看,當我們取得 Orders 的資料時也會一併取得其導覽屬性,也就是 Order_Details 的所有資料,而在 Order_Details 裡也包含著 Orders 的導覽屬性,所以又會在去取得 Orders 的資料....,這樣兩個實體之間不停的往返就會造成了無限迴圈,也是我們前面所說的循環參考的錯誤。

如何解決

方法一:

在開發 ASP.NET MVC 中,時常會用到部分類別(Partail Class)來為我們的資料欄位加上驗證屬性,所以利用此特性來解決我們的問題,首先在 Model 資料夾底下新增兩個檔案分別為:OrdersMetadata.cs 、Order_DetailsMetadata.cs

OrdersMetadata.cs


    [MetadataType(typeof(OrderMD))]
    public partial class Order
    {
        public class OrderMD
        {
            [JsonIgnore()] // 需引用 using Newtonsoft.Json;
            public virtual ICollection<Order_details> Order_Details { get; set; }
        }
    }

Order_DetailsMetadata.cs


    [MetadataType(typeof(Order_DetailsMD))]
    public partial class Order_Details
    {
        public class Order_DetailsMD
        {
            [JsonIgnore()]  // 需引用 using Newtonsoft.Json;
            public virtual Orders Orders { get; set; }
        }
    }

這邊我們在在對應的導覽屬性上都加上 「JsonIgnore」的屬性,來防止循環參考的問題發生,記得是有關聯的兩邊都要加上「JsonIgnore」的屬性。

方法二:

另外一種方法則是利用我們在開發 ASP.NET MVC 時常用到的 ViewModel 的概念,針對特定的頁面或請求只返回特定的資料,所以這邊我們能透過 DTO 方式來解決我們問題,首先我們先在 Model 底下新增一個 DTO.cs 檔案:

DTO.cs


    public class OrderDTO
    {
        public int OrderID { get; set; }
        public string CustomerID { get; set; }
        public int? EmployeeID { get; set; }
        public DateTime? OrderDate { get; set; }
        public List<Order_detailsDTO> Order_Detail { get; set; }
    }

    public class Order_DetailsDTO
    {
        public int OrderID { get; set; }
        public decimal UnitPrice { get; set; }
        public decimal Quantity { get; set; }
    }

並且修改我們原本在 Web API Controller 裡的 Function:


        public IEnumerable<OrderDTO> GetOrders()
        {
            NorthwindEntities db = new NorthwindEntities();
           
            return db.Orders.ToList().Select(p => new OrderDTO
            {
                CustomerID = p.CustomerID,
                EmployeeID = p.EmployeeID,
                OrderDate = p.OrderDate,
                OrderID = p.OrderID,
                Order_Detail = p.Order_Details.Select(x => new Order_DetailsDTO
                {
                    OrderID = x.OrderID,
                    Quantity = x.Quantity,
                    UnitPrice = x.UnitPrice
                }).ToList()
            }); ;
        }

總結

兩種作法都能解決造成循環物件參考的問題,而在 Bruce 和 Will 保哥的文章都指出用第一種方法來解決此循環參考錯誤是最正確的作法,兩種作法算是兩種不同的出發點,所以該怎麼用應該就是看各位讀者如何應用了。


參考來源

新手發文,如有錯誤煩請告知,感謝。
如果喜歡我的文章請按推薦,有任何問題歡迎下面留言~~~

 

 

簽名:

學習這條路很廣,喜歡什麼技術不重要,重要的是你肯花時間去學習