Entity Framework在WCF中序列化的问题(转)
問題描述?
如果你在WCF中用Entity Framework來獲取數據并返回實體對象,那么對下面的錯誤一定不陌生。
接收對 http://localhost:5115/ReService.svc 的 HTTP 響應時發生錯誤。這可能是由于服務終結點綁定未使用 HTTP 協議造成的。
這還可能是由于服務器中止了 HTTP 請求上下文(可能由于服務關閉)所致。有關詳細信息,請參見服務器日志。
這就是因為在返回數據的時候,序列化失敗,導致WCF服務自動停止了。
為什么會序列化失敗
為了方便說明,我們先做個示例來重現這個錯誤。
默認情況下,Entity Framework為了支持它的一些高級特性(延遲加載等),默認將自動生成代理類是設置為true,即
public MyContext(){this.Configuration.ProxyCreationEnabled = true;}這樣,如果我們的實體中包含其它實體的導航屬性,則EF會自動的為這個實體生成代理類。
[DataContract(IsReference=true)]public class Student {public Student(){this.Teachers = new HashSet<Teacher>();}[DataMember]public int ID { get; set; }[DataMember]public virtual string Name { get; set; }[DataMember]public virtual ICollection<Teacher> Teachers { get; set; }}[DataContract(IsReference = true)]public class Teacher{[DataMember]public int ID { get; set; }[DataMember]public virtual string Name { get; set; }}觀察上面兩個實體,Student中有對Teacher的導航屬性,而Teacher則沒有。我們看看通過EF對獲取這兩個對象有什么不同的情況
我們可以看到EF為Student生成了值為System.Data.Entity.DynamicProxies.Student_...的代理實體
而對于Teacher,返回的就是我們所定義的實體。
如果我們在WCF中分別定義一個契約,來返回這兩個實體會怎么樣呢?
[OperationContract]Student GetStudent();[OperationContract]Teacher GetTeacher();實現方法
public Student GetStudent(){using (MyContext context = new MyContext()){return context.Students.FirstOrDefault();}}public Teacher GetTeacher(){using (MyContext context = new MyContext()){return context.Teachers.FirstOrDefault();}}調用 WCF進行測試,我們可以很好的得到GetTeacher()的值,如圖
但是,當調用GetStudent()方法,從服務端返回結果到客戶端時確報錯了。
嗯,沒錯,就是剛開始我說的那個錯誤。但,這是為什么呢。我們明明在Student中加了DataContract和DataMember關鍵字啊。
原因就是EF自動為Student生成了代理類,WCF序列化的其實是EF生成的那個代理類,而不是我們自己定義的Student,而代理類并沒有標識這是一個可以序列化的實體。
解決方法
?1.禁用代理類
既然原因是EF生成了代理類,那我們把它禁用了就可以了嘛。也很簡單,只要將生成代理的配置設置為false即可。
public MyContext(){this.Configuration.ProxyCreationEnabled = false;}禁用后,看看通過EF獲取Student是怎么樣的。
沒錯,代理類沒了,但是我們不能直接通過導航屬性來獲取Teacher了。這可是殺敵一千,自損八百啊。有沒有更好的辦法呢?
2 反序列化
既然代理類是由實體序列化而來的,我們就可以在返回數據前將代理類序列化成我們所需要的實體。
public Student GetStudent(){using (MyContext context = new MyContext()){var stu=context.Students.FirstOrDefault();var serializer = new DataContractSerializer(typeof(Student), new DataContractSerializerSettings(){DataContractResolver = new ProxyDataContractResolver()});using (var stream = new MemoryStream()){// 反序列化serializer.WriteObject(stream, stu);stream.Seek(0, SeekOrigin.Begin);var newStu = (Student)serializer.ReadObject(stream);return newStu;}}}通過這個方法,再測試一下.
不錯,沒有報錯,并且成功的得到了我們想要的結果。
但每個方法都要這樣序列化一下,是不是很麻煩,有沒有更好的方法。
答案肯定有,我們可以通過自定義Attribute,加在服務契約上面,標識通過這個服務返回的方法都要進行反序列化。
public class ProxyDataContractResolver: DataContractResolver{private XsdDataContractExporter _exporter = new XsdDataContractExporter();public override Type ResolveName( string typeName, string typeNamespace, Type declaredType,DataContractResolver knownTypeResolver){return knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null);}public override bool TryResolveType(Type dataContractType,Type declaredType,DataContractResolver knownTypeResolver,out XmlDictionaryString typeName,out XmlDictionaryString typeNamespace){Type nonProxyType = ObjectContext.GetObjectType(dataContractType);if (nonProxyType != dataContractType){// Type was a proxy type, so map the name to the non-proxy nameXmlQualifiedName qualifiedName = _exporter.GetSchemaTypeName(nonProxyType);XmlDictionary dictionary = new XmlDictionary(2);typeName = new XmlDictionaryString(dictionary,qualifiedName.Name, 0);typeNamespace = new XmlDictionaryString(dictionary,qualifiedName.Namespace, 1);return true;}else{// Type was not a proxy type, so do the defaultreturn knownTypeResolver.TryResolveType(dataContractType,declaredType,null,out typeName,out typeNamespace);}}}public class ApplyProxyDataContractResolverAttribute : Attribute, IOperationBehavior{public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters){}public void ApplyClientBehavior(OperationDescription description, ClientOperation proxy){DataContractSerializerOperationBehaviordataContractSerializerOperationBehavior =description.Behaviors.Find<DataContractSerializerOperationBehavior>();dataContractSerializerOperationBehavior.DataContractResolver = new ProxyDataContractResolver();}public void ApplyDispatchBehavior(OperationDescription description, DispatchOperation dispatch){DataContractSerializerOperationBehaviordataContractSerializerOperationBehavior = description.Behaviors.Find<DataContractSerializerOperationBehavior>();dataContractSerializerOperationBehavior.DataContractResolver = new ProxyDataContractResolver();}public void Validate(OperationDescription description){}}
類ApplyProxyDataContractResolverAttribute就是我們想要的結果。現在我們只要在定義服務契約的時候,加上ApplyProxyDataContractResolver關鍵字就可以了。
[OperationContract][ApplyProxyDataContractResolver]Student GetStudent();[OperationContract][ApplyProxyDataContractResolver]Teacher GetTeacher();擴展
對于繼承類的序列化,要在基類用KnownType屬性來標識
[KnownType(typeof(ClassB))][KnownType(typeof(ClassA))][DataContract]public class BaseClass{}[DataContract]public class ClassA : BaseClass{}[DataContract]public class ClassB : BaseClass{}PS:雖然這樣可以解決問題,但是多一層序列化會影響效率,希望EF的后續版本可以解決問題吧。
?轉自:http://www.cnblogs.com/Gyoung/p/3153875.html
轉載于:https://www.cnblogs.com/ITGirl00/p/3533648.html
總結
以上是生活随笔為你收集整理的Entity Framework在WCF中序列化的问题(转)的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 垒骰子|2015年蓝桥杯B组题解析第九题
 - 下一篇: ES6第一节:开发环境的搭建