Обнаружен цикл саморегуляции - Возврат данных из WebApi в браузер

Я использую Entity Framework и имею проблему с получением родительских и дочерних данных в браузере. Вот мои классы:

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

Я использую следующий код для возврата данных вопросов и ответов:

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

На стороне С# это, похоже, работает, однако я замечаю, что объекты ответа ссылаются на вопрос. Когда я использую WebAPI для получения данных в браузере, я получаю следующее сообщение:

Тип ObjectContent`1 не смог сериализовать тело ответа для типа контента 'application/json; кодировка = UTF-8'.

Ядро референтных ссылок обнаружено для свойства "вопрос" с типом "Models.Core.Question".

Это потому, что у Вопроса есть ответы и ответы есть ссылка на Вопрос? Все места, которые я искал, предлагают иметь ссылку на родителя в ребенке, поэтому я не уверен, что делать. Может кто-нибудь дать мне несколько советов по этому поводу.

Ответ 1

Это потому, что у Вопроса есть ответы и ответы ссылаться на вопрос?

Да. Он не может быть сериализован.

EDIT: см. комментарий Tallmaris и комментарий OttO, поскольку он проще и может быть установлен глобально.

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

Старый ответ:

Проецируйте объект EF Question на свой собственный промежуточный или DataTransferObject. Это Dto может быть успешно сериализовано.

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

Что-то вроде:

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

Ответ 2

Вы также можете попробовать это в вашем Application_Start():

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

Это должно исправить вашу проблему, не проходя через много обручей.


РЕДАКТИРОВАТЬ: В соответствии с комментарием OttO ниже, используйте: ReferenceLoopHandling.Ignore вместо.
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;

Ответ 3

В ядре ASP.NET исправление выглядит следующим образом:

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

Ответ 4

Если вы используете OWIN, помните, больше не GlobalSettings для вас! Вы должны изменить этот же параметр в объекте HttpConfiguration, который передается функции IAppBuilder UseWebApi (или любой другой платформы обслуживания, на которой вы находитесь)

Будет выглядеть примерно так.

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

Ответ 5

Если используется DNX/MVC 6/ASP.NET vNext blah blah, даже HttpConfiguration отсутствует. Вы должны конфигурировать форматировщики, используя следующие коды в вашем файле Startup.cs.

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

Ответ 6

Веб-API ASP.NET Core (.NET Core 2.0):

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

Ответ 7

Используя это:

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

не работал у меня. Вместо этого я создал новую, упрощенную версию моего модельного класса, просто чтобы проверить, и это вернулось. Эта статья затрагивает некоторые проблемы, которые я испытывал в своей модели, которая отлично поработала для EF, но не была сериализована:

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

Ответ 8

ReferenceLoopHandling.Ignore не работает для меня. Единственный способ, которым я мог обойти это, - удалить с помощью кода ссылки обратно родителям, которых я не хотел, и сохранить те, которые я сделал.

parent.Child.Parent = null;

Ответ 9

Из-за ленивой загрузки вы получаете эту ошибку. Поэтому мое предложение состоит в том, чтобы удалить виртуальный ключ из свойства. Если вы работаете с API, тогда ленивая загрузка не подходит для вашего здоровья API.

Не нужно добавлять дополнительную строку в свой файл конфигурации.

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

Ответ 10

Я обнаружил, что эта ошибка возникает, когда я создал edmx (XML файл, который определяет концептуальную модель) существующей базы данных, и имел свойства навигации как для родительской, так и для дочерних таблиц. Я удалил все ссылки навигации к родительским объектам, так как я только хотел перейти к детям, и проблема была решена.

Ответ 11

Объекты db = новые объекты()

db.Configuration.ProxyCreationEnabled = false;

db.Configuration.LazyLoadingEnabled = false;

Ответ 12

Для нового веб-приложения Asp.Net с использованием .Net Framework 4.5:

Web Api: Goto App_Start → WebApiConfig.cs:

Должен выглядеть примерно так:

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

Ответ 13

Вы можете динамически создавать новую дочернюю коллекцию, чтобы легко обойти эту проблему.

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

Ответ 14

Ни одна из конфигураций в ответах выше не работала для меня в ASP.NET Core 2.2.

У меня было добавление атрибутов JsonIgnore в моих свойствах виртуальной навигации.

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