Entity Framework在WCF中序列化的問題

問題描述 

若是你在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,而代理類並無標識這是一個能夠序列化的實體

解決方法

 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的後續版本能夠解決問題吧。

相關文章
相關標籤/搜索