Błąd JSON.NET Wykryto pętlę odwołania do typu dla typu

494

Próbowałem serializować klasę POCO, która została automatycznie wygenerowana z Entity Data Model .edmx i kiedy jej użyłem

JsonConvert.SerializeObject 

Wystąpił następujący błąd:

Błąd Wykryto pętlę odwoływania się do typu System.data.entity.

Jak rozwiązać ten problem?

NevenHuynh
źródło
gdy korzystasz z Linq i MVC: stackoverflow.com/a/38241856
aDDin
podczas korzystania z .NET Core 2: stackoverflow.com/a/48709134/4496145
Dave Skender
2
Ten błąd przydarzył mi się, gdy chciałem serializować wynik asyncwywołania metody (a Task) i zapomniałem poprzedzić awaitinstrukcję.
Uwe Keim,

Odpowiedzi:

485

To było najlepsze rozwiązanie https://code.msdn.microsoft.com/Loop-Reference-handling-in-caaffaf7

Poprawka 1: Globalne ignorowanie odniesienia cyklicznego

(Wybrałem / wypróbowałem ten, podobnie jak wiele innych)

Serializator json.net ma opcję ignorowania odwołań cyklicznych. Umieść następujący kod w WebApiConfig.cspliku:

 config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling 
= Newtonsoft.Json.ReferenceLoopHandling.Ignore; 

Prosta poprawka sprawi, że serializator zignoruje odwołanie, co spowoduje pętlę. Ma jednak ograniczenia:

  • Dane tracą informacje referencyjne dotyczące pętli
  • Poprawka dotyczy tylko JSON.net
  • Poziomu referencji nie można kontrolować, jeśli istnieje głęboki łańcuch referencyjny

Jeśli chcesz użyć tej poprawki w projekcie ASP.NET bez interfejsu API, możesz dodać powyższą linię do Global.asax.cs, ale najpierw dodaj:

var config = GlobalConfiguration.Configuration;

Jeśli chcesz użyć tego w projekcie .Net Core , możesz zmienić Startup.csjako:

  var mvc = services.AddMvc(options =>
        {
           ...
        })
        .AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);

Poprawka 2: Zachowanie globalnego odniesienia cyklicznego

Ta druga poprawka jest podobna do pierwszej. Po prostu zmień kod na:

config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling 
     = Newtonsoft.Json.ReferenceLoopHandling.Serialize;     
config.Formatters.JsonFormatter.SerializerSettings.PreserveReferencesHandling 
     = Newtonsoft.Json.PreserveReferencesHandling.Objects;

Kształt danych zostanie zmieniony po zastosowaniu tego ustawienia.

[
   {
      "$id":"1",
      "Category":{
         "$id":"2",
         "Products":[
            {
               "$id":"3",
               "Category":{
                  "$ref":"2"
               },
               "Id":2,
               "Name":"Yogurt"
            },
            {
               "$ref":"1"
            }
         ],
         "Id":1,
         "Name":"Diary"
      },
      "Id":1,
      "Name":"Whole Milk"
   },
   {
      "$ref":"3"
   }
]

Identyfikatory $ id i $ ref utrzymują wszystkie odwołania i wyrównują poziom wykresu obiektowego, ale kod klienta musi znać zmianę kształtu, aby zużywać dane, i dotyczy to również serializatora JSON.NET.

Poprawka 3: Zignoruj ​​i zachowaj atrybuty odniesienia

Ta poprawka dekoruje atrybuty klasy modelu, aby kontrolować zachowanie serializacji na poziomie modelu lub właściwości. Aby zignorować właściwość:

 public class Category 
    { 
        public int Id { get; set; } 
        public string Name { get; set; } 

        [JsonIgnore] 
        [IgnoreDataMember] 
        public virtual ICollection<Product> Products { get; set; } 
    } 

JsonIgnore jest dla JSON.NET, a IgnoreDataMember jest dla XmlDCSerializer. Aby zachować odniesienie:

 // Fix 3 
        [JsonObject(IsReference = true)] 
        public class Category 
        { 
            public int Id { get; set; } 
            public string Name { get; set; } 

           // Fix 3 
           //[JsonIgnore] 
           //[IgnoreDataMember] 
           public virtual ICollection<Product> Products { get; set; } 
       } 

       [DataContract(IsReference = true)] 
       public class Product 
       { 
           [Key] 
           public int Id { get; set; } 

           [DataMember] 
           public string Name { get; set; } 

           [DataMember] 
           public virtual Category Category { get; set; } 
       }

