Wykryto cykliczne odwołanie podczas serializacji obiektu typu „SubSonic.Schema .DatabaseColumn”.

170

Próbuję wykonać prosty zwrot JSON, ale mam problemy, które mam poniżej.

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

Otrzymuję HTTP 500 z wyjątkiem pokazanym w tytule tego pytania. Ja też próbowałem

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

To spowodowało ten sam problem.

Czy to błąd czy moja implementacja?

Jon
źródło
1
Patrz na to. Istnieje rozwiązanie wykorzystujące ScriptIgnoreatrybut. stackoverflow.com/questions/1193857/subsonic-3-0-0-2-structs-tt
freddoo
To było dla mnie najlepsze rozwiązanie; Miałem Gra> Turniej> Gra> Turniej> Gra itp. Umieściłem ScriptIgnoreatrybut we właściwości Tournament.Game i działało dobrze :)
eth0
Jeśli ktoś chce „zautomatyzowanego” (nie
będącego

Odpowiedzi:

175

Wygląda na to, że w hierarchii obiektów istnieją odwołania cykliczne, które nie są obsługiwane przez serializator JSON. Czy potrzebujesz wszystkich kolumn? W widoku możesz wybrać tylko te właściwości, których potrzebujesz:

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

Dzięki temu Twój obiekt JSON będzie lżejszy i łatwiejszy do zrozumienia. Jeśli masz wiele właściwości, AutoMapper może służyć do automatycznego mapowania między obiektami DTO i obiektami widoku.

Darin Dimitrov
źródło
Myślę, że może wybranie tych, które chcę, może zadziałać.Myślę, że odwołanie cykliczne jest spowodowane tym, że w Event IQueryable <Category>, która z kolei będzie miała IQueryable <Event>
Jon
7
Automapper nie gwarantuje, że nie napotkasz tego problemu. Przyszedłem tutaj, szukając odpowiedzi, a właściwie używam automappera.
Kapitan Kenpachi
1
Zobacz odpowiedź z @ClayKaboom, ponieważ wyjaśnia, dlaczego może być okrągła
PandaWood
106

Miałem ten sam problem i rozwiązałem go using Newtonsoft.Json;

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

return Content(list, "application/json");
ddfnfal
źródło
3
Ten kod wbudowany działał dobrze dla mnie. Te same rzeczy w konfiguracji globalnej, o których wspomniał kravits88, nie działają dla mnie. TAKŻE sygnatura metody powinna zostać zaktualizowana, aby zwracała ContentResult dla tego kodu.
BiLaL
6
Należy to oznaczyć jako najlepszą odpowiedź, ponieważ obejmuje przypadki, w których nie można spędzać godzin na przekształcaniu obiektów w inne reprezentacje, jak w odpowiedzi oznaczonej jako zaakceptowana.
Renan,
56

Dzieje się tak, ponieważ złożone obiekty powodują niepowodzenie wynikowego obiektu JSON. I kończy się niepowodzeniem, ponieważ kiedy obiekt jest mapowany, mapuje dzieci, które mapują ich rodziców, tworząc cykliczne odniesienie. Json wymagałoby nieskończonego czasu, aby go serializować, więc zapobiega problemowi z wyjątkiem.

Mapowanie Entity Framework również powoduje to samo zachowanie, a rozwiązaniem jest odrzucenie wszystkich niechcianych właściwości.

Wyjaśniając tylko ostateczną odpowiedź, cały kod wyglądałby tak:

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

Może to być również następujące, jeśli nie chcesz obiektów wewnątrz Resultwłaściwości:

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
źródło
1
+1 za jasne i łatwe do zrozumienia rzeczy, dzięki @Clay. Podoba mi się twoje wyjaśnienie dotyczące pojęć stojących za błędem.
Ajay2707
14

Podsumowując, istnieją 4 rozwiązania tego problemu:

Rozwiązanie 1: wyłącz ProxyCreation dla DBContext i przywróć go na końcu.

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

Rozwiązanie 2: użycie JsonConvert przez ustawienie ReferenceLoopHandling do ignorowania ustawień serializatora.

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

Poniższe dwa rozwiązania są takie same, ale użycie modelu jest lepsze, ponieważ jest silnie wpisane.

Rozwiązanie 3: zwróć Model, który zawiera tylko potrzebne właściwości.

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

Rozwiązanie 4: zwróć nowy obiekt dynamiczny, który zawiera tylko potrzebne właściwości.

    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
źródło
7

JSON, podobnie jak XML i różne inne formaty, jest formatem serializacji opartym na drzewie. Nie będzie Cię kochać, jeśli w swoich obiektach masz odwołania cykliczne, tak jak wyglądałoby „drzewo”:

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

Często istnieją sposoby na wyłączenie nawigacji po określonej ścieżce; na przykład XmlSerializermożesz oznaczyć właściwość nadrzędną jako XmlIgnore. Nie wiem, czy jest to możliwe w przypadku danego serializatora json, ani czy DatabaseColumnma odpowiednie znaczniki ( bardzo mało prawdopodobne, ponieważ musiałoby odwoływać się do każdego interfejsu API serializacji)

Marc Gravell
źródło
4

Jest to spowodowane nowym szablonem DbContext T4, który jest używany do generowania jednostek EntityFramework. Aby móc wykonać śledzenie zmian, te szablony używają wzorca proxy, owijając nimi Twoje ładne POCO. Powoduje to problemy podczas serializacji z JavaScriptSerializer.

Zatem dwa rozwiązania to:

  1. Albo po prostu serializujesz i zwracasz potrzebne właściwości na kliencie
  2. Możesz wyłączyć automatyczne generowanie serwerów proxy, ustawiając je w konfiguracji kontekstu

    context.Configuration.ProxyCreationEnabled = false;

Bardzo dobrze wyjaśniono w poniższym artykule.

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

nilesh
źródło
4

Korzystanie z Newtonsoft.Json: W metodzie Global.asax Application_Start dodaj następujący wiersz:

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
kravits88
źródło
1
Najwyraźniej wygląda bardzo prosto, ale nie zadziałało dla mnie
BiLaL
4

dodaj [JsonIgnore]do wirtualnych właściwości w swoim modelu.

MorenajeRD
źródło
4

Unikaj bezpośredniego konwertowania obiektu tabeli. Jeśli relacje są ustawione między innymi tabelami, może to spowodować zgłoszenie tego błędu. Zamiast tego można utworzyć klasę modelu, przypisać wartości do obiektu klasy, a następnie serializować go.

Unais.NI
źródło
3

Udzielone odpowiedzi są dobre, ale myślę, że można je poprawić, dodając perspektywę „architektoniczną”.

Dochodzenie

MVC's Controller.Jsonfunkcja wykonuje swoją pracę, ale w tym przypadku jest bardzo słaba w dostarczaniu odpowiedniego błędu. Używając Newtonsoft.Json.JsonConvert.SerializeObject, błąd określa dokładnie, jaka właściwość wyzwala odwołanie cykliczne. Jest to szczególnie przydatne podczas serializacji bardziej złożonych hierarchii obiektów.

Właściwa architektura

Nigdy nie należy próbować serializować modeli danych (np. Modeli EF), ponieważ właściwości nawigacyjne ORM są drogą do zatracenia, jeśli chodzi o serializację. Przepływ danych powinien wyglądać następująco:

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

Modele usług można uzyskać z modeli danych przy użyciu automatycznych maperów (np. Automapper ). Chociaż nie gwarantuje to braku cyrkularnych odniesień, właściwy projekt powinien to zrobić: modele usług powinny zawierać dokładnie to, czego wymaga usługobiorca (tj. Właściwości).

W tych rzadkich przypadkach, gdy klient zażąda hierarchii obejmującej ten sam typ obiektu na różnych poziomach, usługa może utworzyć strukturę liniową z relacją rodzic -> dziecko (używając tylko identyfikatorów, a nie odwołań).

Nowoczesne aplikacje starają się unikać jednoczesnego ładowania złożonych struktur danych, a modele usług powinny być wąskie. Na przykład:

  1. dostęp do zdarzenia - ładowane są tylko dane nagłówka (identyfikator, nazwa, data itp.) -> model usługi (JSON) zawierający tylko dane nagłówka
  2. lista zarządzanych uczestników - dostęp do wyskakującego okienka i leniwe ładowanie listy -> model usługi (JSON) zawierający tylko listę uczestników
Alexei
źródło
1

Używam poprawki, ponieważ używam Knockout w widokach MVC5.

W akcji

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

funkcjonować

   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
źródło
0

Możesz zauważyć właściwości, które powodują odwołanie cykliczne. Następnie możesz zrobić coś takiego:

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

   return object
}
Bassel
źródło
-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
źródło
To nie odpowiada na pytanie
Dane I
-1

Łatwiejszą alternatywą rozwiązania tego problemu jest zwrócenie ciągu znaków i sformatowanie go na json za pomocą 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 );
}

Ważna jest część „Wybierz”, która wybiera właściwości, które chcesz w widoku. Niektóre obiekty mają odniesienie do rodzica. Jeśli nie wybierzesz atrybutów, może pojawić się odwołanie cykliczne, jeśli po prostu weźmiesz tabele jako całość.

Nie rób tego:

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

Zrób to zamiast tego, jeśli nie chcesz całego stołu:

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

Pomaga to renderować widok z mniejszą ilością danych, tylko z potrzebnymi atrybutami i przyspiesza działanie sieci.

Sterling Diaz
źródło