关于asp.net mvc:Json和循环引用异常

Json and Circular Reference Exception

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

举例说明

1
Machine => Customer => Machine

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

编辑

目前我正在使用控制器基类提供的JSON方法。因此,我正在进行的序列化基本如下:

1
Json(machineForm);


更新:

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

相反,在System.Web.Script.Serialization中使用ScriptIgnoreAttribute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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方法使用)将忽略它。


我之所以回答这个问题,是因为它是谷歌(Google)针对"json.encode循环引用"的第三个结果(目前),尽管我不完全同意上面的答案(完全同意),因为使用scriptIgnoreAttribute会假定您的代码中没有任何地方想要在其他方向上遍历某些json的关系。我不相信因为一个用例而锁定你的模型。

这确实激励了我使用这个简单的解决方案。

因为您在MVC中的视图中工作,所以您有了模型,只需将模型分配给view data.model在控制器中,继续使用视图中的LINQ查询来很好地平展数据,删除您想要的特定JSON的有问题的循环引用,如下所示:

1
2
3
4
5
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….->*则尝试:

1
2
3
4
5
6
7
8
9
10
11
12
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);


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

例子:

1
2
3
4
5
6
7
8
9
10
//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);
}


用于有相同的问题。我创建了一个简单的扩展方法,它将L2e对象"展平"为IDictionary。IDictionary由JavaScriptSerializer正确序列化。生成的JSON与直接序列化对象的JSON相同。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    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);
    }

我的操作方法如下:

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

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

将其设置为false应避免出现"循环引用"问题。但是,必须显式加载要包含的导航属性。

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


据我所知,您不能序列化对象引用,但只能复制您可以尝试使用一个类似这样的脏黑客:

  • 客户应将其计算机引用序列化为计算机的ID
  • 当反序列化JSON代码时,可以在其上运行一个简单的函数,将这些ID转换为适当的引用。

  • 我所做的有点激进,但我不需要这个属性,因为它使讨厌的循环引用产生错误,所以我在序列化之前将其设置为空。

    1
    2
    3
    4
    5
    6
    7
    SessionTickets result = GetTicketsSession();
    foreach(var r in result.Tickets)
    {
        r.TicketTypes = null; //those two were creating the problem
        r.SelectedTicketType = null;
    }
    return Json(result);

    如果您确实需要您的属性,您可以创建一个不包含循环引用的ViewModel,但可能保留一些重要元素的ID,稍后您可以使用这些ID恢复原始值。


    这周我也遇到了同样的问题,不能使用匿名类型,因为我需要实现一个要求List的接口。在做了一个显示所有与适航性关系的图表之后,我发现MyTypeMyObject之间存在双向关系,这导致了这个循环引用,因为它们都互相保存了。

    在确定MyObject不需要真正了解MyType从而使其成为单向关系后,这个问题得到了解决。


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

    大多数序列化库都提供了某种钩子来修改如何对每个类执行反序列化。您需要使用该钩子来修改machine类的反序列化,以恢复机器客户中的backreference。钩子的具体内容取决于您使用的JSON库。