Виявлено цикл самостійного посилання - повернення даних з WebApi до браузера


80

Я використовую 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; 
    }

На стороні C # це, здається, працює, однак я помічаю, що об'єкти відповіді мають посилання на запитання. Коли я використовую WebAPI для отримання даних до браузера, я отримую таке повідомлення:

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

Виявлено цикл самостійного посилання для властивості 'question' із типом 'Models.Core.Question'.

Це тому, що Питання має відповіді, а відповіді мають посилання на Питання? Всі місця, які я переглядав, пропонують мати посилання на батьків у дитини, тому я не знаю, що робити. Хтось може дати мені якусь пораду з цього приводу.


6
Використовуйте Dto для веб-API, уникаючи повернення Entity безпосередньо у відповідь
cuongle

Що таке Dto? Весь наш додаток використовує EF, ми використовуємо AngularJS на клієнті, і ми не маємо жодних проблем, крім цього випадку.

1
Що я мав на увазі, що ви повинні визначити свій Dto для вашого веб-Api, Dto є подібним до ViewModel у MVC. Dto - це як обгортка вашої моделі EF для надання даних вашому клієнту (angularjs).
cuongle


Можливо, ви подивитесь на мою відповідь у винятку “Виявлено петлю самостійного посилання” на сторінці JSON.Net .
Мурат Йылдиз,

Відповіді:


73

Це тому, що Питання має відповіді, а відповіді мають посилання на Питання?

Так. Це не можна серіалізувати.

EDIT: Дивіться відповідь Талмаріса та коментар 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; 
}

3
Я хотів би додати, що для мене налаштування ReferenceLoopHandling.Ignore не спрацювало, встановивши його глобально або при запуску API взагалі не працювало. Мені вдалося змусити його працювати, прикрасивши властивість навігації дочірнього класу за допомогою [JsonIgnore]. Я все ще отримую ParentId, але навігація батьків ігнорується під час серіалізації.
Клейтон Ловато,

Привіт! Ігнорування серіалізації призведе до порушення циркулярної залежності Питання> Відповідь> Питання. Чи зберігає його підхід DTO?
Бартош,

У мене ця проблема у старому проекті ASP.NET MVC. GlobalConfiguration.Configuration не має форматування. Чи можете ви підказати, що для цього можна зробити?
Рен

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Re‌ ferenceLoopHandling = ReferenceLoopHandling.Ignore; -> Куди подіти цей рядок коду ???
anhtv13

56

Ви також можете спробувати це у своєму Application_Start():

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

Це повинно вирішити вашу проблему, не переживаючи багато обручів.


EDIT: Відповідно до коментаря OttO нижче, використовуйте: ReferenceLoopHandling.Ignoreзамість.

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

78
Я знаю, що це стара тема, але для тих, хто натрапляє на неї в майбутньому, спробуйте: GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
OttO

@OttO, ваші пропозиції спрацювали для мене. Дуже дякую.
J86

2
Після додавання цього рядка код переходить у нескінченний цикл і показує виняток із переповнення стека.
розробник Microsoft

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; працює краще
Драгош Дурлут

@Demodave вам потрібно створити свій JsonSerializer, використовуючи статичний Create()метод, який приймає параметр як параметр. Документи: newtonsoft.com/json/help/html/…
Талмаріс,

21

Якщо ви використовуєте 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);
}

1
Ти врятував мій день. Мені було цікаво, чому відповідь вище не працює. Так, використання налаштування OWIN у Global.asax не буде працювати.
Сітху

21

В ASP.NET Core виправлення виглядає так:

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

5

Якщо використовується DNX / MVC 6 / ASP.NET vNext бла-бла, навіть 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);
        });
    }

1
В asp-net rc-1-final я вважаю, що це зараз "послуги. Налаштувати <MvcOptions>"
Міхал В.

JsonOutputFormatter знаходиться у просторі імен Microsoft.AspNet.Mvc.Formatters
Сем

2
Для .NET Core 1.0 RTM: новий JsonOutputFormatter (serializerSettings, ArrayPool <char> .Shared);
aherrick

5

Веб-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;
    });
}

2

Використовуючи це:

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


1

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

parent.Child.Parent = null;

1

Для нової веб-програми Asp.Net із використанням .Net Framework 4.5:

Web Api: Перейти до 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 }
        );
    }
}

1

Як частина ASP.NET Core 3.0, команда відмовилася від включення Json.NET за замовчуванням. Детальніше про це ви можете прочитати у [Включаючи Json.Net до netcore 3.x] [1] https://github.com/aspnet/Announcements/issues/325

Помилка може бути спричинена вашим використанням lazyloading: services.AddDbContext (options => options.UseLazyLoadingProxies () ... або db.Configuration.LazyLoadingEnabled = true;

виправлення: додати до startup.cs

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

0

Через ліниве завантаження ви отримуєте цю помилку. Звідси моя пропозиція - видалити віртуальний ключ із властивості. Якщо ви працюєте з 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; }
}

0

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


0

Суб'єкти db = нові сутності ()

db.Configuration.ProxyCreationEnabled = false;

db.Configuration.LazyLoadingEnabled = false;


0

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

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

Жодна з конфігурацій у відповідях вище не працювала для мене в ASP.NET Core 2.2.

У мене були JsonIgnoreатрибути add на моїх властивостях віртуальної навігації.

public class Question
{
    public int QuestionId { get; set; }
    public string Title { get; set; }
    [JsonIgnore]
    public virtual ICollection<Answer> Answers { get; set; }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.