[英]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.ReferenceLoopHandling
= ReferenceLoopHandling.Ignore;
當 Json.NET 嘗試序列化一個已經被序列化的對象(你的對象循環!)時,這個錯誤來自於 Json.NET,並且文檔也非常清楚地說明了這一點,
Json.NET 將忽略引用循環中的對象並且不會序列化它們。 第一次遇到對象時,它會像往常一樣被序列化,但如果該對象是作為其自身的子對象遇到的,則序列化程序將跳過對其進行序列化。
從http://www.newtonsoft.com/json/help/html/SerializationSettings.htm捕獲的參考
避免使用您在實體框架中使用的相同類來映射 API 方法中的實體。 創建 DTO 類以與 API 一起使用,然后手動將它們轉換為您的實體類,或使用 Auto Mapper 之類的工具來幫助您執行此操作。
無論如何,如果你仍然想使用你的Product
和Review
類,我能記住的兩個最簡單的選項是:
正如斯圖爾特所確定的:
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; }
}
關於在(反)序列化時忽略屬性,您可以參考這個問題/答案。
您創建兩個新類:
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.