Исключение Json и Circular Reference Exception

У меня есть объект, который имеет круговую ссылку на другой объект. Учитывая взаимосвязь между этими объектами, это правильный дизайн.

Иллюстрировать

Machine => Customer => Machine

Как и ожидалось, я столкнулся с проблемой, когда пытаюсь использовать Json для сериализации объекта машины или клиента. Я не уверен в том, как решить эту проблему, поскольку я не хочу разорвать связь между объектами Machine и Customer. Каковы варианты решения этой проблемы?

Edit

В настоящее время я использую метод Json, предоставляемый базовым классом Controller. Таким образом, сериализация, которую я делаю, является базовой:

Json(machineForm);

Ответ 1

Update:

Не пытайтесь использовать NonSerializedAttribute, поскольку JavaScriptSerializer, по-видимому, игнорирует его.

Вместо этого используйте ScriptIgnoreAttribute в 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
    // ...
}

Таким образом, когда вы подбрасываете Machine в метод Json, он будет пересекать отношение от Machine до Customer, но не будет пытаться вернуться с Customer до Machine.

Отношения все еще существуют для того, чтобы ваш код выполнял то, что ему нравится, но JavaScriptSerializer (используемый методом Json) будет игнорировать его.

Ответ 2

Я отвечаю на это, несмотря на его возраст, потому что это третий результат (в настоящее время) от Google для "справочной ссылки 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);

Или, если соединение 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);

Ответ 3

Основываясь на ответе 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);
}

Ответ 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);
    }

Ответ 5

В Entity Framework версии 4 существует опция: ObjectContextOptions.LazyLoadingEnabled

Устанавливая значение false, следует избегать проблемы с "круговой ссылкой". Однако вам придется явно загружать свойства навигации, которые вы хотите включить.

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

Ответ 6

Поскольку, насколько мне известно, вы не можете сериализовать ссылки на объекты, но только копии, которые вы могли бы попробовать, используя немного грязного взлома, который выглядит примерно так:

  • Клиент должен сериализовать ссылку на Машину как идентификатор машины
  • При десериализации json-кода вы можете запустить простую функцию поверх нее, которая преобразует эти id в правильные ссылки.

Ответ 7

Вам нужно решить, какой именно "корневой" объект. Скажем, что машина является корнем, тогда клиент является под-объектом машины. Когда вы сериализуете машину, она будет сериализовать клиента как под-объект в JSON, а когда клиент будет сериализован, он НЕ серизует его обратную ссылку на машину. Когда ваш код десериализует машину, он будет десериализовать под-объект клиента машины и восстановить обратную ссылку от клиента на машину.

Большинство библиотек сериализации предоставляют некоторый крючок для изменения способа десериализации для каждого класса. Вам нужно будет использовать этот хук для изменения десериализации для класса машины, чтобы восстановить обратную ссылку в клиенте машины. Точно, что этот крючок зависит от используемой вами библиотеки JSON.

Ответ 8

У меня была та же проблема на этой неделе, и я не мог использовать анонимные типы, потому что мне нужно было реализовать интерфейс с запросом List<MyType>. После создания диаграммы, показывающей все отношения с навигацией, я обнаружил, что MyType имел двунаправленную связь с MyObject, которая вызвала эту круговую ссылку, поскольку они оба сохраняли друг друга.

После принятия решения о том, что 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);

Если вам действительно нужны ваши свойства, вы можете создать модель просмотра, которая не содержит циклических ссылок, но может содержать некоторый идентификатор важного элемента, который вы можете использовать позже для восстановления исходного значения.