前言
一般我们在开法 ASP.NET Web API 时,如果是使用 Entity Framework 技术来操作数据库的话,当两个 Entity 之间包含导览属性(Navigation Property)时,而当我们输出的格式为 JSON 对象时,会出现一个例外,错误讯息为:「'ObjectContent`1' 类型无法序列化内容类型 'application/json; charset=utf-8' 的回应主体。」,而小弟参考了 Will 保哥以及 Bruce 两位前辈的文章后,整理出两种小弟觉得比较可行的替代与解决方案。
了解问题
这张图里包含了两张数据表 Orders 与 Order_Details ,两者之间存在着一对多的关系,而预设 Entity Framework 会自动帮我们的关联数据表加入导览属性(Navigation Property),接着我们往下一张图看下去:
public IEnumerable<Orders> GetOrders() { return db.Orders; }
这段程序代码为 ValuesController 里的一个 Function ,当我们请求时会返回 Orders 所有数据,但当我们输入网址 /api/Values/ 请求时却发生了这样的错误:
这个问题发生的原因为,当我们请求某个特定的 Enity 时会取出该 Entity 的所有属性内容,当然包夸了导览属性的数据,而究竟这个问题如何照成呢?以目前的案例来看,当我们取得 Orders 的资料时也会一并取得其导览属性,也就是 Order_Details 的所有数据,而在 Order_Details 里也包含着 Orders 的导览属性,所以又会在去取得 Orders 的数据....,这样两个实体之间不停的往返就会造成了无限循环,也是我们前面所说的循环参考的错误。
如何解决
方法一:
在开发 ASP.NET MVC 中,时常会用到部分类别(Partail Class)来为我们的数据域位加上验证属性,所以利用此特性来解决我们的问题,首先在 Model 数据夹底下新增两个档案分别为:OrdersMetadata.cs 、Order_DetailsMetadata.cs
view source print ?
01. OrdersMetadata.cs
02.
03. [MetadataType(typeof(OrderMD))]
04. public partial class Order
05. {
06. public class OrderMD
07. {
08. [JsonIgnore()] // 需引用 using Newtonsoft.Json;
09. public virtual ICollection<Order_details> Order_Details { get; set; }
10. }
11. }Order_DetailsMetadata.cs
12.
13. [MetadataType(typeof(Order_DetailsMD))]
14. public partial class Order_Details
15. {
16. public class Order_DetailsMD
17. {
18. [JsonIgnore()] // 需引用 using Newtonsoft.Json;
19. public virtual Orders Orders { get; set; }
20. }
21. }
这边我们在在对应的导览属性上都加上 「JsonIgnore」的属性,来防止循环参考的问题发生,记得是有关联的两边都要加上「JsonIgnore」的属性。
方法二:
另外一种方法则是利用我们在开发 ASP.NET MVC 时常用到的 ViewModel 的概念,针对特定的页面或请求只返回特定的数据,所以这边我们能透过 DTO 方式来解决我们问题,首先我们先在 Model 底下新增一个 DTO.cs 档案:
DTO.cs
view source print ?
01. public class OrderDTO
02. {
03. public int OrderID { get; set; }
04. public string CustomerID { get; set; }
05. public int? EmployeeID { get; set; }
06. public DateTime? OrderDate { get; set; }
07. public List<Order_detailsDTO> Order_Detail { get; set; }
08. }
09. public class Order_DetailsDTO
10. {
11. public int OrderID { get; set; }
12. public decimal UnitPrice { get; set; }
13. public decimal Quantity { get; set; }
14. }
并且修改我们原本在 Web API Controller 里的 Function:
view source print ?
01. public IEnumerable<OrderDTO> GetOrders()
02. {
03. NorthwindEntities db = new NorthwindEntities();
04.
05. return db.Orders.ToList().Select(p => new OrderDTO
06. {
07. CustomerID = p.CustomerID,
08. EmployeeID = p.EmployeeID,
09. OrderDate = p.OrderDate,
10. OrderID = p.OrderID,
11. Order_Detail = p.Order_Details.Select(x => new Order_DetailsDTO
12. {
13. OrderID = x.OrderID,
14. Quantity = x.Quantity,
15. UnitPrice = x.UnitPrice
16. }).ToList()
17. }); ;
18. }
总结
两种作法都能解决造成循环对象参考的问题,而在 Bruce 和 Will 保哥的文章都指出用第一种方法来解决此循环参考错误是最正确的作法,两种作法算是两种不同的出发点,所以该怎么用应该就是看各位读者如何应用了。