检测到自引用循环 - 从Web Api返回数据到浏览器

时间:2013-06-26 07:06:34

标签: c# entity-framework serialization asp.net-web-api json.net

我正在使用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的响应主体;字符集= UTF-8' 。

     

使用类型'Models.Core.Question'检测属性'question'的自引用循环。

这是因为问题有答案而且答案有回复问题的参考?我看过的所有地方都建议引用孩子的父母,所以我不知道该怎么办。有人可以给我一些建议。

15 个答案:

答案 0 :(得分:68)

  

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

是。它无法序列化。

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

答案 1 :(得分:54)

您也可以在Application_Start()

中尝试此操作
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;

它应该解决你的问题而不需要经历很多箍。

<小时/> 编辑:根据OttO的评论,请使用:ReferenceLoopHandling.Ignore代替。

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

答案 2 :(得分:22)

In ASP.NET Core the fix is as follows:

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

答案 3 :(得分: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);
}

答案 4 :(得分:5)

如果使用DNX / MVC 6 / ASP.NET vNext blah blah,即使 $scope.list = [{isVisible:true,value:"sample"},{isVisible:false,value:"sample"},{isVisible:true,value:"sample"}] <li ng-repeat="tag in list" ng-show="tag.isVisible"> <span>{{tag.value}}</span> </li> 也丢失了。您必须使用HttpConfiguration文件中的以下代码配置格式化程序。

Startup.cs

答案 5 :(得分: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 :(得分: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

答案 7 :(得分:1)

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

parent.Child.Parent = null;

答案 8 :(得分:1)

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

Web Api:转到App_Start - &gt; 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 }
        );
    }
}

答案 9 :(得分: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;
        });

答案 10 :(得分: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; }
}

答案 11 :(得分:0)

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

答案 12 :(得分:0)

实体db = new Entities()

db.Configuration.ProxyCreationEnabled = false;

db.Configuration.LazyLoadingEnabled = false;

答案 13 :(得分: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; 
    }

答案 14 :(得分:0)

以上答案中的所有配置都不适用于ASP.NET Core 2.2。

我在虚拟导航属性上添加了errno属性。

JsonIgnore