2010-01-04 55 views
37

我有具有圓形參照其他對象的對象。鑑於這些對象之間的關係,這是正確的設計。JSON和循環引用異常

來說明

Machine => Customer => Machine 

不愧我遇到一個問題,當我嘗試使用JSON序列化機或客戶對象。什麼我不確定的是如何解決這個問題,因爲我不想打破機和客戶對象之間的關係。有什麼解決這個問題的選擇?

編輯

目前我使用Json method provided by the Controller base class。所以我做了序列化是基本的:

Json(machineForm); 

回答

51

更新:

不要嘗試使用NonSerializedAttribute,爲JavaScriptSerializer顯然忽略它。

而是使用System.Web.Script.SerializationScriptIgnoreAttribute

public class Machine 
{ 
    public string Customer { get; set; } 

    // Other members 
    // ... 
} 

public class Customer 
{ 
    [ScriptIgnore] 
    public Machine Machine { get; set; } // Parent reference? 

    // Other members 
    // ... 
} 

這樣,當你折騰MachineJson方法,它會遍歷從Machine的關係Customer但不會嘗試回去從CustomerMachine

您的代碼仍然存在關係,但JavaScriptSerializer(由Json方法使用)將忽略它。

+0

我將不得不提供一個鏡頭,但不幸的是這是來自一個域/核心對象,我不確定我想引入對該項目的System.Web.Script.Serialization的引用。我將這些核心對象封裝在一個模型中,我想我可以使用'ScriptIgnore',但在那時我可以刪除循環引用。此解決方案似乎提供了類似的解決方案,以匿名類型http://stackoverflow.com/questions/372955/best-way-to-filter-domain-objects-for-json-output-in-an-asp-net -mvc-application/372977#372977 – ahsteele 2010-01-05 01:46:35

+0

也可以。你應該對你的問題/評論有具體的說明 - 說你不能/不會刪除循環引用不同於說模型中你不能/不會改變任何東西。我經常將匿名類型傳遞給'Json',我在這裏看到的唯一問題是,如果您有多個方法返回JSON'ed'Machine',則需要記住每次「過濾」它。我眼中的巧克力與香草 - 它們都是不錯的選擇。 – Aaronaught 2010-01-05 02:25:01

+0

@Aaron瞭解。不確定我不能/不會確定它*感覺*是否正確地將引用添加到核心/域項目,以便爲特定的UI情況提供此解決方案。我喜歡使用NonSerializedAttribute作爲System名稱空間中的解決方案。這似乎是JavaScriptSerializer忽略它的一個缺點。 – ahsteele 2010-01-05 02:48:23

1

因爲,據我所知,你不能序列化對象的引用,但只複製你可以嘗試使用了一下骯髒的黑客工具,是這樣的:

當你反序列化JSON的代碼,那麼你可以運行在它上面的一個簡單的函數,這些轉變的ID爲正確引用
  1. 客戶應序列的機牀參考作爲機器的ID
+0

當然,但不會將機器信息傳遞到查詢頁面,並且需要額外的查詢才能獲得機器屬性。 – ahsteele 2010-01-04 23:36:11

0

您需要決定這是「根」對象。假設機器是根,那麼客戶就是機器的一個子對象。當您連載機器時,它會將客戶作爲JSON中的子對象序列化,並且當客戶序列化時,它會將其反向引用到機器上。當你的代碼deserialises機器,它會deserialise機器的客戶子對象以及恢復從客戶機背參考。

大多數序列化庫提供某種鉤子來修改如何爲每個類執行反序列化。你需要使用掛鉤來修改deserialisation爲機器類恢復機器的客戶反向引用。究竟是什麼鉤子取決於你正在使用的JSON庫。

4

用於有同樣的問題。我創建了一個簡單的擴展方法,將L2E對象「扁平化」爲一個IDictionary。一個IDictionary由JavaScriptSerializer正確序列化。生成的Json與直接序列化對象相同。

由於我限制了序列化的級別,所以避免了循環引用。它也不包括1-> n鏈接表(Entitysets)。

private static IDictionary<string, object> JsonFlatten(object data, int maxLevel, int currLevel) { 
     var result = new Dictionary<string, object>(); 
     var myType = data.GetType(); 
     var myAssembly = myType.Assembly; 
     var props = myType.GetProperties(); 
     foreach (var prop in props) { 
      // Remove EntityKey etc. 
      if (prop.Name.StartsWith("Entity")) { 
       continue; 
      } 
      if (prop.Name.EndsWith("Reference")) { 
       continue; 
      } 
      // Do not include lookups to linked tables 
      Type typeOfProp = prop.PropertyType; 
      if (typeOfProp.Name.StartsWith("EntityCollection")) { 
       continue; 
      } 
      // If the type is from my assembly == custom type 
      // include it, but flattened 
      if (typeOfProp.Assembly == myAssembly) { 
       if (currLevel < maxLevel) { 
        result.Add(prop.Name, JsonFlatten(prop.GetValue(data, null), maxLevel, currLevel + 1)); 
       } 
      } else { 
       result.Add(prop.Name, prop.GetValue(data, null)); 
      } 
     } 

     return result; 
    } 
    public static IDictionary<string, object> JsonFlatten(this Controller controller, object data, int maxLevel = 2) { 
     return JsonFlatten(data, maxLevel, 1); 
    } 

我的行動方法是這樣的:

public JsonResult AsJson(int id) { 
     var data = Find(id); 
     var result = this.JsonFlatten(data); 
     return Json(result, JsonRequestBehavior.AllowGet); 
    } 
31

儘管它的年齡,我回答這個問題,因爲它是第三結果(目前)從谷歌「json.encode循環引用」,雖然我不同意上面的答案(完全),因爲使用ScriptIgnoreAttribute假定你不會在代碼中的任何地方想要遍歷另一個方向上的某個JSON關係。因爲一個用例,我不相信鎖定你的模型。

它的確激勵我使用這個簡單的解決方案。

由於您在MVC中的視圖中工作,因此您擁有該模型,並且您只需將該模型分配給控制器中的ViewData.Model,繼續在視圖中使用LINQ查詢來壓扁數據很好消除對特定的JSON違規循環引用你想是這樣的:

var jsonMachines = from m in machineForm 
        select new { m.X, m.Y, // other Machine properties you desire 
           Customer = new { m.Customer.Id, m.Customer.Name, // other Customer properties you desire 
           }}; 
return Json(jsonMachines); 

或者在機牀 - >客戶關係是1 .. * - > *然後嘗試:

var jsonMachines = from m in machineForm 
        select new { m.X, m.Y, // other machine properties you desire 
           Customers = new List<Customer>(
               (from c in m.Customers 
               select new Customer() 
               { 
                Id = c.Id, 
                Name = c.Name, 
                // Other Customer properties you desire 
               }).Cast<Customer>()) 
           }; 
return Json(jsonMachines); 
2

Entity Framework version 4,有一個選項可用:ObjectContextOp tions.LazyLoadingEnabled

將其設置爲false應避免「循環引用」問題。但是,您將不得不顯式加載要包含的導航屬性。

見:http://msdn.microsoft.com/en-us/library/bb896272.aspx

+0

您也可能在某些情況下也必須禁用代理生成功能。 – 2011-05-12 02:34:00

+0

在下面添加了一個示例:http://stackoverflow.com/a/17068227/605586 – Thomas 2013-06-12 14:34:31

9

基於通心絡的回答,你必須 禁用延遲加載和代理的創建,你可以使用正常的方法來獲取數據。

例子:

//Retrieve Items with Json: 
public JsonResult Search(string id = "") 
{ 
    db.Configuration.LazyLoadingEnabled = false; 
    db.Configuration.ProxyCreationEnabled = false; 

    var res = db.Table.Where(a => a.Name.Contains(id)).Take(8); 

    return Json(res, JsonRequestBehavior.AllowGet); 
} 
+0

謝謝 - 這是我最喜歡的解決方案,因爲它可以防止不必要的框架計算,而不是使用更多的代碼「退出」到子集中不必要的工作執行後的數據。 – Stephen 2015-05-28 19:33:48

+0

HFS花了我3天才找到這個,但非常感謝你張貼這個解決方案! – user3320597 2016-05-31 14:38:33

0

我有同樣的問題,這個星期爲好,因爲我需要實現一個接口要求一個List<MyType>不能使用匿名類型。在製作一個圖表顯示所有關係與導航能力之後,我發現MyTypeMyObject之間存在雙向關係,這導致了此循環引用,因爲它們都互相保存。

在確定MyObject確實不需要知道MyType,並由此使其成爲單向關係之後,此問題得以解決。

0

我所做的是有點激進,但我不需要屬性,這使得令人討厭的循環引用引起的錯誤,所以我已經在序列化之前將它設置爲null。

SessionTickets result = GetTicketsSession(); 
foreach(var r in result.Tickets) 
{ 
    r.TicketTypes = null; //those two were creating the problem 
    r.SelectedTicketType = null; 
} 
return Json(result); 

如果你真的需要你的屬性,你可以創建一個不成立循環引用視圖模型,但也許保持重要元素的一些標識,那你以後可以用於恢復原始值。