[英]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.