文章出處

問題描述 

如果你在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 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的后續版本可以解決問題吧。

 

 


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()