检测到自引用循环-将数据从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'。

为类型为“ Models.Core.Question”的属性“ question”检测到自引用循环。

这是因为问题有答案,而答案有对问题的引用吗?我看过的所有地方都建议在孩子中提及父母,因此我不确定该怎么做。有人可以给我一些建议吗?


6
将Dto用于您的网络api,避免直接在您的响应中返回Entity
cuongle 2013年

什么是Dto?我们的整个应用程序都使用EF,我们在客户端上使用AngularJS,除了这种情况下,我们没有其他问题。

1
我的意思是您应该为Web Api定义Dto,Dto与MVC中的ViewModel相似。Dto就像EF模型的包装器一样,可以为您的客户端(angularjs)提供数据。
cuongle 2013年


Answers:


73

这是因为问题有答案,而答案有对问题的引用吗?

是。无法序列化。

编辑:请参阅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; 
}

3
我想补充一点,对我来说,设置ReferenceLoopHandling.Ignore不起作用,将其全局设置或在API启动时根本不起作用。我设法通过使用[JsonIgnore]装饰子类的导航属性来使其工作。我仍然获得ParentId,但是序列化时忽略了Parent导航。
克莱顿·洛瓦托

您好,忽略序列化将破坏循环依赖性Question> Answer> Question。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;

它应该解决您的问题而无需经历很多麻烦。


编辑:根据下面OttO的评论,请使用:ReferenceLoopHandling.Ignore代替。

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

78
我知道这是一个旧线程,但是对于那些将来遇到它的人,请尝试:GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
OttO 2014年

@OttO,您的建议对我有用。非常感谢。
J86

2
添加此行后,代码将进入无限循环并显示堆栈溢出异常。
Microsoft Developer

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; 工作得更好
Dragos Durlut '16

@Demodave,您必须使用Create()接受设置作为参数的静态方法来创建JsonSerializer 。文件:newtonsoft.com/json/help/html/…–
Tallmaris

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
你救了我的日子。我想知道为什么上面的答案不起作用。是的,无法在Global.asax中使用OWIN设置。
锡胡

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网RC-1决赛,我相信它是“services.Configure <MvcOptions>”现在
米哈尔W.

JsonOutputFormatter位于Microsoft.AspNet.Mvc.Formatters命名空间中
Sam

2
对于.NET Core 1.0 RTM:新的JsonOutputFormatter(serializerSettings,ArrayPool <char> .Shared);
aherrick '16

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


1

ReferenceLoopHandling.Ignore对我不起作用。我唯一可以解决的方法是通过代码删除指向我不想要的父级的链接,并保留我想要的父级。

parent.Child.Parent = null;

1

对于使用.Net Framework 4.5的新Asp.Net Web应用程序:

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

您使用延迟加载可能导致错误: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文件)并且它对父表和子表都具有Navigation属性时,我发现了此错误。我删除了所有到父对象的导航链接,因为我只想导航到子对象,因此问题得以解决。


0

实体db =新的Entities()

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在虚拟导航属性上添加了属性。

public class Question
{
    public int QuestionId { get; set; }
    public string Title { get; set; }
    [JsonIgnore]
    public virtual ICollection<Answer> Answers { get; set; }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.