Json 和循环引用异常

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2002940/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 17:23:21  来源:igfitidea点击:

Json and Circular Reference Exception

asp.net-mvcjsonserializationcircular-reference

提问by ahsteele

I have an object which has a circular reference to another object. Given the relationship between these objects this is the right design.

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

To Illustrate

为了显示

Machine => Customer => Machine

As is expected I run into an issue when I try to use Json to serialize a machine or customer object. What I am unsure of is how to resolve this issue as I don't want to break the relationship between the Machine and Customer objects. What are the options for resolving this issue?

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

Edit

编辑

Presently I am using Json method provided by the Controller base class. So the serialization I am doing is as basic as:

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

Json(machineForm);

回答by Aaronaught

Update:

更新:

Do not try to use NonSerializedAttribute, as the JavaScriptSerializerapparently ignores it.

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

Instead, use the ScriptIgnoreAttributein System.Web.Script.Serialization.

相反,使用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
    // ...
}

This way, when you toss a Machineinto the Jsonmethod, it will traverse the relationship from Machineto Customerbut will not try to go back from Customerto Machine.

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

The relationship is still there for your code to do as it pleases with, but the JavaScriptSerializer(used by the Jsonmethod) will ignore it.

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

回答by eudaimos

I'm answering this despite its age because it is the 3rd result (currently) from Google for "json.encode circular reference" and although I don't agree with the answers (completely) above, in that using the ScriptIgnoreAttribute assumes that you won't anywhere in your code want to traverse the relationship in the other direction for some JSON. I don't believe in locking down your model because of one use case.

尽管它已经很老了,我还是会回答这个问题,因为它是 Google 为“json.encode 循环引用”提供的第三个结果(目前),尽管我不同意上面的答案(完全),因为使用 ScriptIgnoreAttribute 假设您您的代码中不会有任何地方想要遍历某些 JSON 的另一个方向的关系。我不相信因为一个用例而锁定您的模型。

It did inspire me to use this simple solution.

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

Since you're working in a View in MVC, you have the Model and you want to simply assign the Model to the ViewData.Model within your controller, go ahead and use a LINQ query within your View to flatten the data nicely removing the offending circular reference for the particular JSON you want like this:

由于您在 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);

Or if the Machine -> Customer relationship is 1..* -> * then try:

或者,如果 Machine -> Customer 关系是 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);

回答by Thomas

Based on txl's answer you have to disable lazy loading and proxy creation and you can use the normal methods to get your data.

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

Example:

例子:

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

回答by GvS

Use to have the same problem. I have created a simple extension method, that "flattens" L2E objects into an IDictionary. An IDictionary is serialized correctly by the JavaScriptSerializer. The resulting Json is the same as directly serializing the object.

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

Since I limit the level of serialization, circular references are avoided. It also will not include 1->n linked tables (Entitysets).

由于我限制了序列化级别,因此避免了循环引用。它也不包括 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);
    }

My Action method looks like this:

我的 Action 方法如下所示:

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

回答by txl

In the Entity Framework version 4, there is an option available: ObjectContextOptions.LazyLoadingEnabled

实体框架版本 4 中,有一个可用选项:ObjectContextOptions.LazyLoadingEnabled

Setting it to false should avoid the 'circular reference' issue. However, you will have to explicitly load the navigation properties that you want to include.

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

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

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

回答by Swizec Teller

Since, to my knowledge, you cannot serialize object references, but only copies you could try employing a bit of a dirty hack that goes something like this:

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

  1. Customer should serialize its Machine reference as the machine's id
  2. When you deserialize the json code you can then run a simple function on top of it that transforms those id's into proper references.
  1. 客户应将其机器引用序列化为机器的 ID
  2. 当您反序列化 json 代码时,您可以在其上运行一个简单的函数,将这些 id 转换为正确的引用。

回答by Nat

You need to decide which is the "root" object. Say the machine is the root, then the customer is a sub-object of machine. When you serialise machine, it will serialise the customer as a sub-object in the JSON, and when the customer is serialised, it will NOTserialise it's back-reference to the machine. When your code deserialises the machine, it will deserialise the machine's customer sub-object and reinstate the back-reference from the customer to the machine.

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

Most serialisation libraries provide some kind of hook to modify how deserialisation is performed for each class. You'd need to use that hook to modify deserialisation for the machine class to reinstate the backreference in the machine's customer. Exactly what that hook is depends on the JSON library you are using.

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

回答by Br2

I've had the same problem this week as well, and could not use anonymous types because I needed to implement an interface asking for a List<MyType>. After making a diagram showing all relationships with navigability, I found out that MyTypehad a bidirectional relationship with MyObjectwhich caused this circular reference, since they both saved each other.

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

After deciding that MyObjectdid not really need to know MyType, and thereby making it a unidirectional relationship this problem was solved.

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

回答by DiSaSteR

What I have done is a bit radical, but I don't need the property, which makes the nasty circular-reference-causing error, so I have set it to null before serializing.

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

If you really need your properties, you can create a viewmodel which does not hold circular references, but maybe keeps some Id of the important element, that you could use later for restoring the original value.

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