Dilema: Lazy Loading ou DataContractSerializer? (Post-Aspirina)

Fala pessoal, beleza?

Sensibilizado com os muitos prováveis mortais que vão esbarrar neste problema, resolvi postar um troubleshooting sobre o que aconteceu comigo ontem.

Cenário: Você está usando Entity Framework 4, gerou classes POCO através do modelo conceitual (com ou sem T4), está com Lazy Loading habilitado e quer distribuir estas classes em um serviço WCF.

Após decorar todas as classes e estar aparentemente pronto, tudo começa com uma bela e abrangente exceção do WCF:

“The underlying connection was closed: The connection was closed unexpectedly.”

Se você tiver esperança, como funcionou comigo, vai querer ligar o tracing do WCF. Existem inúmeras formas de configurá-lo, a que eu usei foi simples e good-enough:

<system.diagnostics>
    <sources>
      <source name="System.ServiceModel"
              switchValue="Information, ActivityTracing"
              propagateActivity="true">
        <listeners>
          <add name="traceListener"
              type="System.Diagnostics.XmlWriterTraceListener"
              initializeData= "c:\temp\NomeDoSeuArquivoDeLog.svclog" />
        </listeners>
      </source>
    </sources>
  </system.diagnostics>

Adicione o trecho XML acima no Web.config do seu serviço e tudo estará pronto para começarmos a perseguir o verdadeiro problema. Se você está no mesmo cenário que eu estive e descrevi acima, vai encontrar no SvcTraceViewer.exe a seguinte mensagem da exceção:

“There was an error while trying to serialize parameter http://tempuri.org/:SUA_OPERACAO_AQUI. The InnerException message was ‘Type ‘System.Data.Entity.DynamicProxies.SUA_ENTIDADE_AQUI_7446BE543376C2D0E2C8BCE8BE89AC8659C6C2397A28812EBD37F48756075A0D’ with data contract name ‘SUA_ENTIDADE_AQUI_7446BE543376C2D0E2C8BCE8BE89AC8659C6C2397A28812EBD37F48756075A0D:http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies&#8217; 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.”

A exceção acima esclarece bastante coisa. Entre linhas ela nos diz que o DataContractSerializer não sabe fazer resolução dos DynamicProxies gerados pelo Entity Framework (os DynamicProxies existem para que seja possível utilizar o recurso de Lazy Loading). A grosso modo, desabilitamos a criação de proxys do Lazy Loading ou interceptamos a criação do DataContractSerializer.

Opção 1: Desabilitar a criação de proxies para o Lazy-Loading

meuDataContext.ContextOptions.ProxyCreationEnabled = false;

Opção 2: Implementar um OperationBehavior que utilize o DataContractResolver e acioná-lo via atributo (nas operações do contrato do serviço).

  1. Crie um Assemly e referencie System.Data.Entity, System.Runtime.Serialization e System.ServiceModel
  2. Crie uma classe chamada ApplyDataContractResolverAttribute com o seguinte código:
public class ApplyDataContractResolverAttribute : Attribute, IOperationBehavior
{
   public ApplyDataContractResolverAttribute()
   {
   }

   public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
   {
   }

   public void ApplyClientBehavior(OperationDescription description, System.ServiceModel.Dispatcher.ClientOperation proxy)
   {
      DataContractSerializerOperationBehavior dataContractSerializerOperationBehavior =
      description.Behaviors.Find<DataContractSerializerOperationBehavior>();
      dataContractSerializerOperationBehavior.DataContractResolver =
      new ProxyDataContractResolver();
   }

   public void ApplyDispatchBehavior(OperationDescription description, System.ServiceModel.Dispatcher.DispatchOperation dispatch)
   {
      DataContractSerializerOperationBehavior dataContractSerializerOperationBehavior =
      description.Behaviors.Find<DataContractSerializerOperationBehavior>();
      dataContractSerializerOperationBehavior.DataContractResolver =
      new ProxyDataContractResolver();
   }

   public void Validate(OperationDescription description)
   {
      // Do validation.
   }
}

Pronto! Agora compile o assembly, referencie-o no serviço e decore as operações do contrato (em que deseja trafegar POCOs com DynamicProxy):

[ServiceContract]
public partial interface IMyService
{
	// Operação com DynamicProxys do Entity Framework
	[OperationContract]
        	[ApplyDataContractResolver]
	DynamicFooResponse DynamicFoo (DynamicFooRequest request);
	// Operação sem DynamicProxys do Entity Framework
	[OperationContract]
	FooResponse Foo (FooRequest request);
}

Guarde este assembly no seu WCF-Bag-Of-Tricks!  Bom pessoal, existem milhares de artigos sobre este problema (inclusive na MSDN), mas não podia deixar de fazer a versão português-comentada de um problema tão importante quanto este. Até a próxima!  Stay Lazy-Loaded!