为 WebApi 2 中的属性检测到自引用循环
Posted
技术标签:
【中文标题】为 WebApi 2 中的属性检测到自引用循环【英文标题】:Self referencing loop detected for property in WebApi 2 【发布时间】:2018-01-01 22:15:24 【问题描述】:我创建了一个 Web Api 来将新产品和评论保存在数据库中。下面是WebApi代码:
POST api/产品
[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;
我正在使用 google chrome 扩展程序“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
]
显示以下错误:
"Message":"An error has occurred.",
"ExceptionMessage":"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'.",
"ExceptionType":"System.InvalidOperationException",
"StackTrace":null,
"InnerException":
"Message":"An error has occurred.",
"ExceptionMessage":"Self referencing loop detected for property 'Product' with type 'HelloWebAPI.Models.Product'.
我该如何解决这个错误?
【问题讨论】:
一个产品包含评论的集合,而评论包含产品... JSON.NET Error Self referencing loop detected for type的可能重复 Entity framework self referencing loop detected的可能重复 你可以看看我在 “Self Referencing Loop Detected” exception with JSON.Net 页面上的回答。 【参考方案1】:首先,将导航属性更改为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,可以进行全局设置(取自this SO thread),
GlobalConfiguration.Configuration.Formatters
.JsonFormatter.SerializerSettings.ReferenceLoopHandling
= ReferenceLoopHandling.Ignore;
这个错误来自 Json.NET,它试图序列化一个已经序列化的对象(你的对象循环!),文档也很清楚地说明了这一点,
Json.NET 将忽略引用循环中的对象并且不序列化它们。第一次遇到一个对象时,它将像往常一样被序列化,但如果遇到该对象作为其自身的子对象,则序列化程序将跳过对其进行序列化。
引用来自 http://www.newtonsoft.com/json/help/html/SerializationSettings.htm
【讨论】:
当我将评论和产品虚拟化时抛出异常。请建议 @LogicalDesk,你能告诉异常消息吗?此外,virtual
的建议不是您需要在启动时设置SerializerSettings
以便序列化程序收集它的例外情况—如果这是您遇到的错误。 '【参考方案2】:
避免使用您在 Entity Framework 中使用的相同类在 API 方法中映射您的实体。创建 DTO 类以与 API 一起使用,然后手动将它们转换为您的实体类或使用 Auto Mapper 等工具来帮助您执行此操作。
无论如何,如果您仍想使用 Product
和 Review
类,我能记住的两个最简单的选项是:
移除循环引用属性
由 Stuart 确定:
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;
关于(反)序列化时忽略属性,可以参考this question/answer。
使用 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)混合通常会给仍然不知道序列化如何发生的人带来问题。
在某些情况下,对于简单的场景,我确实直接使用类,因为这不是应该严格遵循的规则。我相信每种情况都有自己的需求。
【讨论】:
你能告诉我如何在这种情况下使用 DTO 吗? @LogicalDesk 我添加了代码示例和一些解释。随意问任何你想要的。以上是关于为 WebApi 2 中的属性检测到自引用循环的主要内容,如果未能解决你的问题,请参考以下文章
如何停止 .Net Core Web API 中的自引用循环?