Как обойти проблему Циркулярной ссылки с JSON и Entity

13

Я экспериментировал с созданием веб-сайта, который использует MVC с JSON для моего уровня представления и Entity Framework для модели данных / базы данных. Моя проблема вступает в игру с сериализацией моих объектов Model в JSON.

Я использую код первый метод для создания моей базы данных. При выполнении первого метода кода отношение один ко многим (родитель / потомок) требует, чтобы у потомка была ссылка на родителя. (Пример кода может быть опечаткой, но вы получите картину)

class parent
{
   public List<child> Children{get;set;}
   public int Id{get;set;}

}
class child
{
    public int ParentId{get;set;}
    [ForeignKey("ParentId")]
    public parent MyParent{get;set;}
    public string name{get;set;}
 }

При возврате «родительского» объекта через JsonResult выдается ошибка циклической ссылки, поскольку у «потомка» есть свойство класса parent.

Я пробовал атрибут ScriptIgnore, но я теряю способность смотреть на дочерние объекты. Мне нужно будет отображать информацию в родительском дочернем виде в какой-то момент.

Я попытался сделать базовые классы для родителей и детей, которые не имеют циклическую ссылку. К сожалению, когда я пытаюсь отправить baseParent и baseChild, они читаются JSON Parser как их производные классы (я уверен, что эта концепция ускользает от меня).

Base.baseParent basep = (Base.baseParent)parent;
return Json(basep, JsonRequestBehavior.AllowGet);

Единственное решение, которое я придумала, - это создание моделей «View». Я создаю простые версии моделей базы данных, которые не включают ссылку на родительский класс. Каждая из этих моделей представления имеет метод для возврата версии базы данных и конструктор, который принимает модель базы данных в качестве параметра (viewmodel.name = databasemodel.name). Этот метод кажется принудительным, хотя он работает.

ПРИМЕЧАНИЕ: я публикую здесь, потому что думаю, что это более достойное обсуждение. Я мог бы использовать другой шаблон проектирования, чтобы преодолеть эту проблему, или это могло быть так же просто, как использование другого атрибута в моей модели. В своих поисках я не нашел хорошего способа преодолеть эту проблему.

Моей конечной целью было бы иметь красивое приложение MVC, которое активно использует JSON для связи с сервером и отображения данных. Поддерживая постоянную модель в разных слоях (или настолько, насколько я могу придумать).

DanScan
источник

Ответы:

6

Я вижу две разные темы в вашем вопросе:

  • Как управлять циклическими ссылками при сериализации в JSON?
  • Насколько безопасно использовать EF-сущности в качестве модельных сущностей в ваших представлениях?

Что касается циклических ссылок, мне жаль говорить, что простого решения не существует. Во-первых, потому что JSON нельзя использовать для представления циклических ссылок, следующий код:

var aParent = {Children : []}, aChild  = {Parent : aParent};
aParent.Children.push(aChild);
JSON.stringify(aParent);

Результаты в: TypeError: Converting circular structure to JSON

Единственный выбор, который у вас есть, - оставить только составную -> составную часть композиции и отбросить компонент «обратная навигация» -> составной, например, в вашем примере:

class parent
{
    public List<child> Children{get;set;}
    public int Id{get;set;}
}
class child
{
    public int ParentId{get;set;}
    [ForeignKey("ParentId"), ScriptIgnore]
    public parent MyParent{get;set;}
    public string name{get;set;}
}

Ничто не мешает вам перекомпоновать это свойство навигации на стороне клиента, используя jQuery:

$.each(parent.Children, function(i, child) {
  child.Parent = parent;  
})

Но затем вам нужно будет снова сбросить его перед отправкой обратно на сервер, поскольку JSON.stringify не сможет сериализовать циклическую ссылку:

$.each(parent.Children, function(i, child) {
  delete child.Parent;  
})

Теперь существует проблема использования EF-сущностей в качестве сущностей модели представления.

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

Более того, использование EF-сущностей в пользовательском интерфейсе может оказаться под угрозой, поскольку все связыватели по умолчанию будут сопоставлять каждое поле из запроса с полями сущностей, включая те, которые вы не хотели устанавливать пользователем.

Таким образом, если вы хотите, чтобы ваше MVC-приложение было должным образом спроектировано, я бы порекомендовал использовать выделенную модель представления, чтобы предотвратить раскрытие клиенту «кишок» вашей внутренней бизнес-модели, поэтому я бы порекомендовал вам конкретную модель представления.

Жюльен Ч.
источник
Есть ли причудливый способ с объектно-ориентированными методами, я могу обойти и круговую ссылку и проблему EF.
DanScan
Есть ли причудливый способ с объектно-ориентированными методами, который я могу обойти и круговую ссылку и проблему EF? Как и BaseObject, наследуется entityObject и viewObject. Таким образом, entityObject будет иметь циклическую ссылку, а viewObject не будет иметь циклическую ссылку. Я обошел это путем создания viewObject из entityObject (viewObject.name = entityObject.name), но это кажется пустой тратой времени. Как я могу обойти эту проблему?
DanScan
Они вам очень понравились. Ваше объяснение было очень ясным и легким для понимания.
Ник
2

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

Для этого вы можете установить DataContractSerializer и установить для свойства DataContractSerializer.PreserveObjectReferences значение false в конструкторе класса вашей модели данных. Это указывает на то, что ссылки на объекты не должны сохраняться при сериализации ответов HTTP.

Примеры:

Формат Json:

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = 
    Newtonsoft.Json.PreserveReferencesHandling.None;

Формат XML:

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Employee), null, int.MaxValue, 
    false, /* preserveObjectReferences: */ false, null);
xml.SetSerializer<Employee>(dcs);

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

Смотрите также класс DataContractsSerializer .

Кьяран Галлахер
источник
1

Сериализатор JSON, работающий с циркулярными ссылками

Вот пример пользовательского Джексона, JSONSerializerкоторый имеет дело с циклическими ссылками путем сериализации первого вхождения и сохранения * referenceв первом вхождении во всех последующих вхождениях.

Работа с круговыми ссылками при сериализации объектов с Джексоном

Соответствующий частичный фрагмент из вышеуказанной статьи:

private final Set<ObjectName> seen;

/**
 * Serialize an ObjectName with all its attributes or only its String representation if it is a circular reference.
 * @param on ObjectName to serialize
 * @param jgen JsonGenerator to build the output
 * @param provider SerializerProvider
 * @throws IOException
 * @throws JsonProcessingException
 */
@Override
public void serialize(@Nonnull final ObjectName on, @Nonnull final JsonGenerator jgen, @Nonnull final SerializerProvider provider) throws IOException, JsonProcessingException
{
    if (this.seen.contains(on))
    {
        jgen.writeString(on.toString());
    }
    else
    {
        this.seen.add(on);
        jgen.writeStartObject();
        final List<MBeanAttributeInfo> ais = this.getAttributeInfos(on);
        for (final MBeanAttributeInfo ai : ais)
        {
            final Object attribute = this.getAttribute(on, ai.getName());
            jgen.writeObjectField(ai.getName(), attribute);
        }
        jgen.writeEndObject();
    }
}

источник
0

Единственное решение, которое я придумала, - это создание моделей «View». Я создаю простые версии моделей базы данных, которые не включают ссылку на родительский класс. Каждая из этих моделей представления имеет метод для возврата версии базы данных и конструктор, который принимает модель базы данных в качестве параметра (viewmodel.name = databasemodel.name). Этот метод кажется принудительным, хотя он работает.

Отправка минимального количества данных является единственным правильным ответом. Когда вы отправляете данные из базы данных, обычно не имеет смысла отправлять каждый столбец со всеми связями. Потребителям не нужно иметь дело с ассоциациями и структурами баз данных, то есть для баз данных. Это не только сэкономит пропускную способность, но и облегчит обслуживание, чтение и использование. Запросите данные, а затем смоделируйте их для того, что вам действительно нужно отправить. голый минимум.

Dante
источник
Когда речь идет о больших данных, требуется больше времени на обработку, поскольку теперь вам нужно все преобразовать дважды.
Дэвид ван Дугтерен
-2

.Include(x => x.TableName ) не возвращая отношения (из основной таблицы в зависимую таблицу) или возвращая только одну строку данных, ИСПРАВИТЕ ЗДЕСЬ:

/programming/43127957/include-not-working-in-net-core-returns-one-parent

Кроме того, в Startup.cs убедитесь, что это вверху:

using Microsoft.EntityFrameworkCore; 
using Newtonsoft.Json; 
using Project_Name_Here.Models;
Джо Хоеллер
источник
сын ват? эм .. ват?
Амель