Wykryto pętlę odwołań do samych siebie - pobieranie danych z WebApi do przeglądarki


80

Używam Entity Framework i mam problem z pobieraniem danych rodziców i dzieci do przeglądarki. Oto moje zajęcia:

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

Używam następującego kodu, aby zwrócić dane pytania i odpowiedzi:

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

Po stronie C # wydaje się to działać, jednak zauważam, że obiekty odpowiedzi mają odniesienia z powrotem do pytania. Kiedy korzystam z WebAPI, aby pobrać dane do przeglądarki, otrzymuję następujący komunikat:

Typowi „ObjectContent” 1 nie udało się serializować treści odpowiedzi dla typu zawartości „application / json”; charset = utf-8 '.

Wykryto pętlę samoodniesień dla właściwości „pytanie” o typie „Models.Core.Question”.

Czy to dlatego, że pytanie ma odpowiedzi, a odpowiedzi mają odniesienie z powrotem do pytania? Wszystkie miejsca, które szukałem, sugerują nawiązanie do rodzica w dziecku, więc nie jestem pewien, co robić. Czy ktoś może mi doradzić w tej sprawie.


6
Użyj Dto dla swojego internetowego interfejsu API, unikając zwracania Jednostki bezpośrednio w odpowiedzi
— cuongle

Co to jest Dto? Cała nasza aplikacja korzysta z EF, używamy AngularJS na kliencie i nie mamy żadnych problemów poza tym jednym przypadkiem.

1
Mam na myśli, że powinieneś zdefiniować swoje Dto dla swojego Web Api, Dto jest trochę podobne do ViewModel w MVC. Dto jest jak opakowanie twojego modelu EF, które dostarcza dane twojego klienta (angularjs).
— cuongle


Możesz rzucić okiem na moją odpowiedź na temat wyjątku „Wykryto pętlę odniesienia do siebie” na stronie JSON.Net .
— Murat Yıldız,

Odpowiedzi:


73

Czy to dlatego, że pytanie ma odpowiedzi, a odpowiedzi mają odniesienie z powrotem do pytania?

Tak. Nie można go serializować.

EDYCJA: Zobacz odpowiedź Tallmarisa i komentarz OttO, ponieważ jest prostszy i można go ustawić globalnie.

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

Stara odpowiedź:

Projektuj obiekt EF Questionna swój własny obiekt pośredni lub DataTransferObject. To Dto można następnie pomyślnie serializować.

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

Coś jak:

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
Dodam, że dla mnie ustawienie ReferenceLoopHandling.Ignore nie działało, ustawienie go globaly lub na starcie API w ogóle nie działało. Udało mi się go uruchomić, dekorując właściwość nawigacji klasy podrzędnej za pomocą [JsonIgnore]. Nadal otrzymuję identyfikator ParentId, ale nawigacja nadrzędna jest ignorowana podczas serializacji.
— Claiton Lovato

Witaj, zignorowanie serializacji spowoduje przerwanie zależności cyklicznej Pytanie> Odpowiedź> Pytanie. Czy podejście DTO to zachowuje?
— Bartosz

Mam ten problem w starym projekcie ASP.NET MVC. GlobalConfiguration.Configuration nie ma elementów formatujących. Czy możesz zasugerować, co można w tym celu zrobić?
— Ren

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Re‌ ferenceLoopHandling = ReferenceLoopHandling.Ignore; -> Gdzie umieścić tę linię kodu ???
— anhtv13

56

Możesz również spróbować tego w Application_Start():

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

Powinien rozwiązać problem bez przechodzenia przez wiele obręczy.


EDYCJA: Zgodnie z komentarzem OttO poniżej, użyj: ReferenceLoopHandling.Ignorezamiast.

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

78
Wiem, że to stary wątek, ale dla tych, którzy natkną się na niego w przyszłości, spróbuj: GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
— OttO

@OttO, twoje sugestie zadziałały dla mnie. Dziękuję bardzo.
— J86,

2
Kod przechodzi do nieskończonej pętli i po dodaniu tego wiersza wyświetla wyjątek przepełnienia stosu.
— Microsoft Developer

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; działa lepiej
— Dragos Durlut

