Para aqueles que trabalham com LINQ To SQL e querem expor as entidades geradas pela ferramenta via WCF, terá que tomar alguns cuidados. Quando essas entidades não mantém um relacionamento, algo que é bem díficil, você consegue retorná-las a partir das operações que o serviço irá disponibilizar.
O problema começa a aparecer quando você deseja enviar para os clientes, entidades que possuem relacionamentos com outras entidades. Um exemplo clássico e que ilustra bem o cenário, é quando temos categorias e produtos, onde cada produto deverá pertencer à somente uma única categoria. Neste caso, temos uma relação direta entre elas. Mas o ponto que torna isso difícil é que o LINQ To SQL cria uma relação bidirecional entra elas. Isso quer dizer que a entidade Categoria terá uma propriedade chamada Produtos, que como o próprio nome diz, retorna a coleção dos produtos daquela categoria; além disso, a classe Produto expõe uma propriedade chamada Categoria, que retorna a instância de uma categoria em qual o produto está contido.
Quando falamos em orientação à objetos, isso é perfeitamente válido e comum. O problema ocorre quando você tenta serializar essa estrutura a partir do WCF. Como eu comentei neste outro post, o WCF tem um comportamento diferente quando há referências circulares, e não conseguirá fazer a serialização porque ele ficará em uma espécie de loop, pois com há referência bidirecional, ao serializar uma categoria, ela serializa os respectivos produtos, e para cada produto a sua respectiva categoria, e para esta categoria os seus produtos, e por aí vai. O serviço roda sem maiores problemas, mas você terá uma exceção quando quando o cliente tentar acessá-lo.
Analisando a imagem abaixo, podemos visualizar a estrutura das classes que compõem o exemplo, e logo ao lado, você pode reparar nas propriedades do arquivo (superfície) DBML, verá que existe uma propriedade chamada Serialization, que pode receber apenas dois valores: None e Unidirectional. O primeiro deles permite que as propriedades das entidades sejam serializadas de acordo com as regras impostas pelo serializador padrão do WCF, que graças a possibilidade de serializar qualquer propriedade, mesmo que elas não estejam decoradas com os atributos DataContractAttribute e DataMemberAttribute (POCO). A segunda opção, Unidirectional, faz com que ele somente consiga serializar o relacionamento em uma única direção para evitar as referências circulares. No nosso caso, teremos a entidade Categoria com a propriedade Produtos, mas a classe Produto não terá uma propriedade que define a sua categoria.
Alterando a opção de serialização para Unidirectional, a forma que você efetua a consulta também terá que mudar. Isso fará com que o LINQ To SQL não consiga trazer os dados (produtos) relacionados aquela categoria, algo que é transparente quando estamos utilizando o LINQ To SQL diretamente. Para conseguir fazer com que os dados relacionados também sejam carregados, temos que recorrer a classe DataLoadOptions, como é mostrado abaixo:
public Categoria[] RecuperarCategorias()
{
using (DBContextDataContext ctx = new DBContextDataContext())
{
DataLoadOptions opts = new DataLoadOptions();
opts.LoadWith<Categoria>(c => c.Produtos);
ctx.LoadOptions = opts;
return (from c in ctx.Categorias select c).ToArray();
}
}
Mas como disse acima, isso funcionará mas você perderá a navegação bidirecional. Felizmente, podemos recorrer à propriedade boleana IsReference, que é exposta pelo atributo DataContractAttribute, definindo isso na classe Categoria. Isso permitirá a criação da navegação bidirecional, mas há um trabalho manual a ser feito para que isso funcione. Quando você muda a propriedade Serialization para None, nenhuma das propriedades é decorada com o atributo DataContractAttribute/DataMemberAttribute; já se definir essa propriedade para Unidirectional, as propriedades que são problemáticas, não estarão decoradas com o atributo DataMemberAttribute.
Sendo assim, o exemplo final fica como é mostrado abaixo, conseguindo ter no cliente, a navegação bidirecional. Obviamente que alguns membros foram omitidos por questões de espaço.
[Table(Name = “dbo.Categoria”)]
[DataContract(IsReference = true)]
public partial class Categoria
{
[DataMember]
[Column(…)]
public int CategoriaId
[DataMember]
[Column(…)]
public string Nome
[DataMember]
[Association(…)]
public EntitySet<Produto> Produtos
}
[Table(Name = “dbo.Produto”)]
public partial class Produto
{
[Column(…)]
public int ProdutoId
[Column(…)]
public int CategoriaId
[Column(…)]
public string Nome
[Association(…)]
public Categoria Categoria
}
Excelente artigo Israel, muito esclarecedor.
Mas ainda fiquei com uma dúvida: e no caso de um método hipotético que retorne o objeto Categoria dado o nome de um produto?
Exemplo: Categoria ObterCategoriaDoProduto(string nomeProduto) { … };
Em meus testes, o utilitário ‘wcftestclient’ não retorna nenhum valor quando invoco o serviço.
Boas Marcos,
Há também alguns cuidados. Em breve eu vou postar por aqui. Já está no BackLog.
Israel, favor desconsiderar minha questão, verifiquei que eu estava cometendo um erro no código. Obrigado!