Overview

最近被序列化,循环引用的问题,让我浑身酸爽。遇到这种异常是在搭建WebApi的时候,当我返回Linq实例类集合的时候出现的。

下定决心要解决这个问题。循环引用引起的原因是:

比如说:我现在有两个 类 A 和 B 现在 A类中有B类类型的属性存放着B类的对象,而B类中有一个属性,存放置A类型的字段。结果,进行序列化的时候,序列化A的时候,因为要序列化这B类对象的属性,然后去序列化B类型的对象,B类型的对象有一个属性放置A类型的对象,然后又去序列化A ,如此循环往复。

说的可能有点抽象,下面我们还是来看代码吧。

会引发异常的代码

一个简陋的Parent类

namespace 序列化循环引用
{
public class Parent
{
public string Name { get; set; } public string Age { get; set; } public string Gender { get; set; } public Child Child { get; set; }
}
}

一个更简陋的Child类

namespace 序列化循环引用
{
public class Child
{
public string Name { get; set; } public Parent Parent { get; set; }
}
}

PS:这两个简陋的类,看起来平淡无奇,但是这里有两个地方需要注意一下,Parent类中,有一个【Child】类型的属性,Child类中有个【Parent】类型的数据。然后下面我要进行一个脑残而又神奇的操作。

private void Form1_Load(object sender, EventArgs e)
{
Child child = new Child();
Parent person = new Parent() { Name = "鲁迅认识的那只猹", Age = "18", Gender = "男", Child = child };
child.Name = "Test";
child.Parent = person; JavaScriptSerializer jss = new JavaScriptSerializer();
string value = jss.Serialize(jss);
Console.WriteLine(value);
}

代码看上去没有问题,但是如果运行程序就会抛出异常

为什么要做这种脑残的操作

我们自己设计的话,当然我想大家,一般情况下是不会这么做的。除非想找一下刺激。但是这种看似脑残的操作,在ORM框架中被广泛的应用,所以说这个操作有些时候并不脑残。

建立一个测试用数据库 表结构如下

填充了一些数据

User表

Department 表

查看Linq生成的实体类

Department

[global::System.Data.Linq.Mapping.TableAttribute(Name="dbo.[User]")]
public partial class User : INotifyPropertyChanging, INotifyPropertyChanged
{ private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty); private int _Id; private string _Username; private string _Password; private int _DepartmentId; private EntityRef<Department> _Department;
/*************省略其他代码****************/
}

User

[global::System.Data.Linq.Mapping.TableAttribute(Name="dbo.Department")]
public partial class Department : INotifyPropertyChanging, INotifyPropertyChanged
{ private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty); private int _DepartmentId; private string _Name; private EntityRef<User> _User;
/*************省略其他代码****************/
}
分析

我们看到 【User】表中引用了【Department】的对象,而【Department】中引用了【User】中的对象,如果我们这样进行序列化的话,一定会出现很有意思的事情 ![img](file:///C:\Users\IT\AppData\Local\Temp\SGPicFaceTpBq\20664\28CEF69B.png)。

为什么微软要这么做呢,先来看看官方的解释。

Provides for deferred loading and relationship maintenance for the singleton side of a one-to-many relationship in a LINQ to SQL application.

为在LINQ到SQL应用程序的一对多关系的单例方面提供延迟加载和关系维护。(感谢有道词典...)

延迟加载我们在写代码的时候可能没有什么感觉,但是关系维护 这一点大家应该深有体会。比如说下面的代码:

TestDBDataContext db = new TestDBDataContext();
var result = from a in db.Users
where 1 == 1
select new
{
用户名 = a.Username,
密码 = a.Password,
部门名称 = a.Department.Name
};

通过【User】实体类,可以快速的关联到【Department】的属性,从而拿到数据。配合我们VS强大的智能感知,可以进行爽快的开发。正是因为这点ORM框架才做出了看似有点问题的设计。

解决问题

上面的讲解,是为了省事,建立了一个Winfrom的项目,到了解决问题的时候了我们需要建立一个【WebApi】的项目。

以下的配置将是我们解决问题的时候的默认配置。

public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
/*************省略的不相干的代码*******************/ //干掉Xml序列化
config.Formatters.Remove(config.Formatters.XmlFormatter);
}
}

获取User表的数据

 public class TestController : ApiController
{
public IEnumerable<object> GetAllUser()
{
Models.DBDataContext db = new Models.DBDataContext();
return db.Users;
}
}

在浏览器中进行浏览,会出现以下的错误页面,其中会有一条类似下面的错误信息

Self referencing loop detected with type 'TestApi.Models.User'. Path '[0].Department.Users

