Pontos de dados

Desmistificando estratégias do Entity Framework: carregando dados relacionados

Julie Lerman

Julie LermanNa coluna Pontos de Dados do último mês, forneci diretrizes de alto nível para a escolha da estratégia de fluxo de trabalho de modelagem das opções Database First, Model First e Code First. Neste mês, abordarei outra importante escolha que você precisará fazer: como recuperar dados relacionados de seu banco de dados. Você pode usar carregamento rápido, carregamento explícito, carregamento lento ou mesmo projeções de consultas.

Essa não é uma decisão única, entretanto, pois diferentes cenários em seu aplicativo podem exigir diferentes estratégias de carregamento de dados. Portanto, é bom estar ciente de cada estratégia de modo que você possa escolher a correta para o trabalho.

Como exemplo, digamos que você tenha um aplicativo que rastreie os animais de estimação da família, e seu modelo tenha uma classe Family e uma classe Pet com uma relação um-para-muitos entre Family e Pet. Digamos que você queira recuperar informações sobre uma família e seus animais de estimação.

Na próxima coluna, continuarei esta série tratando das várias escolhas que você tem para consultar o Entity Framework usando LINQ to Entities, Entity SQL e variações de cada uma dessas opções. Mas, nesta coluna, usarei apenas LINQ to Entities nos exemplos.

Carregamento rápido em uma única viagem ao banco de dados

O carregamento rápido permite trazer todos os dados do banco de dados em uma única viagem. O Entity Framework oferece o método Include para permitir isso. Include usa uma cadeia de caracteres representando um caminho de navegação para os dados relacionados. Eis um exemplo do método Include que retornará gráficos, cada um contendo uma família e um conjunto de animais de estimação:

from f in context.Families.Include("Pets") select f

Se seu modelo tiver outra entidade chamada VetVisit e esta tiver uma relação um-para-muitos com Pet, você poderá trazer as famílias, seus animais de estimação e as visitas ao veterinário tudo de uma vez:

from f in context.Families.Include("Pets.VetVisits") select f

Os resultados do carregamento rápido são retornados como gráficos de objeto, conforme mostrado na Figura 1.

Figura 1 Gráfico de objeto retornado pela consulta de carregamento rápido

Id Name Pets          
    Id Name Type VetVisits    
2 LermanJ 2 Sampson Dog 1 2/1/2011 Excellent
5 4/1/2011 Nail Clipping
4 Sissy Cat 1 3/2/2011 Excellent
3 GeigerA 3 Pokey Turtle 3 2/5/2011 Excellent
4 Riki Cat 6 4/8/2011 Excellent

Include é muito flexível. Você pode usar diversos caminhos de navegação de uma vez e também pode navegar para entidades pai ou por relações muitos-para-muitos.

O carregamento rápido com Include é muito conveniente, de fato, mas se usado muito — se você colocar muitos Includes em uma única consulta ou muitos caminhos de navegação em um único Include — ele pode prejudicar o desempenho da consulta muito rapidamente. A consulta nativa que o Entity Framework cria terá muitas associações e, para acomodar o retorno dos gráficos solicitados, a forma dos resultados do banco de dados pode ser muito mais complexa do que o necessário ou pode retornar mais resultados do que necessário. Isso não significa que você deve evitar o Include, mas que deve filtrar suas consultas do Entity Framework para ter certeza de que não está gerando consultas com mau desempenho. Nos casos em que as consultas nativas são particularmente ruins, você deve repensar sua estratégia de consulta para a área do aplicativo que está gerando essa consulta.

Carregamento lento em viagens adicionais ao banco de dados

Geralmente, ao recuperar dados, você não deseja ou precisa dos dados relacionados imediatamente, ou pode não desejar dados relacionados para todos os resultados. Por exemplo, você pode precisar inserir todas as famílias em seu aplicativo, mas, depois, recuperar somente os animais de estimação dessas pessoas. Faz sentido, nesse caso, fazer o carregamento rápido de todos os animais de estimação de cada entidade de pessoa com o Include? Provavelmente não.

O Entity Framework oferece duas maneiras de carregar os dados relacionados após o fato. A primeira é chamada carregamento lento e, com as configurações apropriadas, isso acontece automaticamente.

