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?


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.


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

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

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

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)


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/


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;

1
Najwyraźniej wygląda bardzo prosto, ale nie zadziałało dla mnie
BiLaL


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.


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

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

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
}

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

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.