2012-10-18 3 views
3

Je reçois l'erreur suivante lorsque j'essaie de renvoyer un objet Questionnaire au client. J'ai ajouté le KnowType [typeof (...)] dans le contrat de données comme suggéré, mais cela ne fonctionne toujours pas. Ne sachant pas quel type est inconnu au Serializer, j'ai juste jeté dans toutes les classes qui sont dans le modèle EF. Quelqu'un peut-il aider? Merci.WCF DataContractSerializer

Voici le contrat de service

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Runtime.Serialization; 
using System.ServiceModel; 
using System.ServiceModel.Web; 
using System.Text; 
using QuestionnaireWcfServiceApp.Models; 

namespace QuestionnaireWcfService 
{ 
    [ServiceContract] 
    public interface IQuestionnaireService 
    { 
     [OperationContract] 
     QuestionnaireContract GetQuestionnaire(string questionnaireName); 

     [OperationContract] 
     QuestionChain LoadQuestion(int questionnaireID, int? questionID, int? userResponse); 
    } 

    [DataContract] 
    [KnownType(typeof(Questionnaire))] 
    [KnownType(typeof(Question))] 
    [KnownType(typeof(Choice))] 
    [KnownType(typeof(Decision))] 
    [KnownType(typeof(QuestionFlow))] 
    public class QuestionChain 
    { 
     [DataMember] 
     public Question Question { get; set; } 

     [DataMember] 
     public int? Decision {get;set;} 
    } 

    [DataContract] 
    [KnownType(typeof(Questionnaire))] 
    [KnownType(typeof(Question))] 
    [KnownType(typeof(Choice))] 
    [KnownType(typeof(Decision))] 
    [KnownType(typeof(QuestionFlow))] 
    public class QuestionnaireContract 
    { 
     [DataMember] 
     public Questionnaire Questionnaire { get; set; } 
    } 
} 

Voici le Service.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Runtime.Serialization; 
using System.ServiceModel; 
using System.ServiceModel.Web; 
using System.Text; 
using QuestionnaireWcfServiceApp.Models; 

namespace QuestionnaireWcfService 
{ 
    public class QuestionnaireService : IQuestionnaireService 
    { 
     QuestionnaireWcfServiceApp.Models.QuestionnaireEntities db = new QuestionnaireEntities(); 

     public QuestionnaireContract GetQuestionnaire(string questionnaireName) 
     { 
      QuestionnaireContract questionnaireContract = new QuestionnaireContract(); 
      if (!string.IsNullOrEmpty(questionnaireName)) 
      { 
       Questionnaire thisQuestionnaire = (from q in db.Questionnaires where q.Name.Equals(questionnaireName) select q).FirstOrDefault(); 
       if (thisQuestionnaire == null) 
        throw new ArgumentNullException("Questionnaire ID is not found."); 
       else 
       { 
        questionnaireContract.Questionnaire = thisQuestionnaire; 
        return questionnaireContract; 
       } 
      } 
      else 
       throw new ArgumentException("Questionnaire name is not specified."); 
     } 

     public QuestionChain LoadQuestion(int questionnaireID, int? currentQuestionID, int? userResponse) 
     { 
      QuestionChain qc = new QuestionChain(); 
      QuestionFlow thisFlow = null; 
      Question nextQuestion = null;   
      Questionnaire thisQuestionnaire = (from q in db.Questionnaires where q.QuestionnaireId == questionnaireID select q).FirstOrDefault(); 

      if (thisQuestionnaire == null) 
       throw new ArgumentNullException("Questionnaire ID is not found"); //InvalidOperationException; 

      if (currentQuestionID.HasValue) 
      { 
       //QuestionID should never be changed after setup. Change the QuestionText around the QuestionID 
       Question thisQuestion = thisQuestionnaire.Questions.Where(q => q.PKey.Equals(currentQuestionID)).FirstOrDefault(); 
       if (thisQuestion == null) 
        throw new ArgumentNullException("Question ID is not found"); 
       else 
       { 
        if (userResponse.HasValue) 
        { 
         thisFlow = thisQuestion.QuestionFlows.First(f => f.QuestionId.Equals(currentQuestionID) && f.ChoiceId.Equals(userResponse)); 
         if (thisFlow.Question1 != null) 
         { 
          nextQuestion = thisFlow.Question1; 
          qc.Question = nextQuestion; 
         } 
         else 
         { 
          qc.Question = null; 
          qc.Decision = thisFlow.Decision.Value; 
         } 
        } 
        else 
        { 
         //can't happen. when reaches here, a userResponse must not be null 
        } 
       } 
      }   
      else 
      { 
       //default to question 1 
       nextQuestion = thisQuestionnaire.Questions.First(q => q.QuestionId.Equals(1)); 
       if (nextQuestion == null) 
        throw new ArgumentNullException("Question ID"); 
       else 
        qc.Question = nextQuestion; 
      } 
      return qc; 
     } 
    } 
} 

