Microsoft Entity Framework Core 2.0

A versão 2.0 do Microsoft Entity Framework Core disponibilizada em 11 de agosto de 2017, está recheada de novidades e a sua evolução é notada claramente não só em desempenho, mas, também em flexibilidade e recursos novos.

Nesse post quero falar sobre alguns pontos:

Configuração e Mapeamento com a InterfaceIEntityTypeConfiguration<T>

Nessa versão ficou bem claro como utilizar uma outra classe para mapear e configurar as entidades contidas em um DbContext. Vale lembrar que esse recurso já existia na versão anterior (EF 6.0), e agora também disponibilizado na versão EF Core 2.0, vamos exemplificar um modelo:

  • Diagrama
Cliente tem nenhum ou vários Telefones

Nesse modelo tem uma relação 1 para muitos onde Cliente pode ou não ter vários Telefones e a seguir vamos representar isso em duas classes que posteriormente serão as entidades:

  • Cliente
Entidade Cliente
  • Telefone
Entidade Telefone

Essas duas classes Cliente e Telefone são as entidades e estão configuradas para que uma se relacione com a outra, o próximo passo é informar ao DbContext do Entity Framework Core saiba disso de maneira explicita:

  • Mapeando Cliente
Cliente: configurado o mapeamento.
  • Mapeando Telefone
Telefone: configurando o mapeamento.

A maneira de configurar os campos é bem parecido com a versão anterior, mas, anteriormente era feita isso dentro do construtor da classe e agora é feito em um método implementado pela Interface IEntityTypeConfiguration<T> mas, realmente o resto é bem parecido.

Para que essas configurações entre em vigor e o DbContext as conheça configure o método OnModelCreating da seguinte forma:

OnModelCreating — ApplyConfiguration

ou seja, o método ApplyConfiguration agora é responsável em aplicar as configurações estipuladas na classes de mapeamento.

Global Query Filter que reflete uma forma elegante de fazer filtros em SQL em um contexto global.

Esse item é muito interessante, porque, é aplicado de uma forma geral em todas as consultas um filtro (também pode ocorrer na carga preguiçosa) que pode ser de grande utilidade quando precisa que isso seja algo padrão em sua aplicação. Um cenário proposto seria mediante o item Configuração e Mapeamento onde na entidade de Telefone possui um campo Ativo que pode ser verdadeiro ou falso. Vamos exemplificar um regra de negócio onde somente os Telefones Ativosserão carregados para a aplicação, mas, que eu não precisasse dizer a todo momento que isso seria uma regra e que pudesse configurar isso de uma maneira Global.

Essa configuração é feita no método OnModelCreating da seguinte maneira:

Global Query Filter

No método HasQueryFilter foi configurado que a entidade Telefone traga em suas SQL um Where da seguinte forma:

SELECT * FROM Telefone WHERE Ativo = 1

e isso ocorrerá em todas as SQL para essa entidade Telefone e se por ventura possuir outros filtros serão colocados após esse. Um outro cenário, exemplo: poderia ser utilizado para dizer que um registro foi excluído, mas, mediante isso é apenas uma flag onde esse registro vai estar contido na sua base como uma forma de documentação, mas, que na sua aplicação não seja mostrado, isso é bem útil em vários cenários.

Um outro ponto interessante que os Include também compartilham da mesma ideia e incluirão de forma transparente esse recurso. Entretanto, em algum momento queria recuperar esse registro anulando esse Global Query Filter, tem como?

Sim, é só chamar o método IgnoreQueryFilters() para que esse filtro não seja adicionado na SQL, exemplo:

IgnoreQueryFilters()
Carregamento de coleção ou agregação.

Agora possui uma forma elegante de carregar coleção ou agregação das entidades mediante seus relacionamentos configurados na classe de mapeamento. Na minha visão isso ajuda demais na hora de programar, não tendo a necessidade de pacotes de terceiros e nem a manipulação de SQL pura.

Exemplos:

  • Carrego o Cliente e depois carrego a coleção de Telefones, mas, eu ainda quero filtrar esse lista de Telefones:
Carregamento de um Cliente, depois sua coleção de Telefones com filtro: somente os Ativos
  • Carrego um Telefone e depois carrego a agregação do Cliente:
Carregamento de um Telefone e sua agregação Cliente
Compilando SQL (Compile Query)

É um recurso que a partir de expressões de consultas um delegate é associado, que compila na sua primeira vez e nas próximas é reutilizada as consultas que são armazenadas no cache, ou seja, não há custo na segunda execução dessa SQL. Esse recuro tem limitações que são: 1 parâmetro do ObjectContext, um parâmetro de retorno e 16 parâmetros de consulta, e se houver necessidade de mais parâmetros de consulta, pode criar alguma estrutura ou classe para resolver esse problema.

