3

I am getting the following error when trying to return a Questionnaire object to the client. I added the KnowType[typeof(...)] in the Data Contract as suggested, but it still doesn't work. Not knowing which type is unknown to the Serializer, I just threw in all classes that are in the EF model. Can someone help? Thanks.

Here is the Service Contract

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; }
    }
}

Here is the 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;
        }
    }
}

This is the exception in the Windows Application Log.

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>
4

2 回答 2

4

与 WCF 一起使用的所有类型都必须是数据协定或标记为 [Serializable]。这包括已经在 WCF 数据协定中的任何对象。如果您有能力,则必须将 [Serializable] 标记添加到类中,或者将 WCF 特定标记添加到它们。这不是开玩笑的意思,即使您将 Type 添加到 KnownTypes,这并不意味着 WCF 会知道它是可序列化的。

如果这些选项都不可用,我建议创建“代理”对象,这些对象可以包含您要传递的值,并让这些值与您的目标对象相互转换。听起来很疯狂,但事情就是这样……

添加示例:

WCF 利用System.Runtime.Serialization命名空间中包含的序列化程序将数据序列化和反序列化为不同格式(XML、SOAP、JSON、二进制)。为了使其工作,要序列化的对象必须是某种可序列化类型(用属性标记)。数据对象的内置 WCF 属性如下所示:

//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 还可以利用标记为Serializable的类(例如 DataTable 和 DataSet)。

//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;
}

创建“代理”对象的想法与添加服务引用时 Visual Studio 生成的想法相同,只是相反。假设您有不可序列化的类“Foo2”,并且具有某些属性。

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

    public string Property2{get;set;}

    public int Property3{get;set;}
}

您可以创建一个代理类(几乎以任何您想要的方式),它允许您在服务之间来回传递属性。

[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;
    }
}

我确信关于如何做到这一点可能有 100,000 种不同的方式和意见,但这只是当时对我有用的可能性的一个例子。如果你看一下CodeProject 上的这篇文章,你可以看到我在这里拍摄的内容。

在您的特定情况下,您可能需要为 EF 创建的 Question 和 Decision 类型创建“代理”对象(或任何您想调用的包装对象),因为它们似乎不是天生可序列化的,除非您可以进入为这些对象生成的代码并添加如上所述的属性。另一个考虑因素是,如果它们是从另一个类(或抽象类)派生的,那么基类也必须标记为可序列化或 DataContract!

于 2012-10-18T17:11:11.380 回答
2

这里的问题是当您使用QuestionnaireEntities查询数据库时获得的对象属于代理类型,而不是Questionnaire(或您定义的其他类型)类型。

您可以测试任何返回对象的调用 .GetType() 。

创建代理以支持延迟加载。它们仅在您尝试访问属性时加载数据。

如果您从 WCF 服务返回它们,您必须告诉实体框架停止创建代理。

您可以通过编写以下代码来做到这一点:

db.Configuration.ProxyCreationEnabled = false;

注意:据我所知,关闭代理也应该有效地关闭延迟加载。根据我对实体框架的经验,您有两个选择,延迟加载或完全不加载

因此,禁用延迟加载后,所有导航属性都将为null。要让 EF 也加载这些属性的值,您需要在查询中使用Include()方法。

于 2012-10-18T18:11:08.747 回答