Evil 域

当Evil遇上先知

导航

用WCF + Entity Framework 序列化时的陷阱

Posted on 2011-07-30 02:06  Saar  阅读(5782)  评论(9编辑  收藏  举报

当使用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。

 

参考资源

Working with POCO Entities

ProxyDataContractResolver Class