• WCF自引用和循环引用导致的序列化问题


    当使用WCF + Entity Framework时要小心,否则,很容易掉入各种陷阱。这里介绍两个在序列化时容易遇到的、会导致服务停止的陷阱。

    一、试图序列化Entity Proxy类而导致服务停止。

    二、序列化时出现死循环导致服务停止;

    无论掉入哪一个陷阱,在客户端都会看到这样的对话框(点击看大图):

    image

    呃~这个图才对Smile

    image

    大致意思是会说:服务不在线或者客户端配置有问题或者Proxy类有问题。而如果跟着Error Details里的第一行Google,会找到许多文章关于调整数据缓存大小、调整操作超时时间……如果按照常规则调试方法——按图索骥找问题,那么就可能越陷越深。

    查找问题时,首先要透过假象,看到问题的本质——WCF服务因为某些原因而停止了。其次,我们要具体分析问题的特殊性,结合经验,来查找问题。

    服务停止有一个典型的原因,那就是序列化出问题了。由于在WCF中,序列化不需要我们写代码实现,它在为我们提供便利的同时,也埋下了伏笔。而EF的使用,又进一步将事情变得错综复杂。凡事有弊有利,往往是使用了EF这样的特殊性,让我们查找问题时也更具方向性。

    EF中,有什么特性会与WCF交互?没错,我们常常把EF的实体类用作DataContractEF的实体类,无论是生成的还是通过Code First写的,运行时,为了支持EF的一些“高级”功能,例如,LazyLoad,Runtime会从你的Entity类自动继承出一个对应的代理类(Proxy Types)并对代理类对象进行操作。例如Person类,在运行时或者就会对应有一个叫Person123456类(肯定不会叫这个名字啦,这权当标识一下)。我们写代码时对Person的操作,运行时会换成对Person123456的操作。

    通过调试窗口,我们可以到看到这个隐藏在背后的类(代码类为Handbook,实际类为DynamicProxies.Handbook_AE28…84):

    image

    这样一来,WCF,具体的说,是DataContractSerializer就不认账了——我只认识老子(Handbook),不认识儿子(代理类),没法序列化!于是它抛出了异常,导致服务中断。这样一来,我们会看到,用WCF返回一般的对象没有问题,一旦返回实体类对象,就会遇到了文章开始时看到的错误。

    一个简单的解决方法:让DbContext使用静态类。在对应的DbContext的构造函数里添加一个设置即可:

       1:  public partial class MTBContainer : DbContext
       2:      {
       3:          public MTBContainer()
       4:              : base("name=MTBContainer")
       5:          {
       6:              this.Configuration.ProxyCreationEnabled = false; // This is the line added.
       7:          }
       8:          //...
       9:          public IDbSet<PlacePictureType> PlacePictureTypes { get; set; }
      10:          //...
      11:      }

    第二个容易遇到的问题是序列化死循环的问题。乍一看错误跟第一个显示的是一样的:服务不可用了!而且同样是序列化问题。

    但是,这类问题有一个特别的地方:有些实体类对象能正常返回,有些不能。

    经过观察分析发现,不能返回的类往往是那些有Navigation属性的实体类,进一步分析,Navigation属性往往会构成对自身的间接引用:例如在一对多表里,往往一端有一个多端对象的集合,同时,多端又有一个一端的引用。比如有一个Parent实体,有一个Child实体,那么,Parent里会有一个ICollection<Child>用来存储所有的孩子,而Child里又会有一个Parent用来指明当前这个Child的Parent。这种互相引用的关系导致了DataContractSerializer在序列化它时出现了问题。

    看来,不光光是在EF中会遇到这样的问题,DataContractSerializer序列化一个直接或者间接引用了自己的类对象,就会陷入死循环。

    说话……Visual Studio的图标是不是像一个死循环?^v^

    image

    回正题,以上面描述的类为例:

       1:      //[DataContract(IsReference = true)]
       2:      [DataContract]
       3:      public class Parent
       4:      {
       5:          public Parent()
       6:          {
       7:              Children = new List<Child>();
       8:          }
       9:          [DataMember]
      10:          public string Name { get; set; }
      11:          [DataMember]
      12:          public IList<Child> Children { get; set; }
      13:      }
      14:   
      15:      //[DataContract(IsReference = true)]
      16:      [DataContract]
      17:      public class Child
      18:      {
      19:          [DataMember]
      20:          public string Name { get; set; }
      21:          [DataMember]
      22:          public Parent ParentRef { get; set; }
      23:      }
    我们在Console Applicatin的Main函数里试着用DataContractSerializer序列化它:
       1:              // Create mock objects
       2:              Parent p = new Parent() { Name = "Baba" };
       3:              Child c1 = new Child() { Name = "John", ParentRef = p };
       4:              Child c2 = new Child() { Name = "Alice", ParentRef = p };
       5:              p.Children.Add(c1);
       6:              p.Children.Add(c2);
       7:             
       8:             // Creat DataContractSerializer
       9:              DataContractSerializer serializer = new DataContractSerializer(typeof(Parent));
      10:             
      11:              // Serialize & output results.
      12:              string result = null;
      13:              using (Stream s = new MemoryStream())
      14:              {
      15:                  serializer.WriteObject(s, p);
      16:                  s.Seek(0, SeekOrigin.Begin);
      17:   
      18:                  using (StreamReader r = new StreamReader(s))
      19:                  {
      20:                      result = r.ReadToEnd();
      21:                  }
      22:              }
      23:              Console.WriteLine(result);

    运行结果是:Crash。默认情况下,DataContractSerializer是这么把对象层层展开

       1:  <Parent>
       2:     <Children>
       3:         <Child>
       4:             <Parent>    <!—- this is Child.Parent Property… Enter into endless loop -->
       5:                 <Children>

    它不会顾及引用关系,只会一味的把对象的属性展成对应的XML元素。因此,Parent –> Child1 –> Parent –> Child1…无穷匮也了。

    解决方法一、已经在上面的代码里了:在DataContract上使用IsReference参数,并且设置为true。([DataContract(IsReference = true)])。

    解决方法二、调用DataContractSerializer的另一个构造函数来替代上面代码中的第9行:

       1:  DataContractSerializer serializer = new DataContractSerializer(typeof(Parent),
       2:          "Parent",
       3:          string.Empty,
       4:          null,
       5:          int.MaxValue,
       6:          false,
       7:          true,
       8:          null,
       9:          null);

    第7行用来表明使用Preserve Object Reference。使用了Preserve Object Reference以后,会产生类似以下代码的序列化结果,每个元素都会有其对应的Id,对象引用对引用Id

       1:  <Parent z:Id="i1" xmlns="http://schemas.datacontract.org/2004/07/Sealize" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
       2:      <Children>
       3:          <Child z:Id="i2">
       4:          <Name>John</Name>
       5:          <ParentRef z:Ref="i1"/>
       6:          </Child>
       7:          <Child z:Id="i3">
       8:              <Name>Alice</Name>
       9:              <ParentRef z:Ref="i1"/>
      10:          </Child>
      11:      </Children>
      12:      <Name>Baba</Name>
      13:  </Parent>
     
    WCF的异常信息不准确给问题判断带来了很大的难度。但是,看清问题本质,分析问题的特殊性,加之一定经验的积累,就能很快的找准问题并解决之。
    假像总是会露出破绽的。例如,虽然异常信息提示可能是超时,但如果一调用服务马上返回错误,一般就不会是超时问题。如果错误提示说可能数据超过允许返回的大小,那么,试着返回一个单一个对象而非集合……
     
    当然,我们还是在不断努力,希望给大家带来提供更加易用的Visual Studio。
  • 相关阅读:
    Toggle控制窗口的显隐
    碰撞检测(2D&&3D)
    3D空间 圆柱体画线
    鼠标拖动2D物体(图片)
    实现图片的闪烁效果
    UI 2D图片随鼠标旋转
    射线检测(Summary)
    [转]C#静态方法与非静态方法的比较
    获取精灵
    用于切割字符串的方法
  • 原文地址:https://www.cnblogs.com/lipf/p/2458803.html
Copyright © 2020-2023  润新知