referencing loop 是关键,告诉我们出现了循环引用.

因为ORM框架出于开发效率的设计,造成了我们序列化的时候,循环引用的问题。经过网上资料的查找找到了如下的解决方案。

  1. 使用匿名类,手动的避免循环引用的状况
  2. 使用【[Newtonsoft.Json.JsonIgnore]】特性,标识引用对象,不进行序列化
  3. 更改WebApi的配置,忽略循环引用的序列化【推荐】

1 使用匿名类

public class TestController : ApiController
{
public IEnumerable<object> GetAllUser()
{
Models.DBDataContext db = new Models.DBDataContext();
//return db.Users; var result = from a in db.Users
select new
{
a.Username,
a.Password,
a.Department.Name
};
return result;
}
}
/*
输出结果
[{"Username":"鲁迅认识的那只猹","Password":"123456","Name":"人事部"},{"Username":"被鲁迅认识的那只猹","Password":"123456","Name":"人事部"},{"Username":"猹","Password":"123456","Name":"财务部"}]
*/

因为使用了匿名类,匿名类中也没有出现,会出现循环引用的情况,所以这是一种解决方案。

2 使用[Newtonsoft.Json.JsonIgnore]特性标记

public IEnumerable<Models.User> GetAllUser()
{
Models.DBDataContext db = new Models.DBDataContext();
return db.Users;
}

直接像上面这么写肯定是报错了,我们需要对Linq生成的实体类稍微做一下修改

[global::System.Data.Linq.Mapping.TableAttribute(Name="dbo.[User]")]
public partial class User : INotifyPropertyChanging, INotifyPropertyChanged
{
/*************省略的不相干的代码*******************/ /*标记 Department 属性在序列化的时候忽略掉*/
[Newtonsoft.Json.JsonIgnore]
[global::System.Data.Linq.Mapping.AssociationAttribute(Name="Department_User", Storage="_Department", ThisKey="Id", OtherKey="DepartmentId", IsForeignKey=true)]
public Department Department
{
get
{
return this._Department.Entity;
}
set
{
Department previousValue = this._Department.Entity;
if (((previousValue != value)
|| (this._Department.HasLoadedOrAssignedValue == false)))
{
this.SendPropertyChanging();
if ((previousValue != null))
{
this._Department.Entity = null;
previousValue.User = null;
}
this._Department.Entity = value;
if ((value != null))
{
value.User = this;
this._Id = value.DepartmentId;
}
else
{
this._Id = default(int);
}
this.SendPropertyChanged("Department");
}
}
}
/*************省略的不相干的代码*******************/
}

再次运行,成功序列化

[{"Id":1,"Username":"鲁迅认识的那只猹","Password":"123456","DepartmentId":1},{"Id":2,"Username":"被鲁迅认识的那只猹","Password":"123456","DepartmentId":1},{"Id":3,"Username":"猹","Password":"123456","DepartmentId":2}]

3 直接进行配置为忽略循环引用 【推荐】

public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
/*************省略的不相干的代码*******************/ //干掉Xml序列化
config.Formatters.Remove(config.Formatters.XmlFormatter);
//设置JSON序列化遇到循环引用的处理方式
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
}
}

运行程序,成功序列化

[{"Id":1,"Username":"鲁迅认识的那只猹","Password":"123456","DepartmentId":1,"Department":{"DepartmentId":1,"Name":"人事部"}},{"Id":2,"Username":"被鲁迅认识的那只猹","Password":"123456","DepartmentId":1,"Department":{"DepartmentId":2,"Name":"财务部"}},{"Id":3,"Username":"猹","Password":"123456","DepartmentId":2,"Department":null}]

结语

关于JSON序列化出现循环引用的问题的解决方案,一共有三种使用匿名类 使用特性标识 直接进制WebApi配置 ,这几种推荐结合的使用,并不是说一种方法可以解决所有的问题,还是要视情况而定,选择最适合的。

本文就到此为止,文中有任何纰漏之处,还望大家多多指出,以免误导大家。

