为 WebApi 2 中的属性检测到自引用循环

Posted

技术标签:

【中文标题】为 WebApi 2 中的属性检测到自引用循环【英文标题】:Self referencing loop detected for property in WebApi 2 【发布时间】:2018-01-01 22:15:24 【问题描述】:

我创建了一个 Web Api 来将新产品和评论保存在数据库中。下面是WebApi代码:

POST api/产品

[ResponseType(typeof(Product))]
public IHttpActionResult PostProduct(Product product)

    if (!ModelState.IsValid)
    
        return BadRequest(ModelState);
    

    db.Products.Add(product);
    db.SaveChanges();

    return CreatedAtRoute("DefaultApi", new  id = product.ProductId , product);

产品类别

public class Product

    public int ProductId  get; set; 
    [Required]
    public string Name  get; set; 
    public string Category  get; set; 
    public int Price  get; set; 
    //Navigation Property
    public ICollection<Review> Reviews  get; set; 

复习课

public class Review

    public int ReviewId  get; set; 
    public int ProductId  get; set; 
    [Required]
    public string Title  get; set; 
    public string Description  get; set; 
    //Navigation Property
    public Product Product  get; set; 

我正在使用 google chrome 扩展程序“POSTMAN”来测试 api。当我尝试通过在 POSTMAN 中创建 POST 请求来保存详细信息时:


    "Name": "Product 4",
        "Category": "Category 4",
        "Price": 200,
        "Reviews": [
            
                "ReviewId": 1,
                "ProductId": 1,
                "Title": "Review 1",
                "Description": "Test review 1",
                "Product": null
            ,
            
                "ReviewId": 2,
                "ProductId": 1,
                "Title": "Review 2",
                "Description": "Test review 2",
                "Product": null
            
        ]

显示以下错误:

"Message":"An error has occurred.",
"ExceptionMessage":"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'.",
"ExceptionType":"System.InvalidOperationException",
"StackTrace":null,
"InnerException":
    "Message":"An error has occurred.",
    "ExceptionMessage":"Self referencing loop detected for property 'Product' with type 'HelloWebAPI.Models.Product'.

我该如何解决这个错误?

【问题讨论】:

一个产品包含评论的集合,而评论包含产品... JSON.NET Error Self referencing loop detected for type的可能重复 Entity framework self referencing loop detected的可能重复 你可以看看我在 “Self Referencing Loop Detected” exception with JSON.Net 页面上的回答。 【参考方案1】:

首先,将导航属性更改为virtual,这将提供延迟加载,

public virtual ICollection<Review> Reviews  get; set; 

// In the review, make some changes as well
public virtual Product Product  get; set; 

其次,既然您知道Product 不会在集合中始终有评论,您不能将其设置为可空吗? ——只是说。

现在回到你的问题,处理这个问题的一个非常简单的方法是忽略无法序列化的对象......再次!使用 Json.NET 的 JsonSerializer 中的 ReferenceLoopHandling.Ignore 设置来执行此操作。对于 ASP.NET Web API,可以进行全局设置(取自this SO thread),

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

这个错误来自 Json.NET,它试图序列化一个已经序列化的对象(你的对象循环!),文档也很清楚地说明了这一点,

Json.NET 将忽略引用循环中的对象并且不序列化它们。第一次遇到一个对象时,它将像往常一样被序列化,但如果遇到该对象作为其自身的子对象,则序列化程序将跳过对其进行序列化。

引用来自 http://www.newtonsoft.com/json/help/html/SerializationSettings.htm

【讨论】:

当我将评论和产品虚拟化时抛出异常。请建议 @LogicalDesk,你能告诉异常消息吗?此外,virtual 的建议不是您需要在启动时设置SerializerSettings 以便序列化程序收集它的例外情况—如果这是您遇到的错误。 '【参考方案2】:

避免使用您在 Entity Framework 中使用的相同类在 API 方法中映射您的实体。创建 DTO 类以与 API 一起使用,然后手动将它们转换为您的实体类或使用 Auto Mapper 等工具来帮助您执行此操作。

无论如何,如果您仍想使用 ProductReview 类,我能记住的两个最简单的选项是:

移除循环引用属性

由 Stuart 确定:

public class Review

    public int ReviewId  get; set; 
    public int ProductId  get; set; 
      [Required]
    public string Title  get; set; 
    public string Description  get; set; 

[IgnoreDataMember]装饰循环引用属性

public class Review

    public int ReviewId  get; set; 
    public int ProductId  get; set; 
      [Required]
    public string Title  get; set; 
    public string Description  get; set; 

    //Navigation Property
    [IgnoreDataMember]
    public Product Product  get; set; 

关于(反)序列化时忽略属性,可以参考this question/answer。


使用 DTO 类

您创建了两个新类:

public class CreateProductRequest

    [Required]
    public string Name  get; set; 
    public string Category  get; set; 
    public int Price  get; set; 
    //Navigation Property
    public IEnumerable<CreateReviewRequest> Reviews  get; set; 


public class CreateReviewRequest

    [Required]
    public string Title  get; set; 
    public string Description  get; set; 

然后像这样修复您的控制器操作:

[ResponseType(typeof(Product))]
public IHttpActionResult PostProduct(CreateProductRequest request)

    if (!ModelState.IsValid)
    
        return BadRequest(ModelState);
    
    var product = new Product
    
        Name = request.Name,
        Category = request.Category,
        Price = request.Price
    
    if (request.Reviews != null)
        product.Reviews = request.Reviews.Select(r => new Review
        
            Title = r.Title,
            Description = r.Description
        );

    db.Products.Add(product);
    db.SaveChanges();

    return CreatedAtRoute("DefaultApi", new  id = product.ProductId , product);

我知道这看起来很多余,但这是因为我正在手动完成所有操作。如果我使用的是 Auto Mapper 之类的东西,我们可以将其简化为:

[ResponseType(typeof(Product))]
public IHttpActionResult PostProduct(CreateProductRequest request)

    if (!ModelState.IsValid)
    
        return BadRequest(ModelState);
    
    var product = AutoMapper.Map<Product>(request);

    db.Products.Add(product);
    db.SaveChanges();

    return CreatedAtRoute("DefaultApi", new  id = product.ProductId , product);

将实体框架(或任何其他 ORM)类与服务层类(例如 Web API、MVC、WCF)混合通常会给仍然不知道序列化如何发生的人带来问题。

在某些情况下,对于简单的场景,我确实直接使用类,因为这不是应该严格遵循的规则。我相信每种情况都有自己的需求。

【讨论】:

你能告诉我如何在这种情况下使用 DTO 吗? @LogicalDesk 我添加了代码示例和一些解释。随意问任何你想要的。

以上是关于为 WebApi 2 中的属性检测到自引用循环的主要内容,如果未能解决你的问题,请参考以下文章

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

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

如何停止 .Net Core Web API 中的自引用循环?

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

Sitecore 7.5 的 MVC 检测和 WebApi 属性路由

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