【问题标题】:Json and Circular Reference ExceptionJson 和循环引用异常
【发布时间】:2011-01-01 10:56:42
【问题描述】:

我有一个对另一个对象有循环引用的对象。鉴于这些对象之间的关系,这是正确的设计。

说明

Machine => Customer => Machine

正如预期的那样,当我尝试使用 Json 序列化机器或客户对象时遇到了问题。我不确定如何解决这个问题,因为我不想破坏 Machine 和 Customer 对象之间的关系。解决此问题的方法有哪些?

编辑

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

Json(machineForm);

【问题讨论】:

    标签: asp.net-mvc json serialization circular-reference


    【解决方案1】:

    因为,据我所知,您无法序列化对象引用,而只能复制副本,您可以尝试使用一些像这样的肮脏技巧:

    1. 客户应将其机器引用序列化为机器的 ID
    2. 反序列化 json 代码后,您可以在其上运行一个简单的函数,将这些 id 转换为正确的引用。

    【讨论】:

    • 当然可以,但这不会将机器信息传递到查询页面,并且需要额外的查询才能获取机器属性。
    【解决方案2】:

    您需要确定哪个是“根”对象。说机器是根,那么客户就是机器的子对象。当您序列化机器时,它会将客户序列化为 JSON 中的子对象,而当序列化客户时,它将序列化它对机器的反向引用。当您的代码反序列化机器时,它将反序列化机器的客户子对象并恢复从客户到机器的反向引用。

    大多数序列化库都提供了某种钩子来修改对每个类执行反序列化的方式。您需要使用该挂钩来修改机器类的反序列化,以恢复机器客户的反向引用。究竟该挂钩是什么取决于您使用的 JSON 库。

    【讨论】:

      【解决方案3】:

      更新:

      不要尝试使用NonSerializedAttribute,因为JavaScriptSerializer 显然会忽略它。

      改为在System.Web.Script.Serialization 中使用ScriptIgnoreAttribute

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

      这样,当你把Machine扔进Json方法时,它会遍历MachineCustomer的关系,但不会尝试从Customer回到Machine

      这种关系仍然存在,您的代码可以随心所欲地处理,但JavaScriptSerializer(由Json 方法使用)将忽略它。

      【讨论】:

      • 我将不得不试一试,但不幸的是,这来自域/核心对象,我不确定我是否要向该项目引入对 System.Web.Script.Serialization 的引用.我将这些核心对象封装在一个模型中,我想我可以使用ScriptIgnore,但那时我可以删除循环引用。该解决方案似乎提供了与匿名类型 stackoverflow.com/questions/372955/… 类似的解决方案
      • 这也有效。您应该具体说明您的问题/cmets - 说您不能/不会删除循环引用与说您不能/不会更改模型中的任何内容是不同的。我经常将匿名类型传递给 Json,我在这里看到的唯一问题是,如果您有多个方法返回 JSON 格式的 Machine,您需要记住每次都“过滤”它。在我眼里,巧克力和香草是一种选择——它们都是不错的选择。
      • @Aaron 明白了。不确定我不能/不会只是不确定是否感觉添加对核心/域项目的引用只是为了为特定的 UI 情况提供此解决方案。我喜欢在 System 命名空间中使用 NonSerializedAttribute 作为其解决方案。 JavaScriptSerializer 忽略它似乎是一个缺点。
      • 我明白你来自哪里。如果这确实特定于单个 UI,则表明 ViewModel 将位于 UI 程序集中,因此“可以”具有序列化引用。另外请记住,在 MVC 对 JSON 的强大支持之前,我们通常使用 XML,并且在实体库中引用 System.Xml 很常见,只是为了声明 XmlElementXmlIgnore 和类似属性。但正如我所说,我理解你的理由,如果你要经常返回这些数据,那么 ViewModel 可能是要走的路(否则匿名类型很好)。
      • 关于利用 System.Web.Script.Serialization 中的 ScriptIgnoreAttribute 的最后说明。不幸的是,它位于 System.Web.Extensions.dll (stackoverflow.com/questions/1156313/…) 中,需要一些工作才能添加:forums.asp.net/p/1055356/1500247.aspx。除此之外,归结为选择删除循环引用或添加命名空间引用以忽略特定属性的序列化。
      【解决方案4】:

      使用会遇到同样的问题。我创建了一个简单的扩展方法,将 L2E 对象“扁平化”为一个 IDictionary。 IDictionary 由 JavaScriptSerializer 正确序列化。生成的 Json 和直接序列化对象是一样的。

      由于我限制了序列化级别,因此避免了循环引用。它也不包括 1->n 链接表(实体集)。

          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);
          }
      

      我的 Action 方法如下所示:

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

      【讨论】:

        【解决方案5】:

        我正在回答这个问题,尽管它已经很老了,因为它是谷歌“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);
        

        【讨论】:

          【解决方案6】:

          Entity Framework version 4中,有一个选项可用:ObjectContextOptions.LazyLoadingEnabled

          将其设置为 false 应该可以避免“循环引用”问题。但是,您必须显式加载要包含的导航属性。

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

          【讨论】:

          • 在某些情况下,您可能还必须禁用代理生成。
          【解决方案7】:

          根据 txl 的回答,您必须 禁用延迟加载和代理创建,您可以使用常规方法获取数据。

          例子:

          //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);
          }
          

          【讨论】:

          • 谢谢 - 这是我在这个问题中最喜欢的解决方案,因为它可以防止不必要的框架计算,而不是在不必要的工作执行后使用更多代码“退出”到数据子集。
          • HFS 我花了 3 天时间才找到这个,但非常感谢你发布这个解决方案!!!
          【解决方案8】:

          这周我也遇到了同样的问题,因为我需要实现一个要求List&lt;MyType&gt; 的接口,所以不能使用匿名类型。在制作了一个显示所有可导航关系的图表后,我发现MyTypeMyObject 存在双向关系,从而导致了这种循环引用,因为它们都相互保存了。

          在确定MyObject 并不真的需要知道MyType,从而使其成为单向关系后,这个问题就解决了。

          【讨论】:

            【解决方案9】:

            我所做的有点激进,但我不需要该属性,这会导致令人讨厌的循环引用错误,因此我在序列化之前将其设置为 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);
            

            如果你真的需要你的属性,你可以创建一个不包含循环引用的视图模型,但可能会保留一些重要元素的 Id,你可以在以后使用它来恢复原始值。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2023-03-05
              • 2016-04-26
              • 2016-03-28
              • 1970-01-01
              • 1970-01-01
              • 2011-06-04
              相关资源
              最近更新 更多