检测到自引用循环 - 从 WebApi 取回数据到浏览器

Posted

技术标签:

【中文标题】检测到自引用循环 - 从 WebApi 取回数据到浏览器【英文标题】:Self referencing loop detected - Getting back data from WebApi to the browser 【发布时间】:2013-06-23 05:05:31 【问题描述】:

我正在使用实体框架,但在将父子数据传输到浏览器时遇到问题。这是我的课程:

 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”的属性“问题”的自引用循环。

这是因为问题有答案并且答案有对问题的引用吗?我看过的所有地方都建议在孩子身上引用父母,所以我不知道该怎么做。有人可以给我一些建议吗?

【问题讨论】:

为你的 web api 使用 Dto,避免在你的响应中直接返回实体 什么是 Dto?我们的整个应用程序使用 EF,我们在客户端使用 AngularJS,除了这种情况,我们没有任何问题。 我的意思是你应该为你的 Web Api 定义你的 Dto,Dto 有点类似于 MVC 中的 ViewModel。 Dto 就像您的 EF 模型的包装器,为您的客户端 (angularjs) 提供数据。 JSON.NET Error Self referencing loop detected for type的可能重复 你可以看看我在 “Self Referencing Loop Detected” exception with JSON.Net 页面上的回答。 【参考方案1】:

这是因为问题有答案,而答案有 参考回问题?

是的。无法序列化。

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

【讨论】:

我想补充一点,对我来说,设置 ReferenceLoopHandling.Ignore 不起作用,全局设置或在 API 启动时根本不起作用。我设法通过使用 [JsonIgnore] 装饰子类的导航属性来使其工作。我仍然得到 ParentId,但在序列化时忽略了 Parent 导航。 您好,忽略序列化会破坏循环依赖 Question>Answer>Question。 DTO 方法是否保留它? 我在一个旧的 ASP.NET MVC 项目中遇到了这个问题。 GlobalConfiguration.Configuration 没有格式化程序。您能建议为此做些什么吗? GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Re‌​ferenceLoopHandling = ReferenceLoopHandling.Ignore; -> 这行代码放在哪里???【参考方案2】:

你也可以在你的Application_Start()试试这个:

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

它应该可以解决您的问题,而无需费力。


编辑: 根据 OttO 下面的评论,请改用:ReferenceLoopHandling.Ignore
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;

【讨论】:

我知道这是一个旧线程,但对于那些将来偶然发现它的人,请尝试: GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; @OttO,您的建议对我有用。非常感谢。 添加此行后代码进入无限循环并显示堆栈溢出异常。 GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;效果更好 @Demodave 您必须使用接受设置作为参数的静态 Create() 方法创建您的 JsonSerializer。文档:newtonsoft.com/json/help/html/…【参考方案3】:

如果使用 OWIN,请记住,您不再需要 GlobalSettings!您必须在传递给 IAppBuilder UseWebApi 函数(或您所在的任何服务平台)的 HttpConfiguration 对象中修改相同的设置

看起来像这样。

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

【讨论】:

你拯救了我的一天。我想知道为什么上面的答案不起作用。是的,在 Global.asax 中使用 OWIN 设置将不起作用。【参考方案4】:

在 ASP.NET Core 中,修复如下:

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

【讨论】:

【参考方案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;
    );

【讨论】:

【参考方案6】:

如果使用 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);
        );
    

【讨论】:

在 asp-net rc-1-final 我相信它现在是“services.Configure JsonOutputFormatter 位于命名空间 Microsoft.AspNet.Mvc.Formatters 对于 .NET Core 1.0 RTM:new JsonOutputFormatter(serializerSettings, ArrayPool.Shared);【参考方案7】:

作为 ASP.NET Core 3.0 的一部分,团队不再默认包含 Json.NET。您可以在 [Including Json.Net to 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;
        );

【讨论】:

这正是我所需要的——谢谢!【参考方案8】:

使用这个:

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

【讨论】:

【参考方案9】:

ReferenceLoopHandling.Ignore 对我不起作用。我可以绕过它的唯一方法是通过代码删除返回到我不想要的父级的链接并保留我做的那些。

parent.Child.Parent = null;

【讨论】:

【参考方案10】:

对于使用 .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 
        );
    

【讨论】:

【参考方案11】:

由于延迟加载,您会收到此错误。因此我的建议是从财产中删除虚拟钥匙。如果您使用 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; 

【讨论】:

【参考方案12】:

我发现当我生成现有数据库的 edmx(定义概念模型的 XML 文件)并且它具有父表和子表的导航属性时,会导致此错误。我删除了所有指向父对象的导航链接,因为我只想导航到子对象,问题就解决了。

【讨论】:

【参考方案13】:

实体 db = 新实体()

db.Configuration.ProxyCreationEnabled = false;

db.Configuration.LazyLoadingEnabled = false;

【讨论】:

【参考方案14】:

您可以动态创建新的子集合以轻松解决此问题。

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; 
    

【讨论】:

【参考方案15】:

以上答案中的配置在 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; 

【讨论】:

以上是关于检测到自引用循环 - 从 WebApi 取回数据到浏览器的主要内容,如果未能解决你的问题,请参考以下文章

从 sql server 读取数据时检测到自引用循环

在 ASP.NET Core 中检测到自引用循环 [重复]

检测到实体框架自引用循环[重复]

检测到实体框架自引用循环

EF中Json序列化对象时检测到循环引用的解决办法

序列化 System.Globalization.CultureInfo 类型的对象时检测到循环引用