JsonObject(IsReference = true)]dotyczy JSON.NET i [DataContract(IsReference = true)]dotyczy XmlDCSerializer. Uwaga: po zastosowaniu DataContractna zajęciach musisz dodać DataMemberdo właściwości, które chcesz serializować.

Atrybuty mogą być stosowane zarówno w serializerze json, jak i xml i dają większą kontrolę nad klasą modelu.

Bishoy Hanna
źródło
7
Poprawka 3 działa dla mnie. Wystarczy usunąć atrybuty DataContract i DataMember i umieścić JsonObject (IsReference = true) w DTO. I to działa. Dzięki.
maestro
1
spróbuj tego GlobalConfiguration.Configuration
Bishoy Hanna
1
Poprawka 3 ma tę zaletę, że działa na kodzie klienta tam, gdzie nie ma
konfiguracji globalnej
1
@BishoyHanna, czy możesz edytować swoją odpowiedź, aby umożliwić jej użycie z normalnych aplikacji ASP.NET? Możesz użyć mojej sugerowanej edycji: stackoverflow.com/review/suggested-edits/17797683
NH.
2
Zastosowanie [JsonIgnore]powyższego atrybutu działało dla mnie.
Nathan Beck,
467

Użyj JsonSerializerSettings

  • ReferenceLoopHandling.Error(domyślnie) spowoduje błąd, jeśli napotkamy pętlę odniesienia. Dlatego otrzymujesz wyjątek.
  • ReferenceLoopHandling.Serialize jest przydatny, jeśli obiekty są zagnieżdżone, ale nie w nieskończoność.
  • ReferenceLoopHandling.Ignore nie będzie serializować obiektu, jeśli jest on obiektem potomnym.

Przykład:

JsonConvert.SerializeObject(YourPOCOHere, Formatting.Indented, 
new JsonSerializerSettings { 
        ReferenceLoopHandling = ReferenceLoopHandling.Serialize
});

W przypadku konieczności serializacji obiektu zagnieżdżonego w nieskończoność można użyć PreserveObjectReferences, aby uniknąć wyjątku StackOverflowException.

Przykład:

JsonConvert.SerializeObject(YourPOCOHere, Formatting.Indented, 
new JsonSerializerSettings { 
        PreserveReferencesHandling = PreserveReferencesHandling.Objects
});

Wybierz, co ma sens dla serializowanego obiektu.

Odniesienie http://james.newtonking.com/json/help/

DalSoft
źródło
66
Wystąpił błąd podczas serializacji pliku danych. Kiedyś ReferenceLoopHandling = ReferenceLoopHandling.Ignoreto działało
8
Jeśli w danych występują pętle referencyjne, użycie ReferenceLoopHandling.Serializespowoduje, że serializator przejdzie w nieskończoną pętlę rekurencyjną i przepełni stos.
Brian Rogers
1
Poprawny. Ponieważ pytanie dotyczy modelu EF, również ważna jest obawa. Poprawiono, aby zapewnić wszystkie dostępne opcje.
DalSoft
1
Ten sam błąd napotkałem podczas próby serializacji obiektu ... jednak obiekt nie ma żadnych odniesień innych niż typ wyliczeniowy.
Marin
1
dla mnie EF jest główną przyczyną tego problemu, ponieważ jednostki z odnośnikami są wszędzie.
Teoman shipahi
57

Rozwiązaniem jest ignorowanie odniesień do pętli i nie serializowanie ich. To zachowanie jest określone w JsonSerializerSettings.

SingleJsonConvert z przeciążeniem:

JsonConvert.SerializeObject(YourObject, Formatting.Indented,
    new JsonSerializerSettings() {
        ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
    }
);

Ustawienie globalne z kodem Application_Start()w Global.asax.cs:

JsonConvert.DefaultSettings = () => new JsonSerializerSettings {
     Formatting = Newtonsoft.Json.Formatting.Indented,
     ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
};

