Selbstreferenzierende Schleife erkannt - Daten von WebApi an den Browser zurückgeben


80

Ich verwende Entity Framework und habe ein Problem damit, übergeordnete und untergeordnete Daten in den Browser zu übertragen. Hier sind meine Klassen:

 public class Question
 {
    public int QuestionId { get; set; }
    public string Title { get; set; }
    public virtual ICollection<Answer> Answers { get; set; }
}

public class Answer
{
    public int AnswerId { get; set; }
    public string Text { get; set; }
    public int QuestionId { get; set; }
    public virtual Question Question { get; set; }
}

Ich verwende den folgenden Code, um die Frage- und Antwortdaten zurückzugeben:

    public IList<Question> GetQuestions(int subTopicId, int questionStatusId)
    {
        var questions = _questionsRepository.GetAll()
            .Where(a => a.SubTopicId == subTopicId &&
                   (questionStatusId == 99 ||
                    a.QuestionStatusId == questionStatusId))
            .Include(a => a.Answers)
            .ToList();
        return questions; 
    }

Auf der C # -Seite scheint dies zu funktionieren, aber ich stelle fest, dass die Antwortobjekte Verweise auf die Frage haben. Wenn ich die WebAPI verwende, um die Daten an den Browser zu senden, wird folgende Meldung angezeigt:

Der Typ 'ObjectContent`1' konnte den Antworttext für den Inhaltstyp 'application / json nicht serialisieren. Zeichensatz = utf-8 '.

Selbstreferenzierende Schleife für Eigenschaft 'Frage' mit Typ 'Models.Core.Question' erkannt.

Liegt das daran, dass die Frage Antworten enthält und die Antworten einen Verweis auf die Frage haben? Alle Orte, an denen ich gesucht habe, schlagen vor, einen Verweis auf die Eltern im Kind zu haben, sodass ich nicht sicher bin, was ich tun soll. Kann mir jemand einen Rat geben.


6
Verwenden Sie Dto für Ihre Web-API und vermeiden Sie die Rückgabe von Entity direkt in Ihrer Antwort
cuongle

Was ist Dto? Unsere gesamte Anwendung verwendet EF, wir verwenden AngularJS auf dem Client und wir haben keine anderen Probleme als für diesen einen Fall.

1
Was ich damit meinte, Sie sollten Ihr Dto für Ihre Web-API definieren. Dto ähnelt ViewModel in MVC. Dto ist wie ein Wrapper Ihres EF-Modells, um Ihrem Kunden Daten bereitzustellen (anglejs).
Cuongle


Vielleicht sehen Sie sich meine Antwort auf die Ausnahme "Self Referencing Loop Detected" mit der Seite JSON.Net an .
Murat Yıldız

Antworten:


73

Liegt das daran, dass die Frage Antworten enthält und die Antworten einen Verweis auf die Frage haben?

Ja. Es kann nicht serialisiert werden.

BEARBEITEN: Siehe Tallmaris 'Antwort und OttOs Kommentar, da er einfacher ist und global eingestellt werden kann.

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Re‌​ferenceLoopHandling = ReferenceLoopHandling.Ignore;

Alte Antwort:

Projizieren Sie das EF-Objekt Questionauf Ihr eigenes Intermediate- oder DataTransferObject. Dieses Dto kann dann erfolgreich serialisiert werden.

public class QuestionDto
{
    public QuestionDto()
    {
        this.Answers = new List<Answer>();
    } 
    public int QuestionId { get; set; }
    ...
    ...
    public string Title { get; set; }
    public List<Answer> Answers { get; set; }
}

Etwas wie:

public IList<QuestionDto> GetQuestions(int subTopicId, int questionStatusId)
{
    var questions = _questionsRepository.GetAll()
        .Where(a => a.SubTopicId == subTopicId &&
               (questionStatusId == 99 ||
                a.QuestionStatusId == questionStatusId))
        .Include(a => a.Answers)
        .ToList();

    var dto = questions.Select(x => new QuestionDto { Title = x.Title ... } );

    return dto; 
}