Exemplo:

CompileQuery
Mapear dois ou mais tipos de dados para a mesma Tabela (Table splitting)

Esse novo recurso vem para facilitar a divisão de tipos de dados, onde esses dados podem ser gravadas em um única tabela, mas, organizados pelo seu tipo em entidades diferentes sendo que a mesma tem a mesma identificação (chave) que é compartilhada para todos as entidades que são configurados e consequentemente relacionadas.

Um exemplo proposta seria Product e ProductDetails:

Entidade: Product e ProductDetails

sendo que foi dividido em duas entidades os dados de produtos. Para relacionar tem que mapear essas entidades da seguinte forma:

Product Map
ProductDetails Map

A tabela resultante tem o seguinte modelo:

Table Products

Essa Table splitting que foi configurado entre Product e ProductDetails usa a chave primária da entidade Product e compartilha para ProductDetais (nesse exemplo é só uma relação, mas, pode ter várias entidades que compartilha da mesma chave primária), fazendo uma relação 1 para 1 no modo tradicional, e na minha visão o maior beneficio é na hora de trazer as informações da tabela, onde a SQL gerada dependendo os casos de forma otimizada trazendo somente o que necessita, ou seja, se não precisar carregar os dados compartilhados a SQL gera só os campos que faz referencia a entidade.

Vamos supor dois exemplos:

1.Sem trazer os dados das entidades relacionadas

Código Linq:

Código gerado (SQL):

Nesse exemplo fica claro a otimização, onde a tabela possui mais campos, mas, na sua geração não traz as entidades relacionadas, então, só coloca os campos que realmente necessitam para criar a entidade Product.

2.Trazer todos os dados compartilhados

Código Linq:

Código gerado (SQL)

Nesse caso foi gerado um SQL com mais informações, que são aquelas pertinentes aos detalhes do produto, isso é lógico porque foi requerido um Include dessas informações.

Existe também por tipos de propriedades que será a próxima abordagem.

Tipos de propriedades (Owned types)

Tem uma lógica parecida, mas, trabalha diferente do Table splitting, o que na minha opinião tem o maior impacto seria o seguinte, de qualquer forma, os dados são gerados para todas as propriedades e recuperados, não tem um otimização como o Table splitting que só carrega as informações das outras entidades quando forem solicitadas, é na verdade uma organização de informações por propriedades, talvez uma analogia seria os Values Types muitos utilizados em DDD.

Como poderia ser a escolha de utilização de uma para outra, conforme a regra de negócio, onde precisa ou não da disponibilidade imediata das informações, se precisar utilize Owned types se não precisar só em alguns caso utilize Table splitting.

Para exemplificar esse tipo que tem uma configuração própria, mediante uma tabela People:

Table People

Vou organizar as informações dividindo a parte do Addressem um propriedade da classe People:

Como configurar isso no Entity Framework Core 2.0?

Crie um classe para mapear utilizando o método OwnsOne configurando os campos da sua propriedade da seguinte forma:

Pronto dessa forma já está configurado a propriedade para ser utilizada e tudo vai ser manipulado na mesma tabela e sua entidade respectiva.

A SQL gerada numa pesquisa simples traz todos os campos pertinentes dessa propriedade, exemplo:

Codigo Linq:

Código gerado (SQL)

Fica bem claro que esse modelo é para ser utilizado quando for trazer todas as informações das propriedades e precisa que esses campos tem um layout especifico e organizado.

Mapear função do banco do tipo (Database scalar function mapping)

Agora é permitido mapear funções do banco de dados em métodos que são configurados na sua classe do DbContext, que são utilizados com Linq e são traduzidas para SQL. Esse recurso é uma grande contribuição Paul Middleton.

Um cenário simples, seria por exemplo trazer a diferença de datas em dias, uma função poderia ser como esse exemplo:

Essa função retorna a diferença de dias entre duas datas, mas, como chamar isso pelo Entity Framework?

No seu DbContext crie um método com essa assinatura:

Ou seja, decore o método com o atributo DbFunction e nas sua configurações:

  • FunctionName: o nome da função criada no seu banco
  • Schema: o nome do esquema que a função pertence no seu banco.

Como utilizar?

Em uma consulta, exemplo:

e na clausula Select, exemplo:

e interessante mostrar a tradução da SQLdesses dois exemplos são respectivamente:

e também é importante dizer que isso é um simples exemplo, quando se trabalha com funções deve medir o esforço e se tudo isso vale a pena, mas, acho mais importante mostrar que o recurso existe e pode e casos surtir um grande efeito satisfatório.

Referencias