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

时间:2017-07-26 14:13:27

标签: c# asp.net-web-api postman

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

// POST api/Products
        [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; }
    }

enter image description here

我使用谷歌浏览器扩展&#39; POSTMAN&#39;测试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
            }
        ]
}

它显示以下错误 -

  

&#34;消息&#34;:&#34;发生了错误。&#34;,&#34; ExceptionMessage&#34;:&#34;   &#39; ObjectContent`1&#39;类型无法序列化响应正文   内容类型&#39; application / json;   字符集= UTF-8&#39;&#34;&#34; ExceptionType&#34;:&#34; System.InvalidOperationException&#34;&#34;堆栈跟踪&#34;:空,&#34;的InnerException& #34; {&#34;消息&#34;:&#34;安   错误已经发生。&#34;,&#34; ExceptionMessage&#34;:&#34;自我引用循环   检测到属性&#39;产品&#39;与类型   &#39; HelloWebAPI.Models.Product&#39;

如何解决此错误?

2 个答案:

答案 0 :(得分:6)

首先,将导航属性更改为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的ReferenceLoopHandling.Ignore中的JsonSerializer设置执行此操作。对于ASP.NET Web API,可以进行全局设置(取自this SO thread),

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

当Json.NET尝试序列化一个已经被序列化的对象(你的对象循环!)时,这个错误来自Json.NET,而且文档也很清楚,

  

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

http://www.newtonsoft.com/json/help/html/SerializationSettings.htm

中捕获的参考

答案 1 :(得分:3)

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

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

删除循环引用属性

正如斯图尔特所说:

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)混合,通常会给那些仍然不知道序列化发生的人带来问题。

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