zoukankan      html  css  js  c++  java
  • WebApi-JSON序列化循环引用

    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:UsersITAppDataLocalTempSGPicFaceTpBq2066428CEF69B.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配置 ,这几种推荐结合的使用,并不是说一种方法可以解决所有的问题,还是要视情况而定,选择最适合的。

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

  • 相关阅读:
    点子
    evil idea
    ubuntu 10.04.3 modify source.list
    点子
    ubuntu常用软件安装
    架构技术介绍网站
    点子
    点子
    【转发】上海地区工作,全国找网络底层技术开发大牛,旅游方面的创业项目。
    文本相似度计算余弦定理和广义Jaccard系数
  • 原文地址:https://www.cnblogs.com/slyfox/p/7388743.html
Copyright © 2011-2022 走看看