若是你在WCF中用Entity Framework來獲取數據並返回實體對象,那麼對下面的錯誤必定不陌生。服務器
接收對 http://localhost:5115/ReService.svc 的 HTTP 響應時發生錯誤。這多是因爲服務終結點綁定未使用 HTTP 協議形成的。ide
這還多是因爲服務器停止了 HTTP 請求上下文(可能因爲服務關閉)所致。有關詳細信息,請參見服務器日誌。測試
這就是由於在返回數據的時候,序列化失敗,致使WCF服務自動中止了。this
爲了方便說明,咱們先作個示例來重現這個錯誤。spa
默認狀況下,Entity Framework爲了支持它的一些高級特性(延遲加載等),默認將自動生成代理類是設置爲true,即3d
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_...的代理實體code
而對於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,而代理類並無標識這是一個能夠序列化的實體。
既然緣由是EF生成了代理類,那咱們把它禁用了就能夠了嘛。也很簡單,只要將生成代理的配置設置爲false便可。
public MyContext() { this.Configuration.ProxyCreationEnabled = false; }
禁用後,看看經過EF獲取Student是怎麼樣的。
沒錯,代理類沒了,可是咱們不能直接經過導航屬性來獲取Teacher了。這但是殺敵一千,自損八百啊。有沒有更好的辦法呢?
既然代理類是由實體序列化而來的,咱們就能夠在返回數據前將代理類序列化成咱們所須要的實體。
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 name XmlQualifiedName 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 default return 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) { DataContractSerializerOperationBehavior dataContractSerializerOperationBehavior = description.Behaviors.Find<DataContractSerializerOperationBehavior>(); dataContractSerializerOperationBehavior.DataContractResolver = new ProxyDataContractResolver(); } public void ApplyDispatchBehavior(OperationDescription description, DispatchOperation dispatch) { DataContractSerializerOperationBehavior dataContractSerializerOperationBehavior = 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的後續版本能夠解決問題吧。