Olá pessoal! Esse será o post do primeiro objetivo do segundo grande tópico da certificação, o Querying and manipulating data by using the Entity Framework. Esse tópico foca especificamente em LINQ e Entity Framework e cobre aproximadamente 20% das questões do exame. Esse primeiro objetivo, Query and manipulate data by using the Entity Framework (que recebe quase o mesmo nome do tópico), cobre aspectos específicos do EF e os componentes que o compõe. Vamos lá?
Fazendo Queries, Updates e Deletes Usando DbContext
Fazer queries usando DbContext é bem direto e simples, usando LINQ:
var query = (from acct in context.Accounts
where acct.AccountAlias == "Primary"
select acct).FirstOrDefault();
Após uma entidade ser retornada de uma query, você pode interagir com o objeto como qualquer outro. Você faz quaisquer modificações necessárias e chama o método SaveChanges do context para fazer um update das informações alteradas.
É possível também atualizar um item que você criou usando o método Add ou Attach e chamando o mesmo método SaveChanges para persistir as alterações. É importante entender bem a diferença entre os dois métodos: incluir uma entidade usando Add faz o item ter o EntityState como Added. Pelo objeto não ter valores originais no ObjectStateEntry, quando SaveChanges é chamado, o contexto vai tentar inserir a entidade no banco de dados. Attach, por outro lado, cria o item com o EntityState Unchanged. Então o momento que você faz o Attach é muito importante. Se você fizer depois de todas as alterações, nada vai acontecer, pois o EntityState vai continuar como Unchanged. Se você fizer o Attach e depois fazer as modificações na entidade, aí sim o context vai fazer o update das propriedades no banco na chamada do SaveChanges.
Para deletar uma entidade do banco, você deve especificar o tipo da entidade usando o método Set e depois chamar o método Remove passando uma query. O item será deletado após a chamada ao SaveChanges.
context.Set
Deferred Execution e Lazy Loading
Esse assunto pode ser bem extenso, por isso vamos simplificar bastante aqui.
Veja o seguinte código:
var query = from acct in context.Accounts
where acct.AccountAlias == "Primary"
select acct;
Suponha que no banco de dados há 20 registros com o nome da conta Primary. É lógico pensar que na próxima linha o count da query será 20, certo? Errado. Nesse ponto, nada aconteceu em termos de banco de dados. A essência de Deferred Execution (execução adiada) é que somente quando você pede o dado, explicitamente referenciando-o, que o acesso ao banco de dados é de fato feito. O código abaixo, por exemplo, potencialmente dispara muitas idas ao banco de dados:
var query = from acct in Context.Accounts
where acct.AccountAlias == "Primary"
select acct;
foreach (var currentAccount in query)
{
Console.WriteLine("Current Account Alias: {0}", currentAccount.AccountAlias);
}
Já o código abaixo, por exemplo, ao invés de retornar uma coleção de itens, retorna somente um valor (count). Como é impossível obter esse valor sem ir ao banco de dados, ao chamar Count (ou qualquer função Aggregate como Average e Max), o acesso ao banco de dados é feito imediatamente:
var query = (from acct in context.Accounts
where acct.AccountAlias == "Primary"
select acct).Count();
Resumindo: no conceito de Deferred Execution, usado pelo EF, dados só são recuperados quando necessário.
O Lazy Loading é um conceito parecido com o Deferred Execution, mas aplicado para relacionamentos no banco de dados.
Imagine uma estrutura onde um Bank tem vários Customers, um Customer tem várias Accounts, uma Account tem várias Transactions e uma Transaction tem várias TransactionDetails. Ao obter um Customer para uma atualização simples de dados, você iria querer que todas as Accounts (com todas as Transactions e as TransactionsDetails) fossem retornadas? Provavelmente não.
As opções de Loading são:
- Lazy Loading: quando uma query é executada, somente os dados da entidade alvo são retornados. Por exemplo, se houver um relacionamento entre Account e Contact, uma query para Account retornaria inicialmente os dados da Account. Informações da Contact associada seriam retornadas quando uma NavigationProperty fosse acessada ou se você explicitamente pedisse;
- Eager Loading: todos os dados relacionados são retornados. Nesse exemplo, ao fazer uma query para uma Account, os dados da Contact já seriam retornados. Essencialmente, é o oposto de Lazy Loading;
- Explicit Loading: quando Lazy Loading está desabilitado, você pode usá-lo para carregar entidades de forma Lazy explicitamente. O método Load permite que você execute queries individualmente.
Você pode achar que Lazy Loading sempre é a melhor opção, mas não se engane. Se você sabe quais dados vai usar, o Lazy Loading pode afetar a performance por fazer várias idas as banco de dados ao invés de uma. Se por outro lado você não tiver certeza do número de requisições ao banco de dados e provavelmente não vai utilizar todos os dados retornados, aí sim o Lazy Loading é a melhor opção.
É possível habilitar ou desabilitar o Lazy Loading visualmente ou por código. Pelo Designer, basta ir ao EF Model, depois Properties, e escolher True ou False na propriedade Lazy Loading Enabled. Por código, o DbContext possui uma propriedade Configuration.LazyLoadingEnabled: context.Configuration.LazyLoadingEnabled = true;
.
Dê uma olhada no código abaixo:
Se você colocar um breakpoint na linha do primeiro foreach, vai perceber bem a diferença do Lazy Loading. Caso esteja desabilitado, toda a cascata de relacionamento será carregada na primeira ida ao banco de dados. Caso habilitado, você vai notar que as propriedades Customers, Transactions e TransactionDetails não serão carregadas. Elas irão sendo carregadas conforme forem usadas (foreach). Algumas outras operações também fazem com que os relacionamentos sejam carregados, como ToList, ToArray ou ToDictionary.
É importante notar também que se uma função Aggregate for chamada, uma query será executada imediatamente para retornar aquele valor. Então se você chamar o método Count de query.Customers e depois iterar pela lista (foreach), duas chamadas ao banco de dados serão feitas. Iterando primeiro (ou chamando o método ToList), você já saberia o tamanho da lista e não seriam necessárias duas chamadas.
Criando Compiled Queries
Usando LINQ, o EF cuida de traduzir a query para a linguagem SQL em tempo de execução, o que leva algum tempo. Por isso, o EF suporta compiled queries, onde ele cacheia o SQL de uma determinada query e somente muda os parâmetros na hora de executá-la. A partir do .NET 4.5, isso é feito automaticamente, mas você pode ainda fazer esse processo manualmente:
static readonly Func
CompiledQuery.Compile
(ctx, email) => (from p in ctx.People
where p.EmailAddress == email
select p).FirstOrDefault());
E usar a query assim: Person p = compiledQuery.Invoke(context, "foo@bar");
. É importante o método ser estático para evitar a compilação a cada chamada. Outro ponto é que se a query for modificada, o EF vai precisar recompilá-la. Então o ganho de performance será perdido se você adicionar um Count ou ToList numa query compilada.
Usando Entity SQL
O EF permite que você execute queries SQL diretamente no DbContext, com todo o conhecimento do seu model. Outro benefício é o maior controle sobre a query SQL que será executada no banco de dados. Você pode fazer uso do Entity Framework Object Services assim:
var queryString = "SELECT VALUE p " +
"FROM MyEntities.People AS p " +
"WHERE p.FirstName='John'";
ObjectQuery
Outro cenário é quando você não precisa do objeto inteiro ou quer trabalhar somente com os valores retornados para ganho de performance. Isso é efetuado através da classe EntityDataReader, bem parecida com a classe DataReader do System.Data. Segue um código de exemplo:
Isso é tudo para esse objetivo. Obrigado pela leitura e fique de olho no próximo post, que será sobre o objetivo 2.2 da certificação Microsoft 70-487: Query and manipulate data by using Data Provider for Entity Framework.
Até mais!