627 Stimmen

JSON.NET-Fehler Selbstreferenzierende Schleife für Typ entdeckt

Ich habe versucht, die POCO-Klasse zu serialisieren, die automatisch aus dem Entity Data Model .edmx generiert wurde, und als ich

JsonConvert.SerializeObject 

Ich habe den folgenden Fehler erhalten:

Fehler Selbstreferenzierungsschleife für Typ System.data.entity entdeckt.

Wie kann ich dieses Problem lösen?

1voto

maxspan Punkte 12034

Bitte stellen Sie auch sicher, dass Sie await und async in Ihrer Methode verwenden. Sie können diesen Fehler erhalten, wenn Ihr Objekt nicht richtig serialisiert ist.

1voto

Dan Punkte 1

Ich habe eine Datenbankanwendung geerbt, die das Datenmodell an die Webseite weitergibt. Die Serialisierung wird standardmäßig versuchen, den gesamten Modellbaum zu durchlaufen, und die meisten der Antworten hier sind ein guter Anfang, wie man das verhindern kann.

Eine Möglichkeit, die noch nicht erforscht wurde, ist der Einsatz von Schnittstellen zur Unterstützung. Ich greife auf ein früheres Beispiel zurück:

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 interface IgnoreUser
{
    [JsonIgnore]
    User User { get; set; }
}

public interface IgnoreCompany
{
    [JsonIgnore]
    User User { get; set; }
}

public partial class CompanyUser : IgnoreUser, IgnoreCompany
{
}

Bei der obigen Lösung werden keine Json-Einstellungen beeinträchtigt. Das Setzen von LazyLoadingEnabled und/oder ProxyCreationEnabled auf false hat Auswirkungen auf die gesamte Backend-Codierung und verhindert einige der wahren Vorteile eines ORM-Tools. Abhängig von Ihrer Anwendung können die LazyLoading/ProxyCreation-Einstellungen das Laden der Navigationseigenschaften verhindern, ohne dass diese manuell geladen werden.

Hier ist eine viel, viel bessere Lösung, um zu verhindern, dass Navigationseigenschaften serialisiert werden, und es verwendet Standard-Json-Funktionalität: Wie kann ich JSON serializer ignorieren Navigationseigenschaften zu tun?

0voto

Adeel Rizvi Punkte 104

Ich war das gleiche Problem konfrontiert und ich versuchte mit JsonSetting zu ignorieren, die selbst-Referenzierung Fehler seine Art von Arbeit, bis ich eine Klasse, die selbst-Referenzierung sehr tief und meine Dot-Net-Prozess hängt auf Json schreiben Wert.

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

}

Sie können das Problem in der Benutzerklasse sehen, auf die sie verweist UnternehmenBenutzer Klasse, die selbstreferenzierend ist.

Jetzt rufe ich die GetAll-Methode auf, die alle relationalen Eigenschaften enthält.

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

In diesem Stadium bleibt mein DotNetCore-Prozess hängen bei Ausführen von JsonResult, Schreiben des Wertes ... und nie kommen. In meiner Startup.cs, habe ich bereits die JsonOption festgelegt. Aus irgendeinem Grund EFCore ist einschließlich verschachtelte Eigenschaft, die ich nicht Ef zu geben, fragen.

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

Das erwartete Verhalten sollte wie folgt aussehen

Hey EfCore können Sie bitte "CompanyUsers" Daten als auch in meinem Company-Klasse aufnehmen, damit ich einfach auf die Daten zugreifen kann.

dann

Hey EfCore, können Sie bitte auch die "FirmaBenutzer.Benutzer" Daten als auch, so dass ich leicht auf die Daten zugreifen kann wie folgt Firma.CompanyUsers.First().User.DisplayName

In diesem Stadium sollte ich nur Folgendes erhalten "Firma.CompanyUsers.First().User.DisplayName" und es sollte mich nicht Firma.CompanyUsers.First().User.CompanyUsers was das Problem der Selbstreferenzierung verursacht; technisch gesehen sollte es mir keine User.CompanyUsers da CompanyUsers eine Navigationseigenschaft ist. Aber, EfCore bekommen sehr aufgeregt und geben mir User.CompanyUsers .

Also beschloss ich, eine Erweiterungsmethode für die Eigenschaft aus dem Objekt ausgeschlossen werden zu schreiben (es ist eigentlich nicht auszuschließen, es ist nur die Eigenschaft auf null). Nicht nur, dass es auch auf Array-Eigenschaften als gut funktionieren wird. unten ist der Code, den ich auch gehen, um das Nuget-Paket für andere Benutzer zu exportieren (nicht sicher, ob dies sogar jemand hilft). Der Grund ist einfach, weil ich zu faul bin, zu schreiben .Select(n => new { n.p1, n.p2}); Ich möchte einfach keine Select-Anweisung schreiben, um nur 1 Eigenschaft auszuschließen!

Dies ist nicht der beste Code (ich werde zu einem späteren Zeitpunkt aktualisieren), da ich in Eile geschrieben habe und obwohl dies jemandem helfen könnte, der das Objekt mit Arrays auch ausschließen (null setzen) möchte.

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

obige Erweiterungsklasse gibt Ihnen die Möglichkeit, die Eigenschaft auf Null zu setzen, um die selbstreferenzierende Schleife auch Arrays zu vermeiden.

Expression Builder

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

Verwendungen:

Modell-Klassen

    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-Daten

           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" } } },

                }
            }
        };

Die Fälle:

Fall 1: Nur Eigenschaft ohne Array ausschließen

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

Fall 2: Eigenschaft mit 1 Array ausschließen

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

Fall 3: Eigenschaft ausschließen mit 2 verschachtelten Arrays

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

Fall 4: EF GetAll-Abfrage mit Includes

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

Sie haben festgestellt, dass Explodieren() Methode ist auch eine Erweiterung Methode nur für unsere Expression Builder, um die Eigenschaft von Array-Eigenschaft zu erhalten. Wann immer es eine Array-Eigenschaft gibt, verwenden Sie die .Explode().YourPropertyToExclude oder .Explode().Property1.MyArrayProperty.Explode().MyStupidProperty . Der obige Code hilft mir, die Selbstreferenzierung so tief zu vermeiden, wie ich will. Jetzt kann ich GetAll verwenden und die Eigenschaft ausschließen, die ich nicht haben möchte!

Vielen Dank, dass Sie diesen Beitrag gelesen haben!

0voto

fraka Punkte 1

Einfach platzieren Configuration.ProxyCreationEnabled = false; innerhalb der Kontextdatei; dadurch wird das Problem gelöst.

public demEntities()
    : base("name=demEntities")
{
    Configuration.ProxyCreationEnabled = false;
}

-1voto

Das hat bei mir funktioniert, weil ich keine Schleife gemacht habe.
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,

Ich habe alles hier gelöst - Entity Framework Kinder Serialisierung mit .Net Core 2 WebAPI https://gist.github.com/Kaidanov/f9ad0d79238494432f32b8407942c606

Wir freuen uns über jeden Hinweis. vielleicht kann jemand es irgendwann verwenden.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X