ASP.Net MVC-Web API使用Entity Framework時遇到Loop Reference

ASP.Net MVC-Web API使用Entity Framework時遇到Loop Reference

最近開始研究Web API,運氣不錯第一個測試專案就遇到問題@@-當新增Control時選擇[API Controller woth read/write actions, using Entity Framework]然後使用Northwnd資料庫,資料表選擇Orders,Order_Details,Products.


imageimage

前端javascript程式碼如下

Code Snippet
  1.  
  2. @section scripts{
  3.     <script type="text/javascript">
  4.         $(document).ready(function () {
  5.  
  6.             $.getJSON('/api/order/', function (data) {
  7.                 alert(data);
  8.             })
  9.             .error(function (jqXHR, textStatus, err) {
  10.                 alert('Error: ' + err);
  11.             });
  12.         });
  13.     </script>
  14. }

執行後出現錯誤,查詢完整的錯誤說明為Self referencing loop detected…,也就是循環參考.


image

 

這錯誤訊息好熟悉,突然想起以前開發Silverlight RIA Service好像也有類似情況.經上網查詢原因後果不其然,問題是一樣的,因為ASP.Net MVC Web API預設採用JSON.Net作為輸出轉換,而預設的情況下JSON.Net會自動一層層解析要輸出的物件之屬性,也就說這個例子中的輸出物件為Order,其中有個屬性為Order_Details,而Order_Details也有個屬性參考至Order,所以產生了循環相依問題,導致錯誤產生.

這個問題處理方式有三種

1.最簡單的方式就是從Entity Framework著手,停用LazyLoading與ProxyCreation.因為LazyLoading停用後那麼當JSON.Net解析Order物件時其屬性Order_Details會返回null(不會自動載入).所以也就避免了此問題

當然此方式的缺點會導致後續程式存取Entity Object時犧牲了LazyLoading的方便性,需要手動處理此問題.

Code Snippet
  1. db.Configuration.LazyLoadingEnabled = false;
  2. db.Configuration.ProxyCreationEnabled = false;
  3.  
  4. return db.Orders.AsEnumerable();

 

2.設定JSON.Net忽略循環參考

透過APP_Start的WebApiConfig.cs,設定config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

使用這個方式時要注意,它只是忽略循環參考的錯誤,但實際上還是會自動一層層解析要輸出的物件之屬性,所以若資料會相依有可能會產生無窮迴圈.

 

3.設定JSON.Net避免循環參考

透過APP_Start的WebApiConfig.cs,設定

config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize;

config.Formatters.JsonFormatter.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;

這種做法與2的差異在於它會將重複過的物件用一個代表取代,譬如底下JSON格式
1: [{"$id":"1","Category":{"$id":"2","Products":[{"$id":"3","Category":{"$ref":"2"},"Id":2,"Name":"Yogurt"},{"$ref":"1"}],"Id":1,"Name":"Diary"},"Id":1,"Name":"Whole Milk"},{"$ref":"3"}]

所以對於資料而言這種作法還是會自動一層層解析要輸出的物件之屬性,只是避免輸出太大量資料.

 

4.手動設定避免循環參考

如同3的模式,透過[JsonIgnore] 與[JsonObject(IsReference = true)] 細部設定,可以更精確的設定每個要輸出的屬性.

缺點是1.設定繁雜. 2.只能通用設定無法例外. 3.因為必須直接或透過 partial class方式設定,故無法將設定與Entity Object class做分離

 

以上作法實際上都有其優缺點, 並沒有一個可以通用的模式, 須看需求而定,這問題與同早期RIA Service的問題相同,但這邊提供一種比較通用的模式就是採用方法1+方法2或3.

停用LazyLoading,而使用程式的方式(透過 Include方法)決定那些屬性要輸出.