@Demodave musisz utworzyć JsonSerializer przy użyciu Create()metody statycznej , która akceptuje ustawienie jako parametr. Dokumenty: newtonsoft.com/json/help/html/…
— Tallmaris

21

Jeśli korzystasz z OWIN, pamiętaj, nigdy więcej GlobalSettings dla Ciebie! Musisz zmodyfikować to samo ustawienie w obiekcie HttpConfiguration, który jest przekazywany do funkcji UseWebApi IAppBuilder (lub dowolnej platformy usługowej, na której jesteś)

Wyglądałoby to mniej więcej tak.

    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
Uratowałeś mój dzień. Zastanawiałem się, dlaczego powyższa odpowiedź nie działa. Tak, użycie ustawienia OWIN w Global.asax nie będzie działać.
— Sithu

21

W ASP.NET Core poprawka jest następująca:

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

5

Jeśli używasz DNX / MVC 6 / ASP.NET vNext bla bla, HttpConfigurationbrakuje nawet . Musisz skonfigurować elementy formatujące, używając następujących kodów w Startup.cspliku.

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
W asp-net rc-1-final uważam, że to jest "services.Configure <MvcOptions>" teraz
— Michał W.

JsonOutputFormatter znajduje się w przestrzeni nazw Microsoft.AspNet.Mvc.Formatters
— Sam

2
NET Core 1.0 RTM: nowy 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

Używając tego:

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

nie działa dla mnie. Zamiast tego utworzyłem nową, uproszczoną wersję mojej klasy modelu tylko do testowania i to wróciło dobrze. Ten artykuł dotyczy niektórych problemów, które miałem w moim modelu, które działały świetnie dla EF, ale nie można ich serializować:

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


1

ReferenceLoopHandling.Ignore nie działa dla mnie. Jedynym sposobem, w jaki mogłem to obejść, było usunięcie za pomocą kodu linków z powrotem do rodzica, którego nie chciałem, i zachowanie tych, które zrobiłem.

parent.Child.Parent = null;

1

W przypadku nowej aplikacji sieci Web Asp.Net korzystającej z .Net Framework 4.5:

Web Api: Idź do App_Start -> WebApiConfig.cs:

Powinien wyglądać mniej więcej tak:

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

W ramach ASP.NET Core 3,0 zespół odszedł od domyślnego uwzględniania Json.NET. Możesz przeczytać więcej na ten temat ogólnie w [Includes Json.Net to netcore 3.x] [1] https://github.com/aspnet/Announcements/issues/325

Błąd mógł być spowodowany przez użycie lazyloading: services.AddDbContext (options => options.UseLazyLoadingProxies () ... lub db.Configuration.LazyLoadingEnabled = true;

poprawka: dodaj do startup.cs

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

0

Z powodu leniwego ładowania otrzymujesz ten błąd. Stąd moja sugestia to usunięcie klucza wirtualnego z właściwości. Jeśli pracujesz z interfejsem API, ładowanie z opóźnieniem nie jest dobre dla stanu interfejsu API.

Nie ma potrzeby dodawania dodatkowej linii w pliku konfiguracyjnym.

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

Zauważyłem, że ten błąd był spowodowany, gdy wygenerowałem edmx (plik XML, który definiuje model koncepcyjny) istniejącej bazy danych i miał on właściwości nawigacji zarówno dla tabel nadrzędnych, jak i podrzędnych. Usunąłem wszystkie łącza nawigacyjne do obiektów nadrzędnych, ponieważ chciałem tylko nawigować do dzieci, a problem został rozwiązany.


0

Jednostki db = nowe jednostki ()

db.Configuration.ProxyCreationEnabled = false;

db.Configuration.LazyLoadingEnabled = false;


0

Możesz dynamicznie utworzyć nową kolekcję podrzędną, aby łatwo obejść ten problem.

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

Żadna z konfiguracji w powyższych odpowiedziach nie działała dla mnie w ASP.NET Core 2.2.

Musiałem dodać JsonIgnoreatrybuty do moich właściwości nawigacji wirtualnej.

public class Question
{
    public int QuestionId { get; set; }
    public string Title { get; set; }
    [JsonIgnore]
    public virtual ICollection<Answer> Answers { get; set; }
}
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.