EF Core – Lazy Loading

Olá pessoal,

Depois um longo tempo sem escrever sobre o EntityFramework, vou retomar com uma série de artigos sobre esta nova versão, o EntityFramework Core.

Para quem ainda não sabe, o EF Core faz parte do .NET Core, que é multi plataforma.

Primeiro, vamos precisamos entender que o EF Core é um ORM totalmente novo, criado literalmente do ZERO, e por isto, ainda tem muitas coisas para serem feitas! Lembrando ainda que o EntityFramework 6 continua existindo como parte do .NET Full Framework.

No EF Core, com o lançamento da versão 2.1, tivemos uma boa evolução da ferramenta, e também a implementação do Lazy Loading.

Mas o que é o Lazy Loading ?

Quando temos relacionamentos no nosso modelo de dados, por exemplo, um Cliente com Pedidos, o EF nos permite que, ao ler o cliente, os pedidos possam ser trazidos também.

Mas isto pode deixar tudo muito lento, pois podemos ter vários clientes, com vários pedidos, e os pedidos também tem outros relacionamentos, como produtos, vendedores, etc.

Para a carga dos dados ficar mais rápida, o Lazy Loading, ou carga atrasada, é empregado e os dados relacionados são trazidos somente se eles forem consultados, ou acionados.

Para demonstrar isto na prática, vamos criar um projeto novo no Visual Studio Code do tipo console, e para tornar isto ainda mais divertido, vamos fazer tudo na linha de comandos:

dotnet new console

dotnet add package Microsoft.EntityFrameworkCore

dotnet add package Microsoft.EntityFrameworkCore.SqlServer

Estes comandos vão criar um projeto .NET Core console e adicionar o EF Core e o provider do SQL Server.

Vou utilizar para este exemplo o banco de dados NorthWind (vou colocar o script no Git)

Agora que temos o projeto, vamos criar duas classes: Customers e Orders (Se preferir, pode fazer engenharia reversa usando este outro artigo):

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace LazyLoading
{
    public class Customers
    {

        [Key]
        public string CustomerID {get; set;}
        public string CompanyName {get; set;}
        public string ContactName {get; set;}
        public virtual ICollection<Orders> Orders {get; set;}

        public Customers()
        {
            Orders = new List<Orders>();

        }
    }
}
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace LazyLoading
{
    public class Orders
    {
        [Key]
        public int OrderID {get; set;}
        public string CustomerID {get; set;}
        public virtual Customers Customers {get; set;}
        public DateTime OrderDate {get; set;}
    }
}

Neste exemplo não estou utilizando todos os campos das tabelas, pois isto não irá interferir.

E por fim a nossa classe de contexto:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.SqlServer;

namespace LazyLoading
{
    public class Contexto : DbContext
    {
        public DbSet<Customers> Customers {get; set;}
        public DbSet<Orders> Orders {get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            var conexao = new SqlConnectionStringBuilder()
            {
                DataSource = "(local)",
                InitialCatalog = "NorthWind",
                IntegratedSecurity = true
            };
            optionsBuilder.UseSqlServer(conexao.ConnectionString);
        }
    }
}

Bom, até agora nada de diferente, certo ? Vamos então listar os dados:

using System;

namespace LazyLoading
{
    class Program
    {
        static void Main(string[] args)
        {
            var db = new Contexto();

            var cliente = db.Customers;
            foreach(var c in cliente)
            {
                Console.WriteLine($"{c.CompanyName} - Orders: {c.Orders.Count}");
            }
        }
    }
}

E o resultado da execução:

Veja que todos os clientes possuem ZERO Orders, isto porque o LazyLoading está desativado ainda!

Vamos então habilitar o LazyLoading adicionando um novo pacote ao nosso projeto:

dotnet add package Microsoft.EntityFrameworkCore.Proxies

Agora podemos habilitar no nosso contexto com o seguinte código na classe Contexto.cs:

using Microsoft.EntityFrameworkCore.Proxies;

e adicionando o comando:

optionsBuilder.UseLazyLoadingProxies();

Nossa classe ficará assim:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.SqlServer;
using Microsoft.EntityFrameworkCore.Proxies; 

namespace LazyLoading
{
    public class Contexto : DbContext
    {
        public DbSet<Customers> Customers {get; set;}
        public DbSet<Orders> Orders {get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            var conexao = new SqlConnectionStringBuilder()
            {
                DataSource = "(local)",
                InitialCatalog = "NorthWind",
                IntegratedSecurity = true
            };
            optionsBuilder.UseSqlServer(conexao.ConnectionString);
            optionsBuilder.UseLazyLoadingProxies();
        }
    }
}

Pronto, agora vamos executar novamente e teremos as Ordens:

Se compararmos com o EntityFramework 6, ficou um pouco diferente, pois temos opção somente para LazyLoading e Proxy, o que eu não acho particularmente muito interessante, já que o objeto resultante vem com o proxy, veja:

Isto acontece porque a Microsoft utilizou o Castle Proxy para implementar o LazyLoading, algo que será melhorado em versões posteriores.

O código fonte deste projeto esta mo meu GitHub em: https://github.com/carloscds/EFCoreSamples

Abraços e até a próxima!

Carlos dos Santos.