繁体   English   中英

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

[英]Self referencing loop detected for property in WebApi 2

我创建了一个 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; }
    }

在此处输入图片说明

我正在使用谷歌浏览器扩展“邮递员”来测试 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":"发生错误。","ExceptionMessage":"'ObjectContent`1' 类型未能序列化内容类型 'application/json; charset=utf-8' 的响应正文。","ExceptionType" :"System.InvalidOperationException","StackTrace":null,"InnerException":{"Message":"发生错误。","ExceptionMessage":"检测到属性 'Product' 的自引用循环,类型为 'HelloWebAPI.Models .产品'。

如何解决此错误?

首先,将 Navigation 属性更改为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,可以进行全局设置(取自这个 SO 线程),

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

当 Json.NET 尝试序列化一个已经被序列化的对象(你的对象循环!)时,这个错误来自于 Json.NET,并且文档也非常清楚地说明了这一点,

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

http://www.newtonsoft.com/json/help/html/SerializationSettings.htm捕获的参考

避免使用您在实体框架中使用的相同类来映射 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; }
}

[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; }
}

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


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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM