惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

罗磊的独立博客
Apple Machine Learning Research
Apple Machine Learning Research
The Cloudflare Blog
WordPress大学
WordPress大学
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 叶小钗
博客园 - 聂微东
阮一峰的网络日志
阮一峰的网络日志
腾讯CDC
博客园 - 三生石上(FineUI控件)
V
V2EX
有赞技术团队
有赞技术团队
V
Visual Studio Blog
小众软件
小众软件
Jina AI
Jina AI
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - Franky
量子位
T
Tailwind CSS Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
P
Palo Alto Networks Blog
Cisco Talos Blog
Cisco Talos Blog
I
Intezer
Project Zero
Project Zero
A
Arctic Wolf
P
Privacy International News Feed
V
Vulnerabilities – Threatpost
L
Lohrmann on Cybersecurity
S
Securelist
C
Cybersecurity and Infrastructure Security Agency CISA
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Tor Project blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
S
Security @ Cisco Blogs
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Google DeepMind News
Google DeepMind News
N
News and Events Feed by Topic
TaoSecurity Blog
TaoSecurity Blog
L
LINUX DO - 热门话题
G
GRAHAM CLULEY
Help Net Security
Help Net Security
N
News | PayPal Newsroom
W
WeLiveSecurity
G
Google Developers Blog
Microsoft Security Blog
Microsoft Security Blog
Engineering at Meta
Engineering at Meta
MongoDB | Blog
MongoDB | Blog
C
Check Point Blog

博客园 - Saar

LightSwitch 2011 实践之个人汽车仓库 —— 起步 让我们更快一点的Visual Studio的技巧们 实战 WPF Jump List 编程 实战Bing MAP REST服务的封装、调用 选好 Async 函数的返回类型 大家都来搭架构 —— VS 2010 Layer Diagram (层图) 基本使用 基于Entity Framework 4.1实现一个适用于单元测试的MockDbContext(下) 基于Entity Framework 4.1实现一个适用于测试的MockDbContext(上) 利用IProgress轻松搞定进度条编程 C# 5.0功能之Async一瞥 C#不是C++ 之 委托(Delegate) C#不是C++ 之 泛型(Generic) Silverlight 学习笔记——MVVM模式实现主从数据显示 Silverlight 学习笔记——用MediaElement制作在线音乐播放器 Silverlight 学习笔记——应用程序模型 Visual Studio 2010 部分新功能概览-QS, HR, GFU Silverlight学习笔记——依赖属性和路由事件 Silverlight 学习笔记——布局 Visual Studio 2010 部分新功能概览-Dynamic & Named / Optional Parameters
用WCF + Entity Framework 序列化时的陷阱
Saar · 2011-07-30 · via 博客园 - Saar

当使用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/">
   5:          <ParentRef z:Ref="i1"/>
   9:              <ParentRef z:Ref="i1"/>

WCF的异常信息不准确给问题判断带来了很大的难度。但是,看清问题本质,分析问题的特殊性,加之一定经验的积累,就能很快的找准问题并解决之。

假像总是会露出破绽的。例如,虽然异常信息提示可能是超时,但如果一调用服务马上返回错误,一般就不会是超时问题。如果错误提示说可能数据超过允许返回的大小,那么,试着返回一个单一个对象而非集合……

当然,我们还是在不断努力,希望给大家带来提供更加易用的Visual Studio。

参考资源

Working with POCO Entities

ProxyDataContractResolver Class

Little knowledge is dangerous.