Jak obejść problem z okólnikiem z JSON i Entity


13

Eksperymentowałem z tworzeniem strony internetowej, która wykorzystuje MVC z JSON dla mojej warstwy prezentacji i frameworku Entity dla modelu danych / bazy danych. Mój problem wchodzi w grę z serializowaniem obiektów Model do JSON.

Korzystam z pierwszej metody kodu, aby utworzyć bazę danych. Podczas wykonywania pierwszej metody kodu relacja jeden do wielu (rodzic / dziecko) wymaga, aby dziecko miało odwołanie do rodzica. (Przykładowy kod może być literówką, ale masz obraz)

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

Podczas zwracania obiektu „nadrzędnego” przez JsonResult generowany jest błąd odwołania cyklicznego, ponieważ „dziecko” ma właściwość nadrzędnej klasy.

Próbowałem atrybutu ScriptIgnore, ale tracę możliwość patrzenia na obiekty potomne. W pewnym momencie będę musiał wyświetlić informacje w widoku nadrzędnego dziecka.

Próbowałem utworzyć klasy podstawowe zarówno dla rodzica, jak i dziecka, które nie mają odwołania cyklicznego. Niestety, kiedy próbuję wysłać baseParent i baseChild, są one odczytywane przez JSON Parser jako ich pochodne klasy (jestem prawie pewien, że ta koncepcja mi ucieka).

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

Jedynym rozwiązaniem, które wymyśliłem, jest stworzenie modeli „Widok”. Tworzę proste wersje modeli baz danych, które nie zawierają odwołania do klasy nadrzędnej. Każdy z tych modeli widoków ma metodę zwracania wersji bazy danych i konstruktora, który przyjmuje model bazy danych jako parametr (viewmodel.name = databasemodel.name). Ta metoda wydaje się wymuszona, chociaż działa.

UWAGA: Piszę tutaj, ponieważ uważam, że jest to bardziej warte dyskusji. Mógłbym wykorzystać inny wzorzec projektowy, aby rozwiązać ten problem, lub może to być tak proste, jak użycie innego atrybutu w moim modelu. Podczas poszukiwań nie widziałem dobrej metody rozwiązania tego problemu.

Moim celem końcowym byłoby stworzenie ładnej aplikacji MVC, która mocno wykorzystuje JSON do komunikacji z serwerem i wyświetlania danych. Utrzymując spójny model na warstwach (lub najlepiej, jak mogę to wymyślić).

Odpowiedzi:


6

W twoim pytaniu widzę dwa różne tematy:

  • Jak zarządzać referencjami cyklicznymi podczas serializacji do JSON?
  • Jak bezpieczne jest używanie encji EF jako encji modelowych w twoich widokach?

Jeśli chodzi o okólniki, przykro mi powiedzieć, że nie ma prostego rozwiązania. Po pierwsze, ponieważ JSON nie może być używany do reprezentowania odwołań cyklicznych, następujący kod:

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

Prowadzi do: TypeError: Converting circular structure to JSON

Jedyny wybór, jaki masz, to zachować tylko kompozyt -> część składową kompozycji i odrzucić komponent „wstecznej nawigacji” -> kompozyt, a więc w twoim przykładzie:

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

Nic nie stoi na przeszkodzie, aby ponownie skomponować tę właściwość nawigacji po stronie klienta, tutaj przy użyciu jQuery:

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

Ale wtedy będziesz musiał odrzucić go ponownie przed wysłaniem go z powrotem na serwer, ponieważ JSON.stringify nie będzie w stanie serializować cyklicznego odwołania:

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

Teraz jest problem z użyciem encji EF jako encji modelu widoku.

Najpierw EF może używać dynamicznych serwerów proxy swojej klasy do implementowania zachowań takich jak wykrywanie zmian lub leniwe ładowanie, musisz je wyłączyć, jeśli chcesz serializować encje EF.

Ponadto używanie obiektów EF w interfejsie użytkownika może być zagrożone, ponieważ wszystkie domyślne spoiwa będą mapować każde pole z żądania na pola podmiotów, w tym te, których użytkownik nie chciał ustawić.

Dlatego jeśli chcesz, aby Twoja aplikacja MVC była odpowiednio zaprojektowana, zaleciłbym użycie dedykowanego modelu widoku, aby zapobiec „odwróceniu się” twojego wewnętrznego modelu biznesowego przed ujawnieniem się klientowi, dlatego poleciłbym ci konkretny model widoku.


Czy istnieje jakiś wymyślny sposób z technikami obiektowymi, w których można obejść zarówno odniesienie kołowe, jak i problem EF.
DanScan

Czy istnieje jakiś wymyślny sposób z technikami obiektowymi, w których można obejść zarówno odniesienie kołowe, jak i problem EF? Podobnie jak BaseObject jest dziedziczony przez entityObject i viewObject. Więc podmiotObject miałby odwołanie cykliczne, ale viewObject nie miałby odwołania cyklicznego. Ominąłem to, budując viewObject z bytuObject (viewObject.name = entityObject.name), ale wydaje się to stratą czasu. Jak mogę obejść ten problem?
DanScan

Oni bardzo . Twoje wyjaśnienie było bardzo jasne i łatwe do zrozumienia.
Nick

2

Prostszą alternatywą dla próby serializacji obiektów byłoby wyłączenie serializacji obiektów nadrzędnych / podrzędnych. Zamiast tego możesz wykonać osobne wywołanie w celu pobrania powiązanych obiektów nadrzędnych / podrzędnych, kiedy i kiedy ich potrzebujesz. To może nie być idealne dla twojej aplikacji, ale jest to opcja.

Aby to zrobić, możesz skonfigurować DataContractSerializer i ustawić właściwość DataContractSerializer.PreserveObjectReferences na „false” w konstruktorze klasy modelu danych. Oznacza to, że odniesienia do obiektów nie powinny być zachowywane przy serializowaniu odpowiedzi HTTP.

Przykłady:

Format Json:

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

Format XML:

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

Oznacza to, że jeśli pobierzesz element, do którego odwołują się obiekty potomne, obiekty potomne nie będą serializowane.

Zobacz także klasę DataContractsSerializer .


1

Serializator JSON zajmujący się odwołaniami cyklicznymi

Oto przykład niestandardowego Jacksona, JSONSerializerktóry zajmuje się referencjami cyklicznymi, serializując pierwsze wystąpienie i przechowując * referencedo pierwszego wystąpienia we wszystkich kolejnych wystąpieniach.

Postępowanie z odniesieniami kołowymi podczas szeregowania obiektów za pomocą Jacksona

Odpowiedni fragment częściowy z powyższego artykułu:

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

Jedynym rozwiązaniem, które wymyśliłem, jest stworzenie modeli „Widok”. Tworzę proste wersje modeli baz danych, które nie zawierają odwołania do klasy nadrzędnej. Każdy z tych modeli widoków ma metodę zwracania wersji bazy danych i konstruktora, który przyjmuje model bazy danych jako parametr (viewmodel.name = databasemodel.name). Ta metoda wydaje się wymuszona, chociaż działa.

Jedynym prawidłowym rozwiązaniem jest przesłanie minimum danych. Gdy wysyłasz dane z bazy danych, zwykle nie ma sensu wysyłanie każdej kolumny ze wszystkimi powiązaniami. Konsumenci nie powinni mieć do czynienia z powiązaniami i strukturami baz danych, czyli bazami danych. Pozwoli to nie tylko zaoszczędzić przepustowość, ale także będzie o wiele łatwiejsze w utrzymaniu, czytaniu i zużyciu. Zapytaj o dane, a następnie zamodeluj je pod kątem tego, czego faktycznie potrzebujesz, aby wysłać eq. absolutne minimum.


W przypadku dużych zbiorów danych wymagany jest dłuższy czas przetwarzania, ponieważ teraz trzeba wszystko przekształcić dwa razy.
David van Dugteren

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.