none
Erro WCF Web Service RRS feed

  • Pergunta

  • Olá.

    Estou executando meu projeto WCF que é compilado normalmente, porém quando abre a página (no browser) a seguinte mensagem é mostrada.

    'objeto' não está marcado com OptionalFieldAttribute, indicando que deve ser serializado. No entanto, 'class' deriva de uma classe marcada com DataContractAttribute e uma configuração IsReference de 'True'. Não é possível ter membros de dados obrigatórios nas classes IsReference. Decore 'objeto' com OptionalFieldAttribute ou desabilite a configuração IsReference na classe pai apropriada.

    Minha class possui um objeto que vem do Entity Framework, não sei se isso influencia em algo.

    Alguém pode ajudar?

    Obrigado.

    Marcos Aguiar Jr - Brazil
    sábado, 12 de setembro de 2009 11:54

Respostas

  • Boas Marcos,

    É aí o problema. Você não pode expor a classe de contexto do Entity Framework, pois ela não é serializável.

    O teu serviço precisa, na verdade, expor as classes de modelo, que também são criadas pelo Entity Framework durante a extração dos metadados do banco de dados. O contexto do EF deve ser mantido internamente. O teu serviço será a ponte entre os clientes e o teu framework de persistência, que nesta caso é o EF.
    http://www.israelaece.com
    • Marcado como Resposta Marcos Aguiar Jr segunda-feira, 14 de setembro de 2009 18:16
    segunda-feira, 14 de setembro de 2009 17:18
    Moderador
  • Boas Marcos,

    Sim, você pode expor diretamente a sua classe de Modelo gerada pelo EF, seguinte apenas os cuidados/detalhes mencionados neste artigo: http://msdn.microsoft.com/en-us/magazine/cc700340.aspx.

    Muitas vezes, você não quer expor, na íntegra, a sua classe de modelo de dados, aí faz sentido você criar uma segunda classe (DTO), para trafegar os dados entre as partes.
    http://www.israelaece.com
    • Marcado como Resposta Marcos Aguiar Jr segunda-feira, 14 de setembro de 2009 18:16
    segunda-feira, 14 de setembro de 2009 17:25
    Moderador