3
Ich möchte hinzufügen, dass für mich das Festlegen von ReferenceLoopHandling.Ignore nicht funktioniert hat, das Festlegen global oder beim API-Start überhaupt nicht funktioniert hat. Ich habe es geschafft, es zum Laufen zu bringen, indem ich die Navigationseigenschaft der untergeordneten Klasse mit [JsonIgnore] dekoriert habe. Ich erhalte immer noch die ParentId, aber die Parent-Navigation wird beim Serialisieren ignoriert.
Claiton Lovato

Hallo, das Ignorieren der Serialisierung unterbricht die zirkuläre Abhängigkeit Frage> Antwort> Frage. Bewahrt der DTO-Ansatz dies?
Bartosz

Ich habe dieses Problem in einem alten ASP.NET MVC-Projekt. GlobalConfiguration.Configuration hat keine Formatierer. Können Sie bitte vorschlagen, was dafür getan werden kann?
Ren

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Re‌ ferenceLoopHandling = ReferenceLoopHandling.Ignore; -> Wo soll diese Codezeile abgelegt werden ???
anhtv13

56

Sie können dies auch in Ihrem Application_Start():

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;

Es sollte Ihr Problem beheben, ohne viele Reifen zu durchlaufen.


BEARBEITEN : Verwenden Sie gemäß dem folgenden Kommentar von OttO ReferenceLoopHandling.Ignorestattdessen : .

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;

78
Ich weiß, dass dies ein alter Thread ist, aber für diejenigen, die in Zukunft darauf stoßen, versuchen Sie: GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
OttO

@OttO, deine Vorschläge haben bei mir funktioniert. Vielen Dank.
J86

2
Code geht in eine Endlosschleife und zeigt nach dem Hinzufügen dieser Zeile eine Stapelüberlaufausnahme an.
Microsoft Developer

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; funktioniert besser
Dragos Durlut

@Demodave Sie müssen Ihren JsonSerializer mit der statischen Create()Methode erstellen , die eine Einstellung als Parameter akzeptiert. Docs: newtonsoft.com/json/help/html/…
Tallmaris

21

Wenn Sie OWIN verwenden, denken Sie daran, dass Sie keine GlobalSettings mehr für Sie haben! Sie müssen dieselbe Einstellung in einem HttpConfiguration-Objekt ändern, das an die IAppBuilder UseWebApi-Funktion (oder an die Serviceplattform, auf der Sie sich befinden) übergeben wird.

Würde ungefähr so ​​aussehen.

    public void Configuration(IAppBuilder app)
    {      
       //auth config, service registration, etc      
       var config = new HttpConfiguration();
       config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
       //other config settings, dependency injection/resolver settings, etc
       app.UseWebApi(config);
}

1
Du hast meinen Tag gerettet. Ich habe mich gefragt, warum die Antwort oben nicht funktioniert hat. Ja, die Verwendung der OWIN-Einstellung in Global.asax funktioniert nicht.
Sithu

21

In ASP.NET Core lautet der Fix wie folgt:

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

5

Wenn Sie DNX / MVC 6 / ASP.NET vNext verwenden, HttpConfigurationfehlt sogar bla . Sie müssen Formatierer konfigurieren, indem Sie die folgenden Codes in Ihrer Startup.csDatei verwenden.

public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().Configure<MvcOptions>(option => 
        {
            //Clear all existing output formatters
            option.OutputFormatters.Clear();
            var jsonOutputFormatter = new JsonOutputFormatter();
            //Set ReferenceLoopHandling
            jsonOutputFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
            //Insert above jsonOutputFormatter as the first formatter, you can insert other formatters.
            option.OutputFormatters.Insert(0, jsonOutputFormatter);
        });
    }

1
In asp-net rc-1-final glaube ich, dass es jetzt "services.Configure <MvcOptions>" ist
Michał W.

JsonOutputFormatter befindet sich im Namespace Microsoft.AspNet.Mvc.Formatters
Sam

2
Für .NET Core 1.0 RTM: neuer JsonOutputFormatter (serializerSettings, ArrayPool <char> .Shared);
aherrick

5

ASP.NET Core-Web-API (.NET Core 2.0):

// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.Configure<MvcJsonOptions>(config =>
    {
        config.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
    });
}

2

Verwenden Sie diese:

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore

hat bei mir nicht funktioniert. Stattdessen habe ich eine neue, vereinfachte Version meiner Modellklasse erstellt, um sie zu testen, und das hat gut funktioniert. Dieser Artikel befasst sich mit einigen Problemen, die ich in meinem Modell hatte und die für EF hervorragend funktionierten, aber nicht serialisierbar waren:

http://www.asp.net/web-api/overview/data/using-web-api-with-entity-framework/part-4


1

ReferenceLoopHandling.Ignore hat bei mir nicht funktioniert. Die einzige Möglichkeit, dies zu umgehen, bestand darin, die Links zu den Eltern, die ich nicht wollte, per Code zu entfernen und die Links beizubehalten, die ich getan hatte.

parent.Child.Parent = null;

1

Für eine neue Asp.Net-Webanwendung mit .Net Framework 4.5:

Web-API: Gehe zu App_Start -> WebApiConfig.cs:

Sollte ungefähr so ​​aussehen:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services
        // Configure Web API to use only bearer token authentication.
        config.SuppressDefaultHostAuthentication();
        config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

        // ReferenceLoopHandling.Ignore will solve the Self referencing loop detected error
        config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;

        //Will serve json as default instead of XML
        config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

1

Als Teil von ASP.NET Core 3.0 hat das Team Json.NET standardmäßig nicht mehr aufgenommen. Weitere Informationen hierzu finden Sie im Allgemeinen unter [Einschließlich Json.Net bis Netcore 3.x] [1] https://github.com/aspnet/Announcements/issues/325

Ein Fehler kann durch die Verwendung von Lazyloading verursacht werden: services.AddDbContext (options => options.UseLazyLoadingProxies () ... oder db.Configuration.LazyLoadingEnabled = true;

Fix: Hinzufügen zu startup.cs

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

0

Aufgrund des verzögerten Ladens wird dieser Fehler angezeigt. Daher ist mein Vorschlag, den virtuellen Schlüssel aus der Eigenschaft zu entfernen. Wenn Sie mit API arbeiten, ist das verzögerte Laden nicht gut für Ihren API-Zustand.

Sie müssen Ihrer Konfigurationsdatei keine zusätzliche Zeile hinzufügen.

public class Question
 {
    public int QuestionId { get; set; }
    public string Title { get; set; }
    public ICollection<Answer> Answers { get; set; }
}

public class Answer
{
    public int AnswerId { get; set; }
    public string Text { get; set; }
    public int QuestionId { get; set; }
    public Question Question { get; set; }
}

0

Ich stellte fest, dass dieser Fehler verursacht wurde, als ich ein edmx (XML-Datei, die ein konzeptionelles Modell definiert) einer vorhandenen Datenbank generierte und Navigationseigenschaften sowohl für die übergeordnete als auch für die untergeordnete Tabelle hatte. Ich habe alle Navigationslinks zu den übergeordneten Objekten gelöscht, da ich nur zu untergeordneten Objekten navigieren wollte, und das Problem wurde behoben.


0

Entitäten db = neue Entitäten ()

db.Configuration.ProxyCreationEnabled = false;

db.Configuration.LazyLoadingEnabled = false;


0

Sie können dynamisch eine neue untergeordnete Sammlung erstellen, um dieses Problem einfach zu umgehen.

public IList<Question> GetQuestions(int subTopicId, int questionStatusId)
    {
        var questions = _questionsRepository.GetAll()
            .Where(a => a.SubTopicId == subTopicId &&
                   (questionStatusId == 99 ||
                    a.QuestionStatusId == questionStatusId))
            .Include(a => a.Answers).Select(b=> new { 
               b.QuestionId,
               b.Title
               Answers = b.Answers.Select(c=> new {
                   c.AnswerId,
                   c.Text,
                   c.QuestionId }))
            .ToList();
        return questions; 
    }

0

Keine der Konfigurationen in den obigen Antworten hat in ASP.NET Core 2.2 für mich funktioniert.

Ich musste die JsonIgnoreAttribute in meinen virtuellen Navigationseigenschaften hinzufügen .

public class Question
{
    public int QuestionId { get; set; }
    public string Title { get; set; }
    [JsonIgnore]
    public virtual ICollection<Answer> Answers { get; set; }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.