Introdução
A Entity Framework é a tecnologia para acesso a dados recomendada pela Microsoft. A sua próxima versão (disponível a partir de Junho de 2016) será a quinta e promete ser revolucionária em vários sentidos. Este artigo irá revelar o que precisa de saber sobre ela.
História
A Entity Framework foi lançada em conjunto com o Service Pack 1 da framework .NET versão 3.5, em meados de 2009. No início, dividiu atenções com o LINQ to SQL, outra framework de acesso a dados lançada ao mesmo tempo; por esta, no entanto, ser mais simples e limitada unicamente a SQL Server, cedo foi deixada para trás, passando a Microsoft a recomendar a utilização da Entity Framework para todos os cenários de acesso a bases de dados relacionais.
Com a primeira versão da Entity Framework, a Microsoft iniciou uma aventura num mercado onde já existiam outros participantes, sendo o mais notório o NHibernate. Como framework ORM – Object-Relational Mapper –, a Entity Framework encontrava-se então muito longe do que estes outros participantes ofereciam, beneficiando, no entanto, de bom suporte a LINQ, tecnologia então nos seus primórdios, e que se viria a revelar essencial.
A segunda encarnação foi introduzida com o .NET 4.0 e trouxe consigo o suporte opcional a Plain Old CLR Objects (POCOs) – simples classes .NET sem necessidade de herdar de uma classe específica da framework, carregamento implícito (lazy loading) e a possibilidade de personalizar os modelos T4 usados para gerar as classes, quer quando o modelo era importado da base de dados, quer quando este era definido no editor integrado no Visual Studio.
Com a terceira versão lançada, 4.1, a Microsoft passou a propor um novo fluxo para o desenvolvimento: começar com o código (modelo POCO) e a partir daí gerar a base de dados. Esta abordagem adequa-se bem à metodologia de desenvolvimento conhecida por Domain-Driven Design (DDD), a qual advoga em primeiro lugar a representação por meio de classes de todos os conceitos do domínio de negócio, sendo a persistência um aspecto que, embora claramente importante, deve ser o mais transparente possível, não devendo limitar as opções de desenho do modelo de domínio. Passaram a existir convenções para definir grande parte da tradução de e para o modelo relacional, sendo desnecessário configurar todos os detalhes explicitamente. O novo API DbContext, grandemente simplificador relativamente ao ObjectContext, revelou-se extremamente popular, apesar de algumas ausências, totalmente aceitáveis numa primeira versão, e o mesmo se passou com a nova funcionalidade de migrações, poderosa, que trouxe para o mundo das bases de dados as funcionalidades de controlo de versões, tão bem conhecidas dos programadores. Passou a falar-se de Entity Framework Code First.
Brevemente a Microsoft disponibilizou uma actualização – 5 – que introduziu tipos de dados enumerados e geo-espaciais ao modelo Code First, bem como a possibilidade de chamar funções SQL transparentemente em pesquisas LINQ através de métodos estáticos em classes .NET.
Finalmente, a versão 6 veio culminar o processo de maturação da Entity Framework como ferramenta ORM de pleno direito, com suporte a intercepção e logging de chamadas, uso de stored procedures para operações Create-Update-Delete (CUD), possibilidade de definição de convenções personalizadas, e, uma das funcionalidades mais apreciadas, a possibilidade de efectuar a maior parte das operações de forma assíncrona. Muitas das funcionalidades previamente introduzidas foram também alvo de melhoramentos, com destaque para as migrações. Parecíamos estar no bom caminho.
Eis senão quando a Microsoft veio baralhar completamente o jogo, com o anúncio da sua nova versão, designada inicialmente por 7, e, mais tardiamente, por Core 1, nome que ficou.
Funcionalidades Adicionadas ou Alteradas
Novo Código
A EF Core foi totalmente escrita de raiz. Esta decisão teve a ver com o desejo da Microsoft de se ver livre de código “legado”, muito do qual praticamente não era usado – alguém usa Entity-SQL ou o ObjectContext desde a saída do Code First? – mas também com a necessidade de uma profunda alteração que permitisse a implementação de novas funcionalidades radicalmente incompatíveis com a anterior arquitectura. Esta abordagem, com méritos, veio, no entanto, causar algumas limitações à versão inicial, 1.0, como veremos a seguir.
Instalação
À semelhança das anteriores versões, a EF Core irá ser disponibilizada exclusivamente através do Nuget. A diferença é que, ao invés de um único pacote Nuget, teremos de instalar vários, consoante as necessidades:
Pacote | Descrição |
Microsoft.EntityFrameworkCore | Classes base da Entity Framework |
Microsoft.EntityFrameworkCore.Tools | Outras funcionalidades (migrações, p.ex.) |
NoSQL
O mundo em 2015 e 2016, no que respeita às Tecnologias de Informação, é diferente do que era em 2009. Por um lado, o movimento No SQL ou Not-Only SQL, ganhou grande preponderância – passou a ser normal, numa empresa, a utilização de vários tipos de tecnologias para armazenamento de dados, mesmo concorrentemente, para responder a diferentes necessidades. Se a Entity Framework no início correspondia ao que as pessoas esperavam, pode argumentar-se que já não é assim. Revelando atenção às novas tendências, a Microsoft tornou a nova versão da Entity Framework agnóstica em relação às fontes de dados de uma forma inédita: passou a suportar não apenas bases de dados relacionais mas potencialmente qualquer fonte de dados, incluindo NoSQL, através de um mecanismo de providers. Foram anunciadas duas provas de conceito, na forma de providers para Azure Table Storage e para o muito popular serviço de cache distribuída Redis, mas este anúncio fica, para já, pela intenção, já que a versão inicial, Core 1.0, não irá incluir esta funcionalidade. A ideia é promissora – usar a mesma API para efectuar consultas a potencialmente qualquer fonte de dados – mas teremos de esperar para ver.
Múltiplas Plataformas
Sendo construída de raiz com suporte ao .NET Core, a versão de .NET multi-plataforma, a Entity Framework Core irá, assim, correr nos sistemas operativos Linux, MacOS, FreeBSD e Windows, bem como em dispositivos móveis usando Windows 10 e aplicações desenvolvidas para a Windows Store. Tal não exclui, no entanto, a utilização em aplicações .NET 4.6 ou superior, a qual continuará a ser, por agora e durante muito tempo, largamente dominante.
Configuração de Fornecedores de Dados
É substancialmente mais fácil a configuração de fornecedores de dados, passando a existir um método virtual na classe DbContext
exclusivamente para esse efeito: trata-se do método OnConfiguring
:
Este exemplo ilustra a utilização do fornecedor SQL Server. Outro exemplo, agora para o fornecedor em memória (requer a instalação do pacote Microsoft.EntityFrameworkCore.InMemory
):
Para que possamos usar um fornecedor, será necessário instalar o seu pacote Nuget. Os pacotes incluídos na versão 1 da Entity Framework Core são:
Nome | Descrição |
Microsoft.EntityFrameworkCore.SqlServer | Fornecedor de acesso para SQL Server |
Microsoft.EntityFrameworkCore.SQLite | Fornecedor de acesso para SQLite |
Microsoft.EntityFrameworkCore.InMemory | Fornecedor de acesso in-memory (para testes) |
Npgsql.EntityFrameworkCore.PostgreSQL | Fornecedor de acesso para PostgreSQL |
EntityFrameworkCore.SqlServerCompact35 EntityFrameworkCore.SqlServerCompact40 | Fornecedores de acesso para SQL Server Compact 3.5 e 4.0 |
*.Design | Fornecedores para geração de entidades a partir de uma base de dados relacional (p.ex., Microsoft.EntityFrameworkCore.SqlServer.Design) |
Para cada um destes existe um método de extensão específico para a classe DbContextOptionsBuilder
, o qual aceita diferentes parâmetros, tais como a connection string a usar (o in-memory não aceita nenhum).
Podemos ver a lista de fornecedores sempre actualizada em https://docs.efproject.net/en/latest/providers/index.html.
Geração de Identificadores
Historicamente, a EF apenas suportava duas estratégias para geração de chaves primárias:
IDENTITY
, do SQL Server;- Chaves fornecidas manualmente (p.ex., GUIDs geradas por código, ou quaisquer outras chaves de tipos de dados básicos que fossem unívocas).
A nova versão vem finalmente trazer novidades a este respeito, nomeadamente, pela introdução do algoritmo High-Low. Este algoritmo, bem conhecido dos utilizadores de NHibernate, permite a geração de chaves primárias pela Entity Framework e não pela base de dados. Como não há bela sem senão, a Microsoft implementou este algoritmo com recurso às sequências do SQL Server 2012, pelo que não será suportado em versões anteriores, e o mecanismo no qual assenta não é verdadeiramente modular. Cai assim por terra o sonho de um mecanismo de geração de chaves primárias independente da base de dados.
A forma de configurar o uso de sequências para geração de identificadores globalmente é a seguinte (no método OnModelCreating
):
Mas é também possível defini-lo entidade a entidade:
Geração de SQL Optimizada
A Microsoft gaba-se de ter optimizado a geração de SQL, no caso da utilização de bases de dados relacionais. Este era, de facto, um dos calcanhares de Aquiles das versões anteriores. Adicionalmente, passou a ser possível enviar ao mesmo tempo múltiplos comandos INSERT
, UPDATE
e DELETE
, ao invés de um por cada, respectivamente, inserção, actualização ou apagamento, o que, só por si, deve melhorar o desempenho sensivelmente. Além disso, a reescrita do gerador de SQL introduz outras melhorias, no sentido de produzir SQL mais optimizado.
Um exemplo de SQL a inserir três registos e a retornar as chaves geradas (IDENTITY
) pode ser:
Execução de Funções no Lado do Cliente
O LINQ introduz validação de queries no momento da compilação; no entanto, esta validação não inclui chamadas de métodos de extensão: estas aparentarão ser válidas até que o código corra, altura em que a EF tentará executá-los na base de dados, caso estes métodos tenham sido marcados para tal. Se não, com as versões anteriores da EF, isto resulta numa excepção, mas tal não é o caso da EF Core: pura e simplesmente, as chamadas serão efectuadas no lado do cliente, após os resultados terem sido materializados, de forma transparente:
Neste exemplo, o método Format
irá ser executado após os registos terem sido retornados da base de dados e os objectos materializados. Qualquer método de extensão pode ser usado, apenas não na cláusula where
, como é facilmente compreensível.
Outro exemplo, usar uma função client-side para transformar uma coluna de forma a que possa ser ordenada:
A ordenação, neste caso, apenas será aplicada no lado do cliente, já que a Entity Framework não sabe nada sobre o método de extensão Soundex
(mero exemplo, este método não existe).
Mistura de SQL com LINQ
Passa a ser possível combinar queries LINQ com SQL. Um exemplo ilustrativo:
Parece magia, mas a Entity Framework é capaz de combinar o resultado produzido pelo stored procedure SearchBlogs
(poderia ser um SELECT
, claro está) e aplicar ordenação pelo campo indicado (Name
), resultando no SQL seguinte (simplificado):
EXEC sp_executesql 'SELECT [b].BlogId, [b].Name, [b].Url, [b].Description FROM (SELECT * FROM dbo.SearchBlogs (@p0)) AS [b] ORDER BY [b].Name'
Repare-se que o SQL passado ao método FromSql
suporta parâmetros. O único cuidado a ter com este SQL é que esteve deverá retornar colunas compatíveis com a entidade que se pretende devolver.
Propriedades Sombra
Passou a ser possível configurar propriedades de uma entidade que, tendo correspondência em colunas na base de dados (ou numa fonte de dados NoSQL), não possuem equivalência na classe .NET. Ou seja, são possivelmente incluídas em SELECT
s, UPDATE
s e INSERT
s, apesar de não serem “visíveis” nas classes POCO. Um exemplo óbvio seria a adição de colunas de “auditoria” – “criado por”, “criado em”, “modificado por”, “modificado em”. Por não serem visíveis no código, serão, em teoria, mais difíceis de interferir com. O código para configurar tal seria algo como o seguinte:
Já a utilização seria:
Finalmente, é também possível efectuar queries LINQ sobre propriedades sombra, recorrendo a uma sintaxe algo bizarra:
Anexar Entidades em Cascata
Sempre foi possível “anexar” entidades a um contexto Entity Framework diferente daquele que as carregou. Por exemplo, entidades devolvidas por um web service (saber se um web service deve ou não devolver entidades é uma questão que não será discutida aqui) podem ser posteriormente anexadas a um contexto a residir num servidor diferente e serem tratadas como originarias desse contexto. A novidade introduzida na nova versão consiste em poder definir, para cada entidade relacionada com a entidade a anexar, qual o seu estado, do ponto de vista do novo contexto. Vejamos o que acontecia se anexássemos uma entidade com uma colecção de outras entidades associadas:
Todos os elementos Post
da colecção Posts
da instância da classe Blog
seriam, assim, marcados como não modificados. Imaginemos agora que possuíamos um grafo muito complexo onde, para cada entidade nele presente pretendíamos especificar o seu estado. Para esse cenário, foi criado o API TrackGraph
:
Ou seja, o TrackGraph
sabe como percorrer todas as relações a partir de uma entidade raiz e permite que adicionemos a nossa lógica para definição dos estados encontrados.
Geração de Entidades a Partir da Base de Dados
A Entity Framework possibilita a geração de entidades a partir da base de dados (claro está, apenas para bases de dados relacionais), sem necessidade do Visual Studio. Para tal, é necessário ter presente o fornecedor de acesso pretendido (p.ex., Microsoft.EntityFrameworkCore.SqlServer
) e também a sua versão “design”, a qual contém a funcionalidade para geração de entidades (Microsoft.EntityFrameworkCore.SqlServer.Design
). A sua utilização é simples:
Claro está, este exemplo assume um servidor de base de dados SQL Server Express local com o nome SQLEXPRESS
, uma base de dados chamada Blog
e autenticação integrada do Windows. O leitor deverá alterar estes dados de acordo com o seu cenário concreto.
Outras Funcionalidades
Importa também referir que a nova Entity Framework possibilita também, na senda das versões anteriores, uma elevada extensibilidade: grande parte dos seus componentes pode ser trocada por outros. É possível, nomeadamente, injectar fornecedores de log, de geração de SQL, e muitos outros. Este tópico é vasto, pelo que fica apenas um exemplo de configuração de log para a consola:
Funcionalidades Ainda Não Suportadas
Nem tudo, no entanto, é positivo. Para não atrasar ainda mais a data de lançamento (já bastante atrasada), a versão 1.0 não inclui um número de funcionalidades que se encontravam nas versões anteriores:
- Estratégias de mapeamento de heranças: apenas Table Per Type Hierarchy / Single Table Inheritance é suportada, não Table Per Type / Class Table Inheritance nem Table Per Concrete Type / Concrete Table Inheritance;
- Carregamento implícito (lazy loading) e explícito (explicit loading) não são suportados; é necessário indicar, aquando da execução de uma query LINQ as entidades a trazer, por meio de chamadas a
Include
; - Relações muitos-para-muitos: podem ser emulados recorrendo a duas relações um-para-muitos;
- Tipos complexos (complex types) também não existem na nova versão;
- Mapeamento de inserções, alterações e apagamentos por procedimentos armazenados (stored procedures);
- Agrupamentos (
GROUP BY
) na base de dados, ao invés de em memória; - A capacidade de associar entidades a vistas;
- Estratégias para tentar re-executar comandos aquando de perda de ligação também não existirão;
- A possibilidade de adicionar convenções personalizadas;
- Intercepção de queries e comandos SQL;
- Filtrar colecções de entidades a carregar;
- Conversões de tipos de dados, por exemplo, de
String
paraXML
; - Como foi dito no início, todo o suporte a NoSQL não foi ainda incluído nesta primeira versão.
Funcionalidades Removidas
Foram removidas as seguintes funcionalidades, a título definitivo:
- Todos os APIs dependentes de
ObjectContext
(introduzido na versão 1.0 da Entity Framework) foram removidos. Isto significa que não existe mais Entity SQL ou os eventosSavingChanges
eObjectMaterialized
, mas também entidades com estado auto-gerido (self-tracking entities); - O interface
IDatabaseInitializer<T>
e todas as suas implementações (CreateDatabaseIfNotExists
,DropCreateDatabaseAlways
,DropCreateDatabaseIfModelChanges
); a alternativa passa por usar exclusivamente migrações; também deixa de ser possível a alimentação de registos iniciais (seed); - Deixam de existir migrações automáticas;
- A validação de dados de acordo com o API
ComponentModel.DataAnnotations
deixou de ser efectuada; - A abordagem model first deixa de ser suportada, apenas database first ou code first.
Conclusão
A nova versão da Entity Framework não deverá ficar para a história por si, mas pelo que possibilita. Na verdade, é a própria Microsoft a admiti-lo: esta nova versão não é a recomendada para uso profissional, devendo essa escolha recair sobre a Entity Framework 6.x. De facto, são tantas as lacunas que quase parece impossível que o gigante do software tenha optado por a tornar disponível, com tanta pompa e circunstância. No entanto, se pensarmos no suporte a múltiplas plataformas e na possibilidade (futura…) de usar o mesmo API quer para fontes de dados relacionais quer para não relacionais (Azure Table Storage, Redis, outras), a coisa muda de figura. Para sermos honestos, há algumas funcionalidades que merecem a nossa atenção desde já, mas, para uma utilização “a sério”, resta-nos esperar pela próxima versão. A curiosidade já aperta!