Todas as Respostas

  • Boas Marcos,

    Expor classes que vem do Entity Framework, deve ter um tratamento especial. Qual a sua estrutura para tentar simular aqui?
    http://www.israelaece.com
    segunda-feira, 14 de setembro de 2009 11:19
    Moderador
  • Olá Israel..

    Tenho uma classe em um projeto denominado Bean que chama Banco (instituição financeira) que é herdada da classe do EF.

    Exemplo
        public class BeanBancoCADASTRO: EF.BANCO
        {
            public EF.Entities ent;
        }

    Veja que a class é herdada do EF.

    Em outro projeto Web Site - WCF Service onde tenho a interface abaixo:

    public interface IBancos
    {
        [OperationContract]
        BeanBancoCADASTRO SelecionarBanco(string strCodigoBanco);
    
        [OperationContract]
        IEnumerable<BeanBancoCADASTRO> ListarBancos();
    
        [OperationContract]
        bool GravarBanco(BeanBancoCADASTRO beanBanco);
    
        [OperationContract]
        bool DeletarBanco(string strCodigoBanco);
    }
    Quando compilo acontece o erro que mostrei no post anterior. Veja que alguns métodos tem como parâmetro o objeto BeanBancoCADASTRO.

    Isso ajuda Israel?

    Obrigado.





    Marcos Aguiar Jr - Brazil
    segunda-feira, 14 de setembro de 2009 12:00
  • Boas Marcos,

    Tente aplicar os atributos DataContractAttribute e DataMemberAttribute (ou com Serializable):

    [DataContract]
    public class BeanBancoCADASTRO: EF.BANCO
    {
        [DataMember]
        public EF.Entities ent;
    }

    http://www.israelaece.com
    segunda-feira, 14 de setembro de 2009 13:15
    Moderador
  • Olá Israel

    Acho que não da para usar o DataContratc porque a minha classe BeanBancoCADASTRO fica em outro projeto (projeto class library). O que eu tentei fazer mas não deu certo, foi o exemplo abaixo:

    [Serializable]
    public class BeanBancoCADASTRO: EF.BANCO
     {
         [OptionalFieldAttribute]
         public EF.Entities ent;
     }
    Mas não funcionou.

    você esta dizendo para eu suar o DataContract e o DataMember mesmo que a class esteva em outro projeto?

    Obrigado.

    Marcos Aguiar Jr - Brazil
    segunda-feira, 14 de setembro de 2009 13:38
  • Boas Marcos,

    Defina explicitamente DataContract e DataMember:

    [DataContract]
    public class BeanBancoCADASTRO: EF.BANCO
    {
        [DataMember]
        public EF.Entities ent;
    }
    http://www.israelaece.com
    segunda-feira, 14 de setembro de 2009 13:59
    Moderador
  • Israel..

    O erro mudou para:

    ExceptionDetail, provavelmente criado por IncludeExceptionDetailInFaults=true, cujo valor é: System.InvalidOperationException: Foi emitida uma exceção em uma chamada a uma extensão de exportação WSDL: System.ServiceModel.Description.DataContractSerializerOperationBehavior contrato: http://tempuri.org/:IBancos ----> System.Runtime.Serialization.InvalidDataContractException: O tipo 'System.Data.Objects.ObjectContext' não pode ser serializado. Considere marcá-lo com o atributo DataContractAttribute e marcar todos os membros que deseja serializar com o atributo DataMemberAttribute. Consulte a documentação do Microsoft .NET Framework para obter outros tipos suportados.

    Ele não está conseguindo Serializar o ObjectContext é isso?

    Obrigado.

    Marcos Aguiar Jr - Brazil
    segunda-feira, 14 de setembro de 2009 14:18
  • Boas Marcos,

    O que pode estar ocorrendo é que existe alguma classe que está definida como internal, que o serializador não está conseguindo manipular. Todas as classes envolvidas estão marcadas como public?

    Você não está expondo o ObjectContext, né?

    http://www.israelaece.com
    segunda-feira, 14 de setembro de 2009 14:20
    Moderador
  • Israel.

    Como assim exponto o ObjectContext não compreendi.

    na verdade a class tem um atributo privado e uso a propriedade com get e set e nela que estou colocando o DataMember.

    Obrigado.

    Marcos Aguiar Jr - Brazil
    segunda-feira, 14 de setembro de 2009 14:41
  • Boas Marcos,

    Pode me mostrar um exemplo?
    http://www.israelaece.com
    segunda-feira, 14 de setembro de 2009 14:44
    Moderador
  • Segue o exemplo

    [DataContract]
        public class BeanBancoCADASTRO : EF.Cadastro.BANCO
        {
            #region Atributos
            private Consts.oprDB oprDB;
    
            [DataMember]
            private EF.Cadastro.EFCadastro cEFCadastro;
            #endregion
    
            #region Construtor
            public BeanBancoCADASTRO()
            {
                this.InitializeComponent();
            }
            #endregion
    
            #region Initialização dos Componentes
            private void InitializeComponent()
            {
                this.oprDB = Consts.oprDB.oprNull;
            }
            #endregion
    
            #region Propriedades
            [DataMember]
            public EF.Cadastro.EFCadastro CEFCadastro
            {
                get { return this.cEFCadastro; }
                set { this.cEFCadastro = value; }
            }
    
            [DataMember]
            public Consts.oprDB OprDB
            {
                get { return this.oprDB; }
                set { this.oprDB = value; }
            }
            #endregion
        }

    Esse Consts é uma class que possui constantes e esse oprDB é um objeto do tipo enum.

    Obrigado

    Marcos Aguiar Jr - Brazil
    segunda-feira, 14 de setembro de 2009 14:51
  • Boas Marcos,

    E os teus tipos Consts.oprDB e EFCadastro estão marcados para serem serializáveis? (DataContractAttribute/DataMemberAttribute ou SerializableAttribute)
    http://www.israelaece.com
    segunda-feira, 14 de setembro de 2009 14:55
    Moderador
  • Na class de consts eu fiz assim:

        [Serializable]
        public class Consts
        {
            // Operações com Banco de Dados
            public enum oprDB 
            { 
                oprNull = 0, 
                oprInsert = 1, 
                oprUpdate = 2, 
                oprDelete = 3 
            };
        }
    Está certo assim?

    Agora no EF não fiz nada, onde devo fazer isso? no arquivo edmx?

    Marcos Aguiar Jr - Brazil
    segunda-feira, 14 de setembro de 2009 15:01
  • Boas Marcos,

    O Enum deveria ser marcado da seguinte forma:

    [DataContract]
    public enum oprDB

        [EnumMember]
        oprNull = 0, 

        [EnumMember]
        oprInsert = 1, 

        [EnumMember]
        oprUpdate = 2,

        [EnumMember] 
        oprDelete = 3
    }

    Por padrão, as classes do EF já vem marcadas como serializáveis.


    http://www.israelaece.com
    segunda-feira, 14 de setembro de 2009 15:05
    Moderador
  • Ola Israel

    Entao o EF.Cadastro.EFCadastro nao precisa ser serializável? do jeito que está na class seria para funcionar?

    Marcos Aguiar Jr - Brazil
    segunda-feira, 14 de setembro de 2009 15:12
  • Outro detalhe é que a minha Camada EF é uma DLL nesse projeto, pode ter problema com relação a isso também no caso da serialização?
    Marcos Aguiar Jr - Brazil
    segunda-feira, 14 de setembro de 2009 15:14
  • Boas Marcos,

    O problema pode ocorrer se você tem algum membro definido como "internal" e está expondo ele através do seu serviço: http://www.israelaece.com/post/Serializacao-de-tipos-internos.aspx
    http://www.israelaece.com
    segunda-feira, 14 de setembro de 2009 15:18
    Moderador
  • Israel

    não estou utilizando nenhum membro como internal. Acredito que o problema ainda esteva no EF, porque se comento onde estou criando o objeto EFCadastro, a aplicação funciona. Como faço para ver se o EF está serializavel? você saberia como?

    Obrigado.

    Marcos Aguiar Jr - Brazil
    segunda-feira, 14 de setembro de 2009 15:25
  • Boas Marcos,

    Para ir até a classe, basta você clicar com o botão direito do mouse e "Go To Definition". Certifique-se de que lá tenha o atributo Serializable.
    http://www.israelaece.com
    segunda-feira, 14 de setembro de 2009 15:31
    Moderador
  • Israel.

    Não é possível fazer isso, porque como eu disse minha camada EF é uma dll, se eu usou o "Go To Definition" ele abre um metadata e não vou consigo editar. Será que não posso utilizar o EF como uma dll porque não consigo serializa-lo?

    Obrigado.



    Marcos Aguiar Jr - Brazil
    segunda-feira, 14 de setembro de 2009 16:33
  • Boas Marcos,

    Sim, mas lá consegue ver se o atributo está ou não aplicado. Por padrão, ao criar uma classe no EF e expor a mesma através do WCF já deveria funcionar, pois ela já está com o atributo Serializable aplicado, que o WCF já entende.

    Atualmente, qual a mensagem de erro? Onde ela ocorre?
    http://www.israelaece.com
    segunda-feira, 14 de setembro de 2009 17:11
    Moderador
  • A mensagem de erro é a enviei anteriormente

    ExceptionDetail, provavelmente criado por IncludeExceptionDetailInFaults=true, cujo valor é: System.InvalidOperationException: Foi emitida uma exceção em uma chamada a uma extensão de exportação WSDL: System.ServiceModel.Description.DataContractSerializerOperationBehavior contrato: http://tempuri.org/:IBancos ----> System.Runtime.Serialization.InvalidDataContractException: O tipo 'System.Data.Objects.ObjectContext' não pode ser serializado. Considere marcá-lo com o atributo DataContractAttribute e marcar todos os membros que deseja serializar com o atributo DataMemberAttribute. Consulte a documentação do Microsoft .NET Framework para obter outros tipos suportados.

    Acontece quando eu acesso o arquivo svc.

    Fazendo o "Go to Definition" ele mostra isso:

    using System;
    using System.Data.EntityClient;
    using System.Data.Objects;
    
    namespace EF.Cadastro
    {
        public class EFCadastro : ObjectContext
        {
            public EFCadastro();
            public EFCadastro(EntityConnection connection);
            public EFCadastro(string connectionString);
    
            public ObjectQuery<BANCO> BANCO { get; }
            public void AddToBANCO(BANCO bANCO);
    
        }
    }
    


    Marcos Aguiar Jr - Brazil
    segunda-feira, 14 de setembro de 2009 17:15
  • Boas Marcos,

    É aí o problema. Você não pode expor a classe de contexto do Entity Framework, pois ela não é serializável.

    O teu serviço precisa, na verdade, expor as classes de modelo, que também são criadas pelo Entity Framework durante a extração dos metadados do banco de dados. O contexto do EF deve ser mantido internamente. O teu serviço será a ponte entre os clientes e o teu framework de persistência, que nesta caso é o EF.
    http://www.israelaece.com
    • Marcado como Resposta Marcos Aguiar Jr segunda-feira, 14 de setembro de 2009 18:16
    segunda-feira, 14 de setembro de 2009 17:18
    Moderador
  • Israel.

    Classe de modelo? ou seja eu preciso criar outra classe que seria uma cópia do EF?

    Marcos Aguiar Jr - Brazil
    segunda-feira, 14 de setembro de 2009 17:22
  • Boas Marcos,

    Sim, você pode expor diretamente a sua classe de Modelo gerada pelo EF, seguinte apenas os cuidados/detalhes mencionados neste artigo: http://msdn.microsoft.com/en-us/magazine/cc700340.aspx.

    Muitas vezes, você não quer expor, na íntegra, a sua classe de modelo de dados, aí faz sentido você criar uma segunda classe (DTO), para trafegar os dados entre as partes.
    http://www.israelaece.com
    • Marcado como Resposta Marcos Aguiar Jr segunda-feira, 14 de setembro de 2009 18:16
    segunda-feira, 14 de setembro de 2009 17:25
    Moderador
  • Israel a class modelo seria essa?


    [Serializable]
        [DataContract(IsReference = true)]
        [EdmEntityType(NamespaceName = "EF.Cadastro", Name = "BANCO")]
        public class BANCO : EntityObject
        {
            public BANCO();
    
            [EdmRelationshipNavigationProperty("EF.Cadastro", "EF_FK1_BANCOAGENCIA", "AGENCIA")]
            [SoapIgnore]
            [XmlIgnore]
            [DataMember]
            public EntityCollection<AGENCIA> AGENCIA { get; set; }
            [DataMember]
            [EdmScalarProperty(EntityKeyProperty = true, IsNullable = false)]
            public string CD_BANCO { get; set; }
            [DataMember]
            [EdmScalarProperty]
            public string CD_USUARIOATUALIZACAO { get; set; }
            [SoapIgnore]
            [DataMember]
            [EdmRelationshipNavigationProperty("EF.Cadastro", "EF_FK1_COOPERATIVA_BANCO", "COOPERATIVA")]
            [XmlIgnore]
            public COOPERATIVA COOPERATIVA { get; set; }
            [Browsable(false)]
            [DataMember]
            public EntityReference<COOPERATIVA> COOPERATIVAReference { get; set; }
            [EdmScalarProperty]
            [DataMember]
            public DateTime? DT_ATUALIZACAO { get; set; }
            [EdmScalarProperty]
            [DataMember]
            public string NM_BANCO { get; set; }
    
            public static BANCO CreateBANCO(string cD_BANCO);
        }


    Marcos Aguiar Jr - Brazil
    segunda-feira, 14 de setembro de 2009 17:49
  • Boas Marcos,

    Sim, pelo que mostrou no contrato acima, é.
    http://www.israelaece.com
    segunda-feira, 14 de setembro de 2009 17:50
    Moderador
  • Israel, então se eu quiser ter um objetc context na minha class eu não posso porque ele não é serializavel, é isso? Mas posso ter a class do EF modelo publicada porque ela é...
    Marcos Aguiar Jr - Brazil
    segunda-feira, 14 de setembro de 2009 17:55
  • Boas Marcos,

    Sim, você somente pode progapar classes serializáveis para os clientes.

    No caso especifíco do EF ou Linq To SQL, você precisa tomar alguns cuidados que são mostrados naqueles artigos acima.
    http://www.israelaece.com
    segunda-feira, 14 de setembro de 2009 17:58
    Moderador
  • Entao Israel, eu estava colocando o ObjectContext para controle de transação, se não é possível vou ter que pensar em uma outra forma, mas esse tema deve estar em outro tópico do forúm, obrigado pela ajuda e paciência. Pelo menos é possível herdar a class do EF (sem o object context) isso é bom, porque se amanhã eu adicionar um campo no banco de dados a minha class ja estara atualizada.

    Obrigado.

    Marcos Aguiar Jr - Brazil
    segunda-feira, 14 de setembro de 2009 18:15
  • Boas Marcos,

    Dependo do que precisa, você poderá detalhar melhor para que talvez consigamos lhe ajudar.

    Com relação a atualização, tem que tomar cuidado, já que se você adicionar um novo campo no banco e, consequentemente, no seu modelo, mas você precisará atualizar o cliente. Dependendo do seu cenário, compartilhar a DLL que contém o modelo, seria uma boa opção: http://www.israelaece.com/post/Compartilhando-tipos-entre-o-servico-e-o-cliente.aspx.

    Para lidar com possíveis evoluções da sua class, uma boa prática é aplicar a interface IExtensibleDataObject: http://www.israelaece.com/post/(De)Serializacao-de-Objetos-em-WCF.aspx.
    http://www.israelaece.com
    segunda-feira, 14 de setembro de 2009 18:19
    Moderador
  • Seguinte, nao é o tema desse forúm mas se for possível.. vou colocar um exemplo.

    Tenho 2 classes

    Classe Emprestimo
    
    [DataContract]
    public class BeanBancoCADASTRO : EF.Cadastro.EMPRESTIMO
    {
        [DataMember]
        public Consts.oprDB OprDB
        {
            get { return this.oprDB; }
            set { this.oprDB = value; }
        }
    }
    
    
    Classe ParcelaEmprestimo
    
    [DataContract]
    public class BeanBancoCADASTRO : EF.Cadastro.PARCELAEMPRESTIMO
    {
        [DataMember]
        public Consts.oprDB OprDB
        {
            get { return this.oprDB; }
            set { this.oprDB = value; }
        }
    }
    Veja que ambas são herdadas da camada EF.

    tenho mais duas classes com métodos de insert, update, delete. Uma para cada class. Nessa classe eu tenho um Object Context null.

    Abaixo o exemplo apenas do insert

    Class Emprestimo
    
    public EMPRESTIMO InserirEmprestimo(EMPRESTIMO emprestimo, Const.Operacao op)
            {
                if (emprestimo.ent == null)
                    this.ent = new BusinessEntities();
                else
                    this.ent = emprestimo.ent;
    
    
                EMPRESTIMO emp = new EMPRESTIMO();
    
                //emprestimo.CodigoEmprestimo = ContadorDao.IDContador(Const.tabEmprestimo);
    
                Popular(emp, emprestimo);
    
                if (op == Const.Operacao.oprInsert)
                    ent.AddToEMPRESTIMO(emp);
                else if (op == Const.Operacao.oprDelete)
                    ent.DeleteObject(emp);
    
                return emp;
            }

    Class Parcela Emprestimo

    public EMPRESTIMO InserirParcelaEmprestimo(ParcelaEmprestimo emprestimo, Const.Operacao op)
            {
                if (emprestimo.ent == null)
                    this.ent = new BusinessEntities();
                else
                    this.ent = emprestimo.ent;
    
    
                PARCELAEMPRESTIMO emp = new PARCELAEMPRESTIMO();
    
                //emprestimo.CodigoEmprestimo = ContadorDao.IDContador(Const.tabParcelaEmprestimo);
    
                Popular();
    
                if (op == Const.Operacao.oprInsert)
                    ent.AddToEMPRESTIMO(emp);
                else if (op == Const.Operacao.oprDelete)
                    ent.DeleteObject(emp);
    
                return emp;
            }

    A rotina que chama essas classes esta abaixo

            private void caInserirEmprestimo_ExecuteCode(object sender, EventArgs e)
            {
                BusinessEntities ent = new BusinessEntities();
    
                try
                {
                    using (TransactionScope transaction = new TransactionScope())
                    {
                        ent.Connection.Open();
    
                        
                        associado.Emprestimo.ent = ent;
    
                        EmprestimoDao empDao = new EmprestimoDao();
                        EMPRESTIMO emp = empDao.InserirEmprestimo(associado.Emprestimo, Operacao);
    
                        ParcelaEmprestimoDao parDao = new ParcelaEmprestimoDao();
    
                        for (int i = 1; i <= associado.Emprestimo.NumeroParcela; i++)
                        {
                            ParcelaEmprestimo par = (associado.Emprestimo.ParcelaEmprestimo[i - 1] as ParcelaEmprestimo);
                            par.ent = ent;
    
                            parDao.InserirEmprestimo(emp, par);
                        }
    
                        ent.SaveChanges(false);
                        transaction.Complete();
    
                        ent.AcceptAllChanges();
                    }
                }
                finally
                {
                    ent.Dispose();
                }
            }


    O que eu pensei em fazer foi criar o object context na class emprestimo, assim eu passava ele por referencia para o metodo inserir emprestimo e depois para o metodo inserir parcelaemprestimo. Preciso usar o mesmo object context por dois motivos. 

    1 - A uma ligação FK entre EMPRESTIMO e PARCELAEMPRESTIMO, entao precisam estar no mesmo object context senão um erro irá ocorrer.
    2 - Se não tiver no mesmo object context pode acontecer se um insert ser feito em EMPRESTIMO, um erro acontecer em PARCELAEMPRESTIMO e o rollback não acontecer.

    acho que o exemplo está meio confuso, se nao entender algo por favor avise.

    Obrigado.


    Marcos Aguiar Jr - Brazil
    segunda-feira, 14 de setembro de 2009 19:17