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

Log*_*esk 2 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);
        }
Run Code Online (Sandbox Code Playgroud)

产品类别 -

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; }
    }
Run Code Online (Sandbox Code Playgroud)

复习课程 -

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; }
    }
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

我正在使用谷歌浏览器扩展程序'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
            }
        ]
}
Run Code Online (Sandbox Code Playgroud)

它显示以下错误 -

"消息":"发生了错误.","ExceptionMessage":"'ObjectContent`1'类型无法序列化内容类型'application/json; charset = utf-8'.","ExceptionType"的响应正文:"System.InvalidOperationException","StackTrace":null,"InnerException":{"消息":"发生错误.","ExceptionMessage":"为类型为'HelloWebAPI.Models'的属性'Product'检测到自引用循环.产品'.

如何解决此错误?

Ali*_*son 8

避免使用您在实体框架中使用的相同类来映射 API 方法中的实体。创建 DTO 类以与 API 一起使用,然后手动将它们转换为您的实体类,或使用 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; }
}
Run Code Online (Sandbox Code Playgroud)

装饰循环引用属性 [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; }
}
Run Code Online (Sandbox Code Playgroud)

关于在(反)序列化时忽略属性,您可以参考这个问题/答案


使用 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; }
}
Run Code Online (Sandbox Code Playgroud)

然后像这样修复你的控制器动作:

[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);
}
Run Code Online (Sandbox Code Playgroud)

我知道它看起来多余,但这是因为我正在手动完成所有操作。如果我使用 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);
}
Run Code Online (Sandbox Code Playgroud)

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

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


Afz*_*han 6

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

public virtual ICollection<Review> Reviews { get; set; }

// In the review, make some changes as well
public virtual Product Product { get; set; }
Run Code Online (Sandbox Code Playgroud)

其次,既然你知道a Product不会总是在集合中有一个评论,你不能把它设置为可空吗? - 只是说.

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

GlobalConfiguration.Configuration.Formatters
                   .JsonFormatter.SerializerSettings.Re??ferenceLoopHandling 
                   = ReferenceLoopHandling.Ignore;
Run Code Online (Sandbox Code Playgroud)

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

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

参考文献来自http://www.newtonsoft.com/json/help/html/SerializationSettings.htm