簡體   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