Odniesienie: https://github.com/JamesNK/Newtonsoft.Json/issues/78

fartuch
źródło
Dlaczego format globalny jest wcięty?
Murphybro2,
Absolutnie to, czego potrzebowaliśmy do rozwiązania tego problemu (odkryte podczas wdrożenia)! Ty stary ... dzięki za oszczędność czasu !!
Ryan Eastabrook
Rozwiązałem swój problem, dodając „JsonConvert.DefaultSettings” = () => new JsonSerializerSettings {....} w klasie „Startup.cs”
Beldi Anouar
45

Najprostszym sposobem na to jest zainstalowanie Json.NET z nuget i dodanie [JsonIgnore]atrybutu do właściwości wirtualnej w klasie, na przykład:

    public string Name { get; set; }
    public string Description { get; set; }
    public Nullable<int> Project_ID { get; set; }

    [JsonIgnore]
    public virtual Project Project { get; set; }

Chociaż w dzisiejszych czasach tworzę model z właściwościami, które chcę przekazać, dzięki czemu jest lżejszy, nie zawiera niechcianych kolekcji i nie tracę zmian po odbudowaniu wygenerowanych plików ...

Sam Jones
źródło
3
Najlepsza odpowiedź przy użyciu Newton JSON
Aizen
21

W .NET Core 1.0 możesz ustawić to jako ustawienie globalne w pliku Startup.cs:

using System.Buffers;
using Microsoft.AspNetCore.Mvc.Formatters;
using Newtonsoft.Json;

// beginning of Startup class

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc(options =>
        {
            options.OutputFormatters.Clear();
            options.OutputFormatters.Add(new JsonOutputFormatter(new JsonSerializerSettings(){
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
            }, ArrayPool<char>.Shared));
        });
    }
Caleb
źródło
Ale w tym przypadku, jeśli chcę wiedzieć, że ta właściwość jest ignorowana, nie otrzymam żadnego wyjątku.
Mayer Spitzer
10

Jeśli używasz .NET Core 2.x, zaktualizuj sekcję ConfigureServices w pliku Startup.cs

https://docs.microsoft.com/en-us/ef/core/querying/related-data#related-data-and-serialization

    public void ConfigureServices(IServiceCollection services)
    {
    ...

    services.AddMvc()
        .AddJsonOptions(
            options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
        );

    ...
    }

Jeśli używasz .NET Core 3.x bez MVC, byłoby to:

services.AddControllers()
  .AddNewtonsoftJson(options =>
      options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
   );

Ta obsługa pętli referencyjnych jest prawie obowiązkowa, jeśli używasz Entity Framework i wzorca projektowego opartego na bazie danych.

Dave Skender
źródło
2
co jeśli nie użyję services.AddMvc()?
prisar
2
czy to zła praktyka?
Renan Coelho,
Na pierwszy rzut oka możesz pomyśleć, że jest to zła praktyka, ponieważ może zastąpić „celowe projektowanie” unikania starego problemu „nieskończonej pętli”. Jeśli jednak zastanowisz się nad przypadkami użycia klas, możesz potrzebować ich do wzajemnego odniesienia. Na przykład możesz chcieć uzyskać dostęp zarówno do Drzewa> Owoce, jak i Owoce> Drzewa.
Dave Skender
Ponadto, jeśli używasz wzorca projektowego opartego na pierwszej bazie danych z czymś takim jak Entity Framework, w zależności od tego, jak skonfigurujesz swoje klucze obce w bazie danych, automatycznie utworzy te cykliczne odwołania, więc będziesz musiał użyć tego ustawienia, jeśli „Odwróć inżynierię swoich klas.
Dave Skender
9

Aby serializować nas w NEWTONSOFTJSON, gdy masz problem z pętlą, w moim przypadku nie potrzebowałem modyfikować global.asax ani apiconfig. Po prostu używam JsonSerializesSettings ignorując obsługę zapętlania.

JsonSerializerSettings jss = new JsonSerializerSettings();
jss.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
var lst = db.shCards.Where(m => m.CardID == id).ToList();
string json = JsonConvert.SerializeObject(lst, jss);
Carlos Barini
źródło
1
Jeśli ktoś przyszedł tutaj po linijkę, aby przejść do okna zegarka, aby można było przeszukiwać tekst:Newtonsoft.Json.JsonConvert.SerializeObject(objToSerialize, new Newtonsoft.Json.JsonSerializerSettings() {ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore});
Graham
8