Ceci est l'exception dans le journal des applications Windows.

Exception: System.ServiceModel.CommunicationException: There was an error while trying to serialize parameter http://tempuri.org/:GetQuestionnaireResult. The InnerException message was 'Type System.Data.Entity.DynamicProxies.Questionnaire_EF00247BEFB9F733C947A4C3E57FD12709E91510AC0DA534D137ED75FCCAC342' 
with data contract name 'Questionnaire_EF00247BEFB9F733C947A4C3E57FD12709E91510AC0DA534D137ED75FCCAC342: 
http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies' 
is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.'. Please see InnerException for more details. ---> System.Runtime.Serialization.SerializationException: Type 'System.Data.Entity.DynamicProxies.Questionnaire_EF00247BEFB9F733C947A4C3E57FD12709E91510AC0DA534D137ED75FCCAC342' with data contract name 'Questionnaire_EF00247BEFB9F733C947A4C3E57FD12709E91510AC0DA534D137ED75FCCAC342:http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer. 
      at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeAndVerifyType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, Boolean verifyKnownType, RuntimeTypeHandle declaredTypeHandle, Type declaredType) 
      at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithXsiTypeAtTopLevel(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle originalDeclaredTypeHandle, Type graphType) 
      at System.Runtime.Serialization.DataContractSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) 
      at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) 
      at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) 
      at System.Runtime.Serialization.XmlObjectSerializer.WriteObject(XmlDictionaryWriter writer, Object graph) 
      at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.SerializeParameterPart(XmlDictionaryWriter writer, PartInfo part, Object graph) 
      --- End of inner exception stack trace --- 
      at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.SerializeParameterPart(XmlDictionaryWriter writer, PartInfo part, Object graph) 
      at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.SerializeParameter(XmlDictionaryWriter writer, PartInfo part, Object graph) 
      at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.SerializeBody(XmlDictionaryWriter writer, MessageVersion version, String action, MessageDescription messageDescription, Object returnValue, Object[] parameters, Boolean isRequest) 
      at System.ServiceModel.Dispatcher.OperationFormatter.SerializeBodyContents(XmlDictionaryWriter writer, MessageVersion version, Object[] parameters, Object returnValue, Boolean isRequest) 
      at System.ServiceModel.Dispatcher.OperationFormatter.OperationFormatterMessage.OperationFormatterBodyWriter.OnWriteBodyContents(XmlDictionaryWriter writer) 
      at System.ServiceModel.Channels.BodyWriterMessage.OnBodyToString(XmlDictionaryWriter writer) 
      at System.ServiceModel.Channels.Message.ToString(XmlDictionaryWriter writer) 
      at System.ServiceModel.Diagnostics.MessageLogTraceRecord.WriteTo(XmlWriter writer) 
      at System.ServiceModel.Diagnostics.MessageLogger.LogInternal(MessageLogTraceRecord record) 
      at System.ServiceModel.Diagnostics.MessageLogger.LogMessageImpl(Message& message, XmlReader reader, MessageLoggingSource source) 
      at System.ServiceModel.Diagnostics.MessageLogger.LogMessage(Message& message, XmlReader reader, MessageLoggingSource source) 
     Process Name: WebDev.WebServer40 
     Process ID: 11620 

     Event Xml: 
     <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"> 
      <System> 
      <Provider Name="System.ServiceModel 4.0.0.0" /> 
      <EventID Qualifiers="49154">5</EventID> 
      <Level>2</Level> 
      <Task>7</Task> 
      <Keywords>0x80000000000000</Keywords> 
      <TimeCreated SystemTime="2012-10-18T07:32:11.000000000Z" /> 
      <EventRecordID>36499</EventRecordID> 
      <Channel>Application</Channel> 
      <Computer>Jon-PC</Computer> 
      <Security UserID="S-1-5-21-334737869-2079735299-2176000493-1000" /> 
      </System> 
      <EventData> 
      <Data>System.ServiceModel.CommunicationException: There was an error while trying to serialize parameter http://tempuri.org/:GetQuestionnaireResult. The InnerException message was 'Type 'System.Data.Entity.DynamicProxies.Questionnaire_EF00247BEFB9F733C947A4C3E57FD12709E91510AC0DA534D137ED75FCCAC342' with data contract name 'Questionnaire_EF00247BEFB9F733C947A4C3E57FD12709E91510AC0DA534D137ED75FCCAC342:http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.'. Please see InnerException for more details. ---&gt; System.Runtime.Serialization.SerializationException: Type 'System.Data.Entity.DynamicProxies.Questionnaire_EF00247BEFB9F733C947A4C3E57FD12709E91510AC0DA534D137ED75FCCAC342' with data contract name 'Questionnaire_EF00247BEFB9F733C947A4C3E57FD12709E91510AC0DA534D137ED75FCCAC342:http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer. 
      at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeAndVerifyType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, Boolean verifyKnownType, RuntimeTypeHandle declaredTypeHandle, Type declaredType) 
      at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithXsiTypeAtTopLevel(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle originalDeclaredTypeHandle, Type graphType) 
      at System.Runtime.Serialization.DataContractSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) 
      at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) 
      at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) 
      at System.Runtime.Serialization.XmlObjectSerializer.WriteObject(XmlDictionaryWriter writer, Object graph) 
      at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.SerializeParameterPart(XmlDictionaryWriter writer, PartInfo part, Object graph) 
      --- End of inner exception stack trace --- 
      at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.SerializeParameterPart(XmlDictionaryWriter writer, PartInfo part, Object graph) 
      at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.SerializeParameter(XmlDictionaryWriter writer, PartInfo part, Object graph) 
      at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.SerializeBody(XmlDictionaryWriter writer, MessageVersion version, String action, MessageDescription messageDescription, Object returnValue, Object[] parameters, Boolean isRequest) 
      at System.ServiceModel.Dispatcher.OperationFormatter.SerializeBodyContents(XmlDictionaryWriter writer, MessageVersion version, Object[] parameters, Object returnValue, Boolean isRequest) 
      at System.ServiceModel.Dispatcher.OperationFormatter.OperationFormatterMessage.OperationFormatterBodyWriter.OnWriteBodyContents(XmlDictionaryWriter writer) 
      at System.ServiceModel.Channels.BodyWriterMessage.OnBodyToString(XmlDictionaryWriter writer) 
      at System.ServiceModel.Channels.Message.ToString(XmlDictionaryWriter writer) 
      at System.ServiceModel.Diagnostics.MessageLogTraceRecord.WriteTo(XmlWriter writer) 
      at System.ServiceModel.Diagnostics.MessageLogger.LogInternal(MessageLogTraceRecord record) 
      at System.ServiceModel.Diagnostics.MessageLogger.LogMessageImpl(Message&amp; message, XmlReader reader, MessageLoggingSource source) 
      at System.ServiceModel.Diagnostics.MessageLogger.LogMessage(Message&amp; message, XmlReader reader, MessageLoggingSource source)</Data> 
      <Data>WebDev.WebServer40</Data> 
      <Data>11620</Data> 
      </EventData> 
     </Event> 