Com o carregamento lento, você simplesmente precisa fazer alguma referência aos dados relacionados, e o Entity Framework verificará se eles foram carregados na memória. Se não foram, o Entity Framework criará e executará uma consulta em segundo plano, preenchendo os dados relacionados.

Por exemplo, se você executar uma consulta para obter alguns objetos de Family, depois disparar o Entity Framework para obter os Pets de um desses Families simplesmente fazendo uma menção, por assim dizer, da propriedade Pets, o Entity Framework recuperará Pets para você:

var theFamilies= context.Families.ToList();
var petsForOneFamily = theFamilies[0].Pets;

No Entity Framework, o carregamento lento é ativado ou desativado usando a propriedade ObjectContext ContextOptions.LazyLoadingEnabled. Por padrão, o Visual Studio definirá modelos recém-criados para definir LazyLoadingEnabled como verdadeiro, sendo o resultado a ativação por padrão com novos modelos.

Ter o carregamento lento ativado por padrão ao criar uma instância de um contexto pode ser uma grande escolha para seu aplicativo, mas também pode ser um problema para os desenvolvedores que não estejam cientes desse comportamento. Você pode disparar viagens extras ao banco de dados sem perceber. Compete a você estar ciente do uso do carregamento lento, e você poderá escolher explicitamente usá-lo ou não — ativando-o ou desativando-o em seu código ao definir LazyLoadingEnabled como verdadeiro ou falso, conforme necessário.

O carregamento lento é direcionado pelas classes EntityCollection e EntityReference e, portanto, não estará disponível por padrão quando você estiver usando classes Plain Old CLR Object (POCO) — mesmo se LazyLoadingEnabled for verdadeiro. Entretanto, o comportamento de proxy dinâmico do Entity Framework, disparado pela indicação das propriedades de navegação como virtuais ou substituíveis, criará um proxy de tempo de execução que permitirá que seus POCOs sejam carregados lentamente.

O carregamento lento é um ótimo recurso para se ter disponível no Entity Framework, mas apenas se você estiver ciente de quando ele está ativo e considerar fazer escolhas de quando é apropriado ou não. Por exemplo, o artigo da MSDN Magazine “Usando o Entity Framework para reduzir a latência de rede para o SQL Azure” (msdn.microsoft.com/magazine/gg309181) destaca as implicações de usar o carregamento lento em um banco de dados em nuvem a partir de um servidor local. Filtrar as consultas de banco de dados executadas pelo Entity Framework é uma parte importante na escolha das estratégias de carregamento.

Também é importante estar ciente de quando o carregamento lento está desativado. Se LazyLoadingEnabled for definido como falso, a referência a Families[0].Pets no exemplo anterior não dispararia uma consulta de banco de dados que poderia informar que a família não tem animais de estimação, mesmo que possa haver alguns no banco de dados. Portanto, se você estiver dependendo do carregamento lento, certifique-se de que ele esteja ativado.

Carregamento explícito em viagens adicionais ao banco de dados

Você pode desejar deixar o carregamento lento desativado e ter mais controle explícito sobre quando os dados relacionados são carregados. Além de carregar explicitamente com Include, o Entity Framework permite recuperar seleta e explicitamente dados relacionados usando um de seus métodos de carregamento.

Se você gerar classes de entidade usando o modelo de geração de código padrão, as classes herdarão de EntityObject, com dados relacionados expostos em uma EntityCollection ou uma EntityReference. Ambos os tipos têm um método de carregamento que você pode chamar para forçar o Entity Framework a recuperar os dados relacionados. Eis um exemplo de carregamento de uma EntityCollection dos objetos Pets. Observe que Load não tem um valor de retorno:

var theFamilies = context.Families.ToList();
theFamilies[0].Pets.Load();
var petsForOneFamily = theFamilies[0].Pets;

O Entity Framework criará e executará uma consulta que preenche a propriedade relacionada — o conjunto de Pets de Family — e, então, você poderá trabalhar com Pets.

