При сериализации объекта типа SubSonic.Schema .DatabaseColumn была обнаружена циклическая ссылка.

170

Я пытаюсь сделать простой возврат JSON, но у меня возникли проблемы, у меня есть следующие ниже.

public JsonResult GetEventData()
{
    var data = Event.Find(x => x.ID != 0);
    return Json(data);
}

Я получаю HTTP 500 с исключением, как показано в заголовке этого вопроса. Я тоже пробовал

var data = Event.All().ToList()

Это дало ту же проблему.

Это ошибка или моя реализация?

Джон
источник
1
Посмотри на это. Существует решение с использованием ScriptIgnoreатрибута. stackoverflow.com/questions/1193857/subsonic-3-0-0-2-structs-tt
freddoo
Это было лучшее решение для меня; У меня был Game> Tournament> Game> Tournament> Game и т. Д. Я поместил ScriptIgnoreатрибут в свойство Tournament.Game, и он работал нормально :)
eth0
Если кто-то хочет «автоматизированное» (не передовое) решение для этой проблемы, которое не требует дополнительного кода, проверьте этот QA: не сериализуйте ссылки на класс Entity Framework в JSON (библиотека
ServiceStack.Text

Ответы:

175

Кажется, в вашей иерархии объектов есть циклические ссылки, которые не поддерживаются сериализатором JSON. Вам нужны все столбцы? Вы можете выбрать только те свойства, которые вам нужны в представлении:

return Json(new 
{  
    PropertyINeed1 = data.PropertyINeed1,
    PropertyINeed2 = data.PropertyINeed2
});

Это сделает ваш объект JSON легче и легче для понимания. Если у вас много свойств, AutoMapper можно использовать для автоматического сопоставления объектов DTO и объектов View.

Дарин димитров
источник
Я думаю, что, возможно, выбор тех, которые я хочу, может работать, я думаю, что круговая ссылка такова, потому что в Event у меня есть IQueryable <Category>, который в свою очередь будет иметь IQueryable <Event>
Jon
7
Automapper не гарантирует, что вы не получите эту проблему. Я пришел сюда в поисках ответа, и я на самом деле использую Autopper.
Капитан Кенпачи
1
Посмотрите ответ от @ClayKaboom, поскольку он объясняет, почему он может быть круглым
PandaWood
106

У меня была та же проблема, и я решил using Newtonsoft.Json;

var list = JsonConvert.SerializeObject(model,
    Formatting.None,
    new JsonSerializerSettings() {
        ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
});

return Content(list, "application/json");
ddfnfal
источник
3
Этот встроенный код работал нормально для меня. То же самое в глобальной конфигурации, как упомянуто kravits88, не работает для меня. ТАКЖЕ, подпись метода должна быть обновлена, чтобы вернуть ContentResult для этого кода.
BiLaL
6
Это должно быть помечено как лучший ответ, поскольку оно охватывает случаи, когда вы не можете тратить часы на преобразование ваших объектов в другие представления, как в ответе, помеченном как принятый.
Ренан
56

На самом деле это происходит потому, что сложные объекты - это то, что приводит к сбою получающегося объекта json. И это терпит неудачу, потому что, когда объект отображается, он отображает детей, который отображает их родителей, делая круговую ссылку для возникновения. Json потребовалось бы бесконечное время для его сериализации, поэтому это исключает проблему за исключением.

Сопоставление Entity Framework также производит то же поведение, и решение состоит в том, чтобы отбросить все нежелательные свойства.

Просто объясняя окончательный ответ, весь код будет:

public JsonResult getJson()
{
    DataContext db = new DataContext ();

    return this.Json(
           new {
                Result = (from obj in db.Things select new {Id = obj.Id, Name = obj.Name})
               }
           , JsonRequestBehavior.AllowGet
           );
}

Это также может быть следующим, если вы не хотите, чтобы объекты внутри Resultсвойства:

public JsonResult getJson()
{
    DataContext db = new DataContext ();

    return this.Json(
           (from obj in db.Things select new {Id = obj.Id, Name = obj.Name})
           , JsonRequestBehavior.AllowGet
           );
}
ClayKaboom
источник
1
+1 за ясные и простые для понимания вещи, спасибо @Clay. Мне нравится ваше объяснение концепции ошибки.
Ajay2707
14

Подводя итог, есть 4 решения для этого:

Решение 1: отключите ProxyCreation для DBContext и восстановите его в конце.

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    {
        bool proxyCreation = db.Configuration.ProxyCreationEnabled;
        try
        {
            //set ProxyCreation to false
            db.Configuration.ProxyCreationEnabled = false;

            var data = db.Products.ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
        finally
        {
            //restore ProxyCreation to its original state
            db.Configuration.ProxyCreationEnabled = proxyCreation;
        }
    }

Решение 2. Использование JsonConvert путем установки ReferenceLoopHandling для игнорирования настроек сериализатора.

    //using using Newtonsoft.Json;

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    {
        try
        {
            var data = db.Products.ToList();

            JsonSerializerSettings jss = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
            var result = JsonConvert.SerializeObject(data, Formatting.Indented, jss);

            return Json(result, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
    }

Следующие два решения одинаковы, но лучше использовать модель, потому что она строго типизирована.

Решение 3: вернуть модель, которая включает только необходимые свойства.

    private DBEntities db = new DBEntities();//dbcontext

    public class ProductModel
    {
        public int Product_ID { get; set;}

        public string Product_Name { get; set;}

        public double Product_Price { get; set;}
    }

    public ActionResult Index()
    {
        try
        {
            var data = db.Products.Select(p => new ProductModel
                                                {
                                                    Product_ID = p.Product_ID,
                                                    Product_Name = p.Product_Name,
                                                    Product_Price = p.Product_Price
                                                }).ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
    }

Решение 4: вернуть новый динамический объект, который включает только необходимые свойства.

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    {
        try
        {
            var data = db.Products.Select(p => new
                                                {
                                                    Product_ID = p.Product_ID,
                                                    Product_Name = p.Product_Name,
                                                    Product_Price = p.Product_Price
                                                }).ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
    }
Amro
источник
7

JSON, как и XML и другие форматы, является форматом сериализации на основе дерева. Он не будет любить вас, если у вас есть круговые ссылки в ваших объектах, так как «дерево» будет:

root B => child A => parent B => child A => parent B => ...

Часто есть способы отключить навигацию по определенному пути; Например, XmlSerializerвы можете пометить родительское свойство как XmlIgnore. Я не знаю, возможно ли это с рассматриваемым сериализатором json, и DatabaseColumnесть ли у него подходящие маркеры ( очень маловероятно, поскольку он должен ссылаться на каждый API сериализации)

Марк Гравелл
источник
4

Это из-за нового шаблона DbContext T4, который используется для генерации сущностей EntityFramework. Чтобы иметь возможность выполнять отслеживание изменений, в этих шаблонах используется шаблон Proxy, заключая в них ваши красивые POCO. Это тогда вызывает проблемы при сериализации с JavaScriptSerializer.

Итак, 2 решения:

  1. Либо вы просто сериализуете и возвращаете нужные вам свойства на клиенте
  2. Вы можете отключить автоматическую генерацию прокси, установив его в настройках контекста.

    context.Configuration.ProxyCreationEnabled = false;

Очень хорошо объяснено в статье ниже.

http://juristr.com/blog/2011/08/javascriptserializer-circular-reference/

Nilesh
источник
4

Использование Newtonsoft.Json: в вашем методе Global.asax Application_Start добавьте эту строку:

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
kravits88
источник
1
Видимо выглядит очень прямолинейно, но у меня ничего не
вышло
4

добавить [JsonIgnore]к виртуальным свойствам в вашей модели.

MorenajeRD
источник
4

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

Unais.NI
источник
3

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

изучение

MVC's Controller.Jsonфункция выполняет свою работу, но в этом случае она очень слаба для предоставления соответствующей ошибки. При использовании Newtonsoft.Json.JsonConvert.SerializeObjectошибка точно указывает, какое именно свойство вызывает циклическую ссылку. Это особенно полезно при сериализации более сложных иерархий объектов.

Правильная архитектура

Никогда не следует пытаться сериализовать модели данных (например, модели EF), поскольку навигационные свойства ORM - это путь к гибели, когда дело доходит до сериализации. Поток данных должен быть следующим:

Database -> data models -> service models -> JSON string 

Сервисные модели могут быть получены из моделей данных с использованием автоматических картографов (например, Automapper ). Хотя это не гарантирует отсутствие циклических ссылок, надлежащий дизайн должен это делать: модели сервиса должны содержать именно то, что требуется потребителю сервиса (то есть свойства).

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

Современные приложения имеют тенденцию избегать загрузки сложных структур данных сразу, а сервисные модели должны быть тонкими. Например:

  1. получить доступ к событию - загружены только данные заголовка (идентификатор, имя, дата и т. д.) -> модель сервиса (JSON), содержащая только данные заголовка
  2. список управляемых участников - доступ к всплывающему окну и отложенная загрузка списка -> модель сервиса (JSON), содержащая только список участников
Алексей
источник
1

Я использую исправление, потому что использую Knockout в представлениях MVC5.

На действии

return Json(ModelHelper.GetJsonModel<Core_User>(viewModel));

функция

   public static TEntity GetJsonModel<TEntity>(TEntity Entity) where TEntity : class
    {
        TEntity Entity_ = Activator.CreateInstance(typeof(TEntity)) as TEntity;
        foreach (var item in Entity.GetType().GetProperties())
        {
            if (item.PropertyType.ToString().IndexOf("Generic.ICollection") == -1 && item.PropertyType.ToString().IndexOf("SaymenCore.DAL.") == -1)
                item.SetValue(Entity_, Entity.GetPropValue(item.Name));
        }
        return Entity_;  
    }
A.Kosecik
источник
0

Вы можете заметить свойства, которые вызывают круговую ссылку. Тогда вы можете сделать что-то вроде:

private Object DeCircular(Object object)
{
   // Set properties that cause the circular reference to null

   return object
}
Bassel
источник
-1
//first: Create a class as your view model

public class EventViewModel 
{
 public int Id{get;set}
 public string Property1{get;set;}
 public string Property2{get;set;}
}
//then from your method
[HttpGet]
public async Task<ActionResult> GetEvent()
{
 var events = await db.Event.Find(x => x.ID != 0);
 List<EventViewModel> model = events.Select(event => new EventViewModel(){
 Id = event.Id,
 Property1 = event.Property1,
 Property1 = event.Property2
}).ToList();
 return Json(new{ data = model }, JsonRequestBehavior.AllowGet);
}
Ynnoboy
источник
Это не отвечает на вопрос
датчанин I
-1

Более простой альтернативой для решения этой проблемы является возврат строки и форматирование этой строки в json с помощью JavaScriptSerializer.

public string GetEntityInJson()
{
   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.Select(x => new { ID = x.ID, AnotherAttribute = x.AnotherAttribute });
   return j.Serialize(entityList );
}

Важно, чтобы «Выбрать» часть, которая выбирает свойства, которые вы хотите в вашем представлении. У некоторого объекта есть ссылка на родителя. Если вы не выберете атрибуты, может появиться циклическая ссылка, если вы просто возьмете таблицы в целом.

Не делай этого:

public string GetEntityInJson()
{
   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.toList();
   return j.Serialize(entityList );
}

Сделайте это вместо этого, если вы не хотите всю таблицу:

public string GetEntityInJson()
{
   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.Select(x => new { ID = x.ID, AnotherAttribute = x.AnotherAttribute });
   return j.Serialize(entityList );
}

Это помогает отображать представление с меньшим количеством данных, просто с необходимыми атрибутами, и ускоряет работу вашей сети.

Стерлинг Диас
источник