+0

Peut-être que c'est juste la configuration de mon navigateur (les images postées semblent être filtrées), mais je ne vois aucun texte d'exception. Pourriez-vous s'il vous plaît poster l'exception? –

+0

J'ai édité et reformaté le message. L'exception est plutôt longue. Vous devez utiliser les barres de défilement vertical et horizontal pour afficher le texte intégral. Merci. – user266909

+0

Il ressemble à un objet paresseusement chargé par EF qui a créé un proxy dynamique à l'exécution qui ne peut pas être sérialisé. Vous devriez charger toutes les associations avec impatience. Et soyez prudent car si vous avez des dépendances circulaires dans votre hiérarchie d'objets, il ne peut pas non plus être sérialisé. Le meilleur moyen consiste à utiliser des objets de transfert de données et à ne pas exposer vos entités EF dans le contrat de service. –

Répondre

4

Tous les types utilisés avec WCF doivent être un contrat de données ou marqués comme [Serializable]. Cela inclut tous les objets qui sont déjà dans un contrat de données WCF. Si vous en avez la possibilité, vous devez y ajouter la balise [Serializable] ou ajouter les balises spécifiques à WCF. Ceci sans blague signifie que même si vous ajoutez un type aux KnownTypes, cela ne signifie pas que WCF saura qu'il est sérialisable.

Si aucune de ces options n'est disponible, je suggère de créer des objets "proxy" qui peuvent contenir les valeurs que vous voulez transmettre et les convertir vers et à partir de vos objets cibles. Semble fou, mais c'est la façon dont il va ...

EXEMPLE AJOUTÉE:

WCF utilise un sérialiseur containined dans l'espace de noms System.Runtime.Serialization pour sérialiser et désérialiser des données dans différents formats (XML, SOAP, JSON, binaire). Pour que cela fonctionne, les objets qui vont être sérialisés doivent être de type sérialisable (avec un attribut). Construit dans le WCF attributs des objets de données ressemble à ceci:

