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.

Społeczność
źródło
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; 
}
Sam Leach
źródło
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;
Tallmaris
źródło
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);
}
Bon
źródło
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);
Mohsen Afshin
źródło
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);
        });
    }
Stewart Hou
źródło
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;
    });
}
René Schindhelm
źródło
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

Mikrofon
źródło
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;
Rob Sedgwick
źródło
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 }
        );
    }
}
Ogglas
źródło
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;
        });
Łajno
źródło
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; }
}
PAWAN RAJ Shakya
źródło
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.

Zymotik
źródło
0

Jednostki db = nowe jednostki ()

db.Configuration.ProxyCreationEnabled = false;

db.Configuration.LazyLoadingEnabled = false;

Imran
źródło
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; 
    }
spadelives
źródło
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; }
}
chakeda
źródło