EDMX Navigation Property 屬性引發 JSON 物件循環參考錯誤與解決辦法[转]

2013年4月

Entity Framework (EDMX) Navigation Property

當我們透過Model first或Database first建立Entity Framework實體資料模式(EDMX),例如下圖是一個ASP.NET Web API專案,從範例Northwind資料庫裡加入兩張有關連性的資料表到EDMX,資料表的關連性在EDMX裡會以Navigation Property來呈現。

Dn260087.29FBC4453DD81A285DB0F573AFE594FE(zh-tw,MSDN.10).png

當我們什麼都不修改,直接加入API Controller,例如,ProcutsController和OrderDetailsController然後建置、啟動、測試ASP.NET Web API服務會得到一個錯誤。

Dn260087.9B2365C5BADFEFA6B3A87C5FC44E46F3(zh-tw,MSDN.10).png

完整訊息如下:

 
 
{
    "Message": "發生錯誤。",
    "ExceptionMessage": "'ObjectContent`1' 類型無法序列化內容類型 'application/json; charset=utf-8' 的回應主體。",
    "ExceptionType": "System.InvalidOperationException",
    "StackTrace": null,
    "InnerException": {
        "Message": "發生錯誤。",
        "ExceptionMessage": "Self referencing loop detected for property 'Product' with type 'System.Data.Entity.DynamicProxies.Product_65FAC6E44EE4BB6B00D5AD1D9A45D7BE6D877BB340CA7CD682A1F0D0A551EE53'. Path '[0].Order_Details[0]'.",
        "ExceptionType": "Newtonsoft.Json.JsonSerializationException",
        "StackTrace": "   於 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.CheckForCircularReference(JsonWriter writer, Object value, JsonProperty property, JsonContract contract, JsonContainerContract containerContract, JsonProperty containerProperty)\r\n   於 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.CalculatePropertyValues(JsonWriter writer, Object value, JsonContainerContract contract, JsonProperty member, JsonProperty property, JsonContract& memberContract, Object& memberValue)\r\n   於 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)\r\n   於 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)\r\n   於 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IWrappedCollection values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)\r\n   於 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)\r\n   於 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)\r\n   於 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)\r\n   於 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IWrappedCollection values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)\r\n   於 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)\r\n   於 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value)\r\n   於 Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value)\r\n   於 Newtonsoft.Json.JsonSerializer.Serialize(JsonWriter jsonWriter, Object value)\r\n   於 System.Net.Http.Formatting.JsonMediaTypeFormatter.<>c__DisplayClassd.<WriteToStreamAsync>b__c()\r\n   於 System.Threading.Tasks.TaskHelpers.RunSynchronously(Action action, CancellationToken token)"
    }
}

從錯誤訊息(ExceptoinMessage)可以找到問題點,JSON.NET發現了一個「Self referencing loop」的問題。查詢Northwind.tt下關連的Product.vb與Order_Detail.vb:

 
 
Product.vb
Partial Public Class Product
    ' …略 …
    ' 參考了Order_Detail
    Public Overridable Property Order_Details As ICollection(Of Order_Detail) = New HashSet(Of Order_Detail)
End Class
Order_Detail.vb
Partial Public Class Order_Detail
    ' …略 …
    ' 參考了Product
    Public Overridable Property Product As Product
End Class

這種「物件A參考物件B,物件B又參考物件A」的情況稱為物件循環參考

JSON物件循環參考解決辦法

解決辦法一:使用WebApiConfig組態檔設置物件循環參考處理方法

在WebApiConfig組態檔中進行以下設置:

 
 
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore

不過此種方式會副作用,就是產生的JSON格式內容可能和我們預期的內容有很大的差異,例如:

 
 
{
    "$id": "1",
    "$values": [
        {
            "$id": "2",
            "Order_Details": {
                "$id": "3",
                "$values": [
                    {
                        "$id": "4",
                        "Product": {
                            "$ref": "2"
                        },
                        "OrderID": 10285,
                        "ProductID": 1,
                        "UnitPrice": 14.4,
                        "Quantity": 45,
                        "Discount": 0.2
                    },
                    {
                        "$id": "5",
                        "Product": {
                            "$ref": "2"
                        },
                        "OrderID": 10294,
                        "ProductID": 1,
                        "UnitPrice": 14.4,
                        "Quantity": 18,
                        "Discount": 0
                    },
                    // …略…

解決辦法二:使用部分類別進行JSON.NET屬性設置

使用部分類別是最好且最正確的做法,因為不管是在ASP.NET MVC或ASP.NET WebAPI裡,在進行Models的驗證屬性設置時,都一定會使用到部分類別,所以由部分類別來集中處理管理與設置是較好的,而且產生JSON格式又是正確的。

 
 
Product.vb(部分類別)
Imports System.ComponentModel.DataAnnotations
Imports Newtonsoft.Json
<MetadataType(GetType(ProductMD))>
Partial Public Class Product
End Class
Public Class ProductMD
    Public Property ProductID As Integer
    ' …略…
    ' 必須引用Newtonsoft.Json
    <JsonIgnore()>
    Public Overridable Property Order_Details As ICollection(Of Order_Detail) = New HashSet(Of Order_Detail)
End Class
Order_Detail.vb(部分類別)
Imports System.ComponentModel.DataAnnotations
Imports Newtonsoft.Json
<MetadataType(GetType(Order_DetailMD))>
Partial Public Class Order_Detail
End Class
Public Class Order_DetailMD
    Public Property OrderID As Integer
    ' …略…
       ' 必須引用Newtonsoft.Json
    <JsonIgnore()>
    Public Overridable Property Product As Product
End Class

只需要在部份類別裡去設置JSON.NET的<JsonIgnore>屬性,這樣當ASP.NET Web API在處理含有物件循環參考的類別時,就會自行排除物件循環參考的情況。

參考資料:

posted @ 2014-09-17 22:34  redhairboy  阅读(393)  评论(0编辑  收藏  举报