//This is marked with the DataContract attribute, which is WCF specific 
[DataContract] 
public class Foo 
{ 
    //The DataMember attribute is also WCF specific and specifies what data 
    // is included in the serialization. Any properties (or variables) must be 
    // accessable, as in not read only. 
    [DataMember] 
    public string Property1{get;set;} 

    //This variable will be serialized as well, even though it is private. This 
    // works great if you have a readonly property but still need to pass the data 
    [DataMember] 
    private int Id = 0; 


    //This does not have a DataMember attribute and will not be serialized 
    private string var1; 
} 

WCF peut également utiliser des classes (comme le DataTable et DataSet) qui sont marqués comme Serializable.

//This is marked with the Serializable attribute. All public and private 
// fields are automatically serialized (unless there is a containing object 
// that is not serializable then you get a SerializationException 
[Serializable] 
public class Bar 
{ 
    //gets serialized 
    public string Property1{get;set;} 

    //gets serialized 
    private string var1; 
} 

L'idée de créer des objets « proxy » est le même qui est ce qui est généré par Visual Studio lorsque vous ajoutez une référence de service, seulement inversé. Supposons que vous ayez une classe "Foo2" qui ne soit pas sérialisable et qui possède certaines propriétés.

public class Foo2 
{ 
    public string Property1{get;set;} 

    public string Property2{get;set;} 

    public int Property3{get;set;} 
} 

Vous pouvez faire une classe proxy (dans presque comme vous voulez) qui vous permettra de passer les proerties et-vient à et de votre service.

[DataContract] 
public class Foo2Proxy 
{ 
    [DataMember] 
    public string Property1{get;set;} 

    [DataMember] 
    public string Property2{get;set;} 

    [DataMember] 
    public int Property3{get;set;} 

    public Foo2Proxy() 
    { 
    } 

    public Foo2Proxy(Foo2 foo) 
    { 
     this.Property1 = foo.Property1; 
     this.Property2 = foo.Property2; 
     this.Property3 = foo.Property3; 
    } 

    public static Foo2 Create(Foo2Proxy fProxy) 
    { 
     var foo = new Foo2(); 
     foo.Property1 = fProxy.Property1; 
     foo.Property2 = fProxy.Property2; 
     foo.Property3 = fProxy.Property3; 
     return foo; 
    } 
} 

Je suis sûr qu'il ya probablement 100.000 façons et des opinions différentes sur la façon de le faire, mais cela est juste un exemple d'une possibilité qui a fonctionné pour moi à l'époque. Si vous jetez un oeil à this article on CodeProject, vous pouvez voir ce que je tirais ici. Dans votre cas particulier, vous devrez peut-être créer des objets "proxy" (ou des objets wrapper, peu importe comment vous les appelez) pour vos types Question et Decision créés par EF, car il semblerait qu'ils ne soient pas intrinsèquement sérialisable, sauf si vous pouvez entrer dans le code généré pour ces objets et ajouter les attributs comme décrit ci-dessus. Une considération supplémentaire, est si elles sont dérivées d'une autre classe (ou classe abstraite) cette classe de base DOIT ÉGALEMENT être marqué comme serializable ou comme un DataContract!

+0

Merci beaucoup pour la réponse. Pourriez-vous me montrer un exemple de "tags spécifiques à WCF" et un objet proxy que vous avez mentionné plus haut? Merci. – user266909

+0

@ user266909 Mis à jour avec des exemples – iMortalitySX

2

Le problème ici est les objets que vous obtenez lorsque vous interrogez la base de données en utilisant QuestionnaireEntities sont d'un type proxy et non de Questionnaire (ou d'autres types définis par vous) type.

Vous pouvez tester cet appel .GetType() de l'un des objets renvoyés.

Les proxies sont créés pour prendre en charge Chargement paresseux. Ils chargent des données uniquement lorsque vous essayez d'accéder à une propriété. Si vous les renvoyez d'un service WCF, vous devez indiquer à Entity Framework de cesser de créer des proxys.

Vous pouvez le faire en écrivant du code folloing:

db.Configuration.ProxyCreationEnabled = false; 

Note: Pour autant que je sache, éteindre les procurations devriez effectivement désactiver Loading Lazy ainsi. De mon expérience avec Entity Framework, vous avez deux options, Chargement paresseux ou Pas de chargement du tout.

Ainsi, avec Loading Lazy handicapés, toutes les propriétés de navigation seront nulle. Pour que EF puisse également charger des valeurs pour ces propriétés, vous devez utiliser la méthode Include() dans votre requête.

+0

Il se trouve que j'ai des dépendances circulaires. Quelle est l'alternative? Après avoir lu toute la complication pour utiliser EF, je me demande si je devrais simplement convertir les entités en JSON et retourner JSON au lieu d'entités du service WCF. – user266909

+0

+1 A travaillé pour moi. –

Questions connexes