A segunda maneira de carregar explicitamente é usando ObjectContext, em vez de EntityCollection ou EntityReference. Se você estiver dependendo do suporte a POCO no Entity Framework, suas propriedades de navegação não serão EntityCollections ou EntityReferences e, portanto, você não terá o método Load. Em vez disso, você pode usar o método ObjectContext.LoadProperty. LoadProperty usa genéricos para identificar o tipo que você está carregando e, então, uma expressão lambda para especificar qual propriedade de navegação carregar. Aqui está um exemplo do uso de LoadProperty para recuperar Pets de uma determinada instância de pessoa:

context.LoadProperty<Family>(familyInstance, f => f.Pets)

Projeções de consulta como uma alternativa de carregamento

Não se esqueça de que você também pode ter a opção de usar projeções em suas consultas. Por exemplo, você pode escrever uma consulta para recuperar entidades, mas filtrar quais dados relacionados serão recuperados:

var famsAndPets=from family in context.Families
  select new {family,Pets=family.Pets.Any(p=>p.Type=="Reptile")};

Isso retornará todas as famílias e todos os animais de estimação de quaisquer famílias que tenham répteis — tudo em uma única viagem ao banco de dados. Mas, em vez de um gráfico da família com seus animais de estimação, a consulta famsAndPets retornará um conjunto de tipos anônimos com uma propriedade para Family e outra para Pets (consulte a Figura 2).

Figura 2 Tipos anônimos projetados com as propriedades de Family e Pets

Family Pets      
Id Name Id Name Type
2 LermanJ      
3 GeigerA 3 Pokey Turtle
4 Riki Cat

Avalie os prós e os contras

Agora, você tem quatro estratégias disponíveis para recuperar os dados relacionados. Eles precisam não ser mutuamente exclusivos em seu aplicativo. Você pode ter encontrado um motivo muito bom para cada um desses diferentes recursos em vários cenários ao longo de seus aplicativos. Os prós e contras devem ser considerados antes de escolher a estratégia correta para cada caso.

O carregamento rápido com Include é útil para cenários em que você sabe antecipadamente que deseja os dados relacionados de todos os dados principais sendo consultados. Mas lembre-se de duas desvantagens em potencial. Se você tiver muitos Includes ou caminhos de navegação, o Entity Framework poderá gerar uma consulta com fraco desempenho. E você deve ter cuidado com o retorno de mais dados relacionados do que o necessário graças à facilidade de codificar com Include.

O carregamento lento recupera convenientemente os dados relacionados em segundo plano em resposta ao código que simplesmente faz menção aos dados relacionados. Ele também torna a codificação mais simples, mas você deve ter consciência de quanta interação ele está gerando com o banco de dados. Ele pode gerar 40 viagens ao banco de dados, quando apenas uma ou duas seriam necessárias.

O carregamento explícito oferece mais controle sobre quando os dados relacionados são recuperados (e quais dados), mas se você não conhecer essa opção, seu aplicativo poderá ser mal-informado sobre a presença de dados em seu banco de dados. Muitos desenvolvedores acham difícil carregar explicitamente, enquanto outros ficam felizes com o controle granular oferecido.

Usar a projeção em suas consultas oferece potencialmente o melhor de dois mundos, recuperando seletivamente os dados relacionados em uma única consulta. Entretanto, se você estiver retornando tipos anônimos dessas projeções, você poderá achar isso mais difícil de trabalhar, pois os objetos não são rastreados pelo gerenciador de estado do Entity Framework e, portanto, não são atualizáveis.

A Figura 3 mostra um fluxograma de tomada de decisões que você pode usar no primeiro passo da escolha de uma estratégia. Mas você também deve considerar o desempenho. Use as ferramentas de criação de perfis de consultas e de testes de desempenho para garantir que fez as escolhas de carregamento de dados corretas.

Your First Pass at Loading Strategy Decisions

Figura 3 Seu primeiro passo na escolha da estratégia de carregamento

Julie Lerman é uma Microsoft MVP, mentora e consultora do .NET, que reside nas colinas de Vermont. Você pode encontrar sua apresentação sobre acesso a dados e outros tópicos do Microsoft .NET em grupos de usuários e conferências ao redor do mundo. Seu blog está em thedatafarm.com/blog e ela é autora do livro altamente reconhecido, “Programming Entity Framework” (O’Reilly Media, 2010). Siga-a no Twitter, em twitter.com/julielerman.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Tim Laverty