WebApi-JSON序列化循环引用的更多相关文章

  1. [MVC_Json序列化]MVC之Json序列化循环引用

    在做MVC项目时,难免会遇到Json序列化循环引用的问题,大致错误如下 错误1:序列化类型为“...”的对象时检测到循环引用. 错误2:Self referencing loop detected f ...

  2. 解决.Net MVC EntityFramework Json 序列化循环引用问题.

    以前都是到处看博客,今天小菜也做点贡献,希望能帮到大家. 废话不多说,直接进入正题. 用过.net MVC的同学应该都被json序列化报循环引用错误这个问题骚扰过.网上有一些解决办法,但是都治标不治本 ...

  3. 小解系列-自关联对象.Net MVC中 json序列化循环引用问题

    自关联对象在实际开发中用的还是比较多,例如常见的树形菜单.本文是自己实际的一个小测试,可以解决循环引用对象的json序列化问题,文笔不好请多见谅,如有错误请指出,希望有更好的解决方案,一起进步. 构造 ...

  4. 关于json序列化循环引用导致出错

    以下是错误信息: Caused by: java.lang.IllegalStateException: circular reference error  Offending field: meth ...

  5. Json序列化循环引用的问题

    今天在发布接口的时候出突然出现了一个问题,报错代码为: 1 An exception has occurred while using the formatter 'JsonMediaTypeForm ...

  6. 解决MVC Json序列化的循环引用问题/EF Json序列化循引用问题---Newtonsoft.Json

    1..Net开源Json序列化工具Newtonsoft.Json中提供了解决序列化的循环引用问题: 方式1:指定Json序列化配置为 ReferenceLoopHandling.Ignore 方式2: ...

  7. Atitit.json xml 序列化循环引用解决方案json

    Atitit.json xml 序列化循环引用解决方案json 1. 循环引用1 2. 序列化循环引用解决方法1 2.1. 自定义序列化器1 2.2. 排除策略1 2.3. 设置序列化层次,一般3级别 ...

  8. EF webapi json序列化 表间相互引用 无限循环问题解决方案

    WebApiConfig.cs中加入 如下代码即可解决无限循环问题 var json = config.Formatters.JsonFormatter; // 解决json序列化时的循环引用问题 j ...

  9. EntityFramework Model有外键时,Json提示循环引用 解决方法

    正文之前先说两句,距离上篇博客已将近两个月,这方面的学习和探索并没有停止,而是前进道路上遇上了各种各样的问题,需要不断的整理.反思和优化,这段时间的成果,将在最近陆续整理发出来. 个人感觉国内心态太浮 ...

随机推荐

  1. vue-awesome-swiper

    本文地址:https://www.cnblogs.com/veinyin/p/9370113.html  聊起轮播就会想到 swiper,作为一个强大的轮播插件,当然有人为 Vue 进行二次封装,那就 ...

  2. 定价(Price)

    传送门 [题目描述] 在市场上有很多商品的定价类似于 999 元.4999 元.8999 元这样.它们和 1000 元.5000 元和 9000 元并没有什么本质区别,但是在心理学上会让人感觉便宜很多 ...

  3. 【微服务架构】SpringCloud之Ribbon

    一:Ribbon是什么? Ribbon是Netfix发布的开源项目,主要负责客户端的软件负载均衡算法,将Netfix的中间层连接在一起,Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等. ...

  4. 20155303 2016-2017-2 《Java程序设计》第十周学习总结

    20155303 2016-2017-2 <Java程序设计>第十周学习总结 目录 学习内容总结 网络编程 数据库 教材学习中的问题和解决过程 代码调试中的问题和解决过程 代码托管 上周考 ...

  5. mysql5.7.10 源码编译安装记录 (centos6.4)【转】

    一.准备工作 1.1 卸载系统自带mysql 查看系统是否自带MySQL, 如果有就卸载了, 卸载方式有两种yum, rpm, 这里通过yum卸载 rpm -qa | grep mysql //查看系 ...

  6. COM组件服务访问权限

    解决办法 :添加ASP.NET权限访问COM组件服务. IIS 5 上为 {MACHINE}\ASPNET IIS 6 和 IIS 7 上为网络服务:NETWORK SERVICE IIS 7.5 上 ...

  7. Javascript中的Callback方法浅析

    什么是callback?  回调函数就是一个通过函数指针调用的函数.如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数.回调函数不是由该函数 ...

  8. mariadb/mysql使用Navicat连接报错

    [问题1] 使用Navicat连接服务器的mariadb/mysql时报错 access denied for user root@192.168.xx.xx(using password:yes) ...

  9. centos7连接阿里云长时间连接不上

    一.手动修改网卡配置 手上有几台centos7的linux,当连接阿里云的ecs服务器时候长时间连接不上,最后失败的问题. 使用 -vvv参数到如下语句就卡着不动了 ssh -vvv XXX.XXX. ...

  10. java基础45 IO流技术(输入字符流/缓冲输入字符流)

    一.输入字符流 1.1.输入字符流体系 ------| Reader:输入字符流的基类(抽象类)  ----------| FileReader:向指定文件读取数据的输入字符流(把硬盘上的数据读取到程 ...