39

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

为了显示

Machine => Customer => Machine

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

编辑

目前我正在使用Controller 基类提供的 Json 方法。所以我正在做的序列化是基本的:

Json(machineForm);
4

9 回答 9

58

更新:

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

相反,使用ScriptIgnoreAttributein System.Web.Script.Serialization

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

    // Other members
    // ...
}

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

    // Other members
    // ...
}

这样,当你将 aMachine扔进Json方法时,它会遍历 from Machineto的关系,Customer但不会尝试从Customerto返回Machine

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

于 2010-01-04T23:55:15.093 回答
33

尽管它已经很老了,但我还是在回答这个问题,因为它是谷歌“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);
于 2011-04-13T20:14:42.840 回答
10

根据 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);
}
于 2013-06-12T14:34:06.217 回答
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);
    }
于 2010-09-08T09:54:04.637 回答
2

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

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

请参阅:http: //msdn.microsoft.com/en-us/library/bb896272.aspx

于 2011-05-05T21:24:27.690 回答
1

因为,据我所知,你不能序列化对象引用,而只能复制你可以尝试使用一些像这样的肮脏黑客:

  1. 客户应将其机器参考序列化为机器的 ID
  2. 当您反序列化 json 代码时,您可以在其上运行一个简单的函数,将这些 id 转换为正确的引用。
于 2010-01-04T23:09:06.600 回答
0

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

大多数序列化库都提供了某种钩子来修改如何为每个类执行反序列化。您需要使用该挂钩来修改机器类的反序列化,以恢复机器客户的反向引用。该钩子的确切含义取决于您使用的 JSON 库。

于 2010-01-04T23:15:37.063 回答
0

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

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

于 2015-08-25T17:29:06.060 回答
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);

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

于 2017-12-19T11:10:36.640 回答