Możemy dodać te dwa wiersze do konstruktora klasy DbContext, aby wyłączyć pętlę odwoływania się do siebie, np

public TestContext()
        : base("name=TestContext")
{
    this.Configuration.LazyLoadingEnabled = false;
    this.Configuration.ProxyCreationEnabled = false;
}
Sanjay Nishad
źródło
Jest to jeden z najprostszych i działa jak urok . Głosowano, wielkie dzięki ...
Murat Yıldız
Jak napisałem w drugim pytaniu: Nie podoba mi się ten rodzaj odpowiedzi, ponieważ wyłączasz funkcję EF6, która jest domyślnie włączona, a ten fragment kodu może uszkodzić inne części programu. Powinieneś wyjaśnić, co to robi i jakie to ma konsekwencje.
El Mac
@ElMac masz rację, ale jeśli nie potrzebujemy tej funkcji, dlaczego nie możesz skorzystać z tego rozwiązania?
Sanjay Nishad
@SanjayNishad Nie mam nic przeciwko, jeśli nie potrzebujesz tej funkcji. Chodzi tylko o użytkowników, którzy nie wiedzą, co wyłączają.
El Mac
6

Możesz także zastosować atrybut do właściwości. Ten [JsonProperty( ReferenceLoopHandling = ... )]atrybut jest do tego odpowiedni.

Na przykład:

/// <summary>
/// Represents the exception information of an event
/// </summary>
public class ExceptionInfo
{
    // ...code omitted for brevity...

    /// <summary>
    /// An inner (nested) error.
    /// </summary>
    [JsonProperty( ReferenceLoopHandling = ReferenceLoopHandling.Ignore, IsReference = true )]
    public ExceptionInfo Inner { get; set; }

    // ...code omitted for brevity...    
}

Mam nadzieję, że to pomaga, Jaans

Jaans
źródło
4

Aby zignorować odwołania do pętli i nie serializować ich globalnie w MVC 6, skorzystaj z następujących poleceń w pliku startup.cs:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().Configure<MvcOptions>(options =>
        {
            options.OutputFormatters.RemoveTypesOf<JsonOutputFormatter>();
            var jsonOutputFormatter = new JsonOutputFormatter();
            jsonOutputFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
            options.OutputFormatters.Insert(0, jsonOutputFormatter);
        });
    }
GerardBeckerleg
źródło
2

Użyj tego w WebApiConfig.csklasie:

var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);
Anand Kumar
źródło
2

Dla mnie musiałem wybrać inną trasę. Zamiast próbować naprawić serializator JSON.Net musiałem pójść po Leniwym ładowaniu w moim tekście.

Właśnie dodałem to do mojego podstawowego repozytorium:

context.Configuration.ProxyCreationEnabled = false;

Obiekt „kontekstowy” jest parametrem konstruktora, którego używam w moim podstawowym repozytorium, ponieważ używam wstrzykiwania zależności. Zamiast tego możesz zmienić właściwość ProxyCreationEnabled w dowolnym miejscu, w którym utworzony zostanie tekst danych.

http://techie-tid-bits.blogspot.com/2015/09/jsonnet-serializer-and-error-self.html

Xipooo
źródło
2

Miałem ten wyjątek, a moje działające rozwiązanie jest łatwe i proste,

Zignoruj ​​właściwość Referenced, dodając do niej atrybut JsonIgnore:

[JsonIgnore]
public MyClass currentClass { get; set; }

Zresetuj właściwość podczas deserializacji:

Source = JsonConvert.DeserializeObject<MyObject>(JsonTxt);
foreach (var item in Source)
        {
            Source.MyClass = item;
        }

using Newtonsoft.Json;

Mayer Spitzer
źródło
To jest magia, której potrzebuję. Rozwiąż to[JsonIgnore]
saviour123,
2

Zespół:

Działa to z ASP.NET Core; Wyzwanie związane z powyższym polega na tym, jak „ustawić ustawienie na ignorowanie”. W zależności od konfiguracji aplikacji może to być dość trudne. Oto, co zadziałało dla mnie.

Można to umieścić w swojej publicznej nieważnej sekcji ConfigureServices (usługi IServiceCollection).

services.AddMvc().AddJsonOptions(opt => 
        { 
      opt.SerializerSettings.ReferenceLoopHandling =
      Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        });
FlyingV
źródło
2

Ludzie mówili już o dodaniu [JsonIgnore] do wirtualnej właściwości w klasie, na przykład:

[JsonIgnore]
public virtual Project Project { get; set; }

Podzielę się również inną opcją, [JsonProperty (NullValueHandling = NullValueHandling.Ignore)], która pomija właściwość serializacji tylko wtedy, gdy jest null:

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public virtual Project Project { get; set; }
Ali Raza
źródło
1

W przypadku .NET Core 3.0 zaktualizuj klasę Startup.cs, jak pokazano poniżej.

public void ConfigureServices(IServiceCollection services)
{
...

services.AddControllers()
    .AddNewtonsoftJson(
        options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
    );

...
}

Zobacz: https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-core-3-0-preview-5/

Jimmy
źródło
0

Po prostu umieść Configuration.ProxyCreationEnabled = false;w pliku kontekstowym; to rozwiąże problem.

public demEntities()
    : base("name=demEntities")
{
    Configuration.ProxyCreationEnabled = false;
}
fraka
źródło
0

Mój problem rozwiązany dzięki niestandardowej konfiguracji JsonSerializerSettings:

services.AddMvc(
  // ...
               ).AddJsonOptions(opt =>
                 {
                opt.SerializerSettings.ReferenceLoopHandling =
                    Newtonsoft.Json.ReferenceLoopHandling.Serialize;
                opt.SerializerSettings.PreserveReferencesHandling =
                    Newtonsoft.Json.PreserveReferencesHandling.Objects;
                 });
AminGolmahalle
źródło
0

Upewnij się również, że używasz metody Oczekuj i asynchronizuj w swojej metodzie. Ten błąd można uzyskać, jeśli obiekt nie jest poprawnie serializowany.

maxspan
źródło
0

Miałem do czynienia z tym samym problemem i próbowałem użyć JsonSetting, aby zignorować błąd samoreferencji, który działał, dopóki nie otrzymałem klasy, która sam się tak bardzo odwołuje, a mój proces dot-net opiera się na wartości zapisu Jsona.

Mój problem

    public partial class Company : BaseModel
{
    public Company()
    {
        CompanyUsers = new HashSet<CompanyUser>();
    }

    public string Name { get; set; }

    public virtual ICollection<CompanyUser> CompanyUsers { get; set; }
}

public partial class CompanyUser
{
    public int Id { get; set; }
    public int CompanyId { get; set; }
    public int UserId { get; set; }

    public virtual Company Company { get; set; }

    public virtual User User { get; set; }
}

public partial class User : BaseModel
{
    public User()
    {
        CompanyUsers = new HashSet<CompanyUser>();
    }

    public string DisplayName { get; set; }
    public virtual ICollection<CompanyUser> CompanyUsers { get; set; }

}

Problem można zobaczyć w klasie użytkownika, która odnosi się do CompanyUser klasy która jest samodzielna.

Teraz wywołuję metodę GetAll, która zawiera wszystkie właściwości relacyjne.

cs.GetAll("CompanyUsers", "CompanyUsers.User");

Na tym etapie mój proces DotNetCore opiera się na wykonywaniu JsonResult, zapisywaniu wartości ... i nigdy nie nadchodzi. W moim Startup.cs już ustawiłem JsonOption. Z jakiegoś powodu EFCore zawiera zagnieżdżoną właściwość, której nie proszę Ef o podanie.

    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

takie powinno być oczekiwane zachowanie

Hej, EfCore, czy możesz dołączyć również dane „CompanyUsers” do mojej klasy firmowej, aby móc łatwo uzyskać dostęp do danych.

następnie

Hej EfCore, czy możesz również dołączyć dane „CompanyUsers.User”, aby móc łatwo uzyskać dostęp do danych takich jak Company.CompanyUsers.First (). User.DisplayName

na tym etapie powinienem otrzymać tylko „Company.CompanyUsers.First (). User.DisplayName” i nie powinien dać mi Company.CompanyUsers.First (). User.CompanyUsers, który powoduje problem z samodzielnym odwoływaniem się; Technicznie nie powinno dać mi User.CompanyUsers, ponieważ CompanyUsers jest własnością nawigacyjną. Ale EfCore jest bardzo podekscytowany i daje mi User.CompanyUsers .

Postanowiłem więc napisać metodę rozszerzenia właściwości, która ma zostać wykluczona z obiektu (tak naprawdę to nie wyklucza, po prostu ustawia właściwość na null). Nie tylko zadziała również na właściwości tablicy. poniżej znajduje się kod Wyeksportuję również pakiet nuget dla innych użytkowników (nie jestem pewien, czy to komuś pomoże). Powód jest prosty, ponieważ jestem zbyt leniwy, aby pisać. Wybierz (n => new {n.p1, n.p2}); Po prostu nie chcę pisać instrukcji select, aby wykluczyć tylko 1 właściwość!

To nie jest najlepszy kod (na pewnym etapie zaktualizuję), ponieważ napisałem w pośpiechu i chociaż może to pomóc komuś, kto chce wykluczyć (ustawić null) również w obiekcie z tablicami.

    public static class PropertyExtensions
{
    public static void Exclude<T>(this T obj, Expression<Func<T, object>> expression)
    {
        var visitor = new PropertyVisitor<T>();
        visitor.Visit(expression.Body);
        visitor.Path.Reverse();
        List<MemberInfo> paths = visitor.Path;
        Action<List<MemberInfo>, object> act = null;

        int recursiveLevel = 0;
        act = (List<MemberInfo> vPath, object vObj) =>
        {

            // set last propert to null thats what we want to avoid the self-referencing error.
            if (recursiveLevel == vPath.Count - 1)
            {
                if (vObj == null) throw new ArgumentNullException("Object cannot be null");

                vObj.GetType().GetMethod($"set_{vPath.ElementAt(recursiveLevel).Name}").Invoke(vObj, new object[] { null });
                return;
            }

            var pi = vObj.GetType().GetProperty(vPath.ElementAt(recursiveLevel).Name);
            if (pi == null) return;
            var pv = pi.GetValue(vObj, null);
            if (pi.PropertyType.IsArray || pi.PropertyType.Name.Contains("HashSet`1") || pi.PropertyType.Name.Contains("ICollection`1"))
            {
                var ele = (IEnumerator)pv.GetType().GetMethod("GetEnumerator").Invoke(pv, null);

                while (ele.MoveNext())
                {
                    recursiveLevel++;
                    var arrItem = ele.Current;

                    act(vPath, arrItem);

                    recursiveLevel--;
                }

                if (recursiveLevel != 0) recursiveLevel--;
                return;
            }
            else
            {
                recursiveLevel++;
                act(vPath, pv);
            }

            if (recursiveLevel != 0) recursiveLevel--;

        };

        // check if the root level propert is array
        if (obj.GetType().IsArray)
        {
            var ele = (IEnumerator)obj.GetType().GetMethod("GetEnumerator").Invoke(obj, null);
            while (ele.MoveNext())
            {
                recursiveLevel = 0;
                var arrItem = ele.Current;

                act(paths, arrItem);
            }
        }
        else
        {
            recursiveLevel = 0;
            act(paths, obj);
        }

    }

    public static T Explode<T>(this T[] obj)
    {
        return obj.FirstOrDefault();
    }

    public static T Explode<T>(this ICollection<T> obj)
    {
        return obj.FirstOrDefault();
    }
}

powyżej klasy rozszerzenia daje możliwość ustawienia właściwości na null, aby uniknąć pętli odwołujących się nawet do tablic.

Konstruktor wyrażeń

    internal class PropertyVisitor<T> : ExpressionVisitor
{
    public readonly List<MemberInfo> Path = new List<MemberInfo>();

    public Expression Modify(Expression expression)
    {
        return Visit(expression);
    }


    protected override Expression VisitMember(MemberExpression node)
    {
        if (!(node.Member is PropertyInfo))
        {
            throw new ArgumentException("The path can only contain properties", nameof(node));
        }

        Path.Add(node.Member);
        return  base.VisitMember(node);
    }
}

Zastosowania:

Klasy modeli

    public class Person
{
    public string Name { get; set; }
    public Address AddressDetail { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public Country CountryDetail { get; set; }
    public Country[] CountryDetail2 { get; set; }
}

public class Country
{
    public string CountryName { get; set; }
    public Person[] CountryDetail { get; set; }
}

Dummy Data

           var p = new Person
        {
            Name = "Adeel Rizvi",
            AddressDetail = new Address
            {
                Street = "Sydney",
                CountryDetail = new Country
                {
                    CountryName = "AU"
                }
            }
        };

        var p1 = new Person
        {
            Name = "Adeel Rizvi",
            AddressDetail = new Address
            {
                Street = "Sydney",
                CountryDetail2 = new Country[]
                {
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A1" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A2" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A3" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A4" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A5" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A6" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A7" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A8" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A9" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A1" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A2" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A3" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A4" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A5" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A6" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A7" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A8" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A9" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },

                }
            }
        };

Skrzynie:

Przypadek 1: Wyklucz tylko właściwość bez tablicy

p.Exclude(n => n.AddressDetail.CountryDetail.CountryName);

Przypadek 2: Wyklucz właściwość z 1 tablicą

p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryName);

Przypadek 3: Wyklucz właściwość z 2 zagnieżdżonymi tablicami

p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryDetail.Explode().Name);

Przypadek 4: EF GetAll Query With Includes

var query = cs.GetAll("CompanyUsers", "CompanyUsers.User").ToArray();
query.Exclude(n => n.Explode().CompanyUsers.Explode().User.CompanyUsers);
return query;

Zauważyłeś, że metoda Explode () jest również metodą rozszerzenia tylko dla naszego konstruktora wyrażeń, aby uzyskać właściwość z właściwości tablicy. Ilekroć istnieje właściwość tablicowa, użyj .Explode (). YourPropertyToExclude lub .Explode (). Property1.MyArrayProperty.Explode (). MyStupidProperty . Powyższy kod pomaga mi uniknąć odwoływania się do siebie tak głęboko, jak chcę. Teraz mogę korzystać z GetAll i wykluczyć właściwość, której nie chcę!

Dziękujemy za przeczytanie tego dużego posta!

Adeel Rizvi
źródło
-1

Za to, że nie zapętlałem, działało to dla mnie-
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,

Rozwiązałem to wszystko tutaj - serializacja dzieci Entity Framework za pomocą .AP Core 2 WebAPI https://gist.github.com/Kaidanov/f9ad0d79238494432f32b8407942c606

Z pewnością docenią wszelkie uwagi. może ktoś może go kiedyś użyć.

Cwi Gregory Kaidanov
źródło
-1

Kod C #:

            var jsonSerializerSettings = new JsonSerializerSettings
            {
                ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
                PreserveReferencesHandling = PreserveReferencesHandling.Objects,
            };

            var jsonString = JsonConvert.SerializeObject(object2Serialize, jsonSerializerSettings);

            var filePath = @"E:\json.json";

            File.WriteAllText(filePath, jsonString);
Lilach Rayzin
źródło
Jest to zasadniczo te same wskazówki, co w wysoko ocenianej odpowiedzi @ DalSoft sprzed ośmiu lat, ale z dużo mniej wyjaśnieniami.
Jeremy Caney
Mam nadzieję, że rozwiąże problem, ale proszę o dołączenie do niego wyjaśnienia kodu, aby użytkownik uzyskał doskonałe zrozumienie tego, czego naprawdę chce.
Jaimil Patel
-2

Podobało mi się rozwiązanie, które robi to Application_Start()tak, jak w odpowiedzi tutaj

Najwyraźniej nie mogłem uzyskać dostępu do obiektów json w JavaScript przy użyciu konfiguracji w ramach mojej funkcji, jak w odpowiedzi DalSoft, ponieważ zwrócony obiekt miał „\ n \ r” na całym (klucz, val) obiekcie.

W każdym razie wszystko, co działa, jest świetne (ponieważ różne podejścia działają w różnych scenariuszach w zależności od zadanych komentarzy i pytań), chociaż lepiej byłoby zastosować standardowy sposób postępowania z dobrą dokumentacją potwierdzającą to podejście.

rey_coder
źródło