LINQ e EF Core - Aula Prática

Programação Aplicada em .NET • Prof. Me. William Tenório

Sobre esta aula

Na aula teórica começamos a responder a pergunta: "se meus dados estão no SQL Server, como eu trabalho com eles dentro de uma aplicação C#?". Vimos que mapeamento é a correspondência entre a estrutura dos dados no banco e sua representação no código, e que o Entity Framework Core é um ORM (Object-Relational Mapping) que faz essa ponte para nós: cuida da conexão, do mapeamento, das consultas, das inserções, alterações e exclusões, traduz as consultas para SQL e materializa os resultados em objetos.

O caminho que vimos foi: C# / Objetos → LINQ → EF Core → SQL → Banco de Dados — e depois o inverso: Banco → Resultado SQL → EF Core → Objetos C#. Nesta aula prática o objetivo é só um: se familiarizar com esse caminho, conectando em um banco real, fazendo as primeiras consultas com LINQ e vendo o SQL que o EF Core gera. Vamos aprofundar o assunto na próxima aula teórica.

O banco de dados da aula

O laboratório tem um servidor SQL Server compartilhado. Nele já existe o banco AulaEFCore, com uma única tabela, a mesma que vimos na teoria (com uma coluna a mais, Cargo):

Funcionarios
--------------------------------
Id        int            (chave primária, gerada pelo banco)
Nome      nvarchar(100)
Cargo     nvarchar(50)
Salario   decimal(10,2)
Ativo     bit

O servidor, o usuário e a senha de acesso serão informados pelo professor em aula.

O banco é o mesmo para a turma inteira. Tudo o que você inserir, alterar ou apagar aparece para todos os colegas. Por isso, nos exemplos de inserção/alteração/exclusão, cada um trabalha somente com os registros que criou, identificados pelo seu nome no campo Nome. Nunca rode um Remove ou alteração em massa sobre a tabela inteira.

Criando o Projeto no VS Code

  1. Abra o Visual Studio Code.
  2. Crie uma pasta com o seu nome em um local do computador de que você vá se lembrar depois (ex.: Área de Trabalho ou Documentos).
  3. No VS Code, vá em File > Open Folder e selecione a pasta criada.
  4. Abra o terminal integrado em Terminal > New Terminal.
  5. No terminal, execute um comando por vez, apertando Enter e esperando ele terminar antes de digitar o próximo.

Primeiro, crie o projeto:

dotnet new console -n AulaEFCore

Depois, entre na pasta do projeto que acabou de ser criada:

cd AulaEFCore

Agora adicione o pacote do EF Core para SQL Server (é ele que traz o EF Core e o "tradutor" de LINQ para o SQL do SQL Server):

dotnet add package Microsoft.EntityFrameworkCore.SqlServer

Deu erro de versão ao adicionar o pacote? Sem informar a versão, o comando baixa a versão mais recente do EF Core, que pode exigir um .NET mais novo que o instalado na máquina (erro NU1202, "not compatible with net8.0", por exemplo). Veja a sua versão com dotnet --version e informe a versão do pacote correspondente:

# .NET 8
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 8.0.11
# .NET 9
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 9.0.0

Por fim, rode o projeto para confirmar que tudo compila:

dotnet run

Passo 1 — O mapeamento: a classe Funcionario

Do lado do C#, cada linha da tabela Funcionarios vai virar um objeto Funcionario. Crie um arquivo novo chamado Funcionario.cs (na mesma pasta do Program.cs) com o conteúdo:

public class Funcionario
{
    public int Id { get; set; }
    public string Nome { get; set; } = string.Empty;
    public string Cargo { get; set; } = string.Empty;
    public decimal Salario { get; set; }
    public bool Ativo { get; set; }
}

Repare na correspondência, coluna por coluna: int ↔ int, nvarchar ↔ string, decimal ↔ decimal, bit ↔ bool. Por convenção, o EF Core entende sozinho que a propriedade chamada Id é a chave primária.

O = string.Empty só serve para o compilador não reclamar (com um aviso amarelo) de que a string poderia ficar nula. Não muda nada no mapeamento.

Passo 2 — O DbContext e o DbSet

O DbContext é a principal classe de interação entre a aplicação e o banco — uma espécie de sessão de trabalho com o banco. O DbSet<Funcionario> é a representação, no código, da tabela Funcionarios. Crie um arquivo chamado AppDbContext.cs:

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

public class AppDbContext : DbContext
{
    public DbSet<Funcionario> Funcionarios { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        options.UseSqlServer(
            "Server=SERVIDOR;Database=AulaEFCore;User Id=USUARIO;Password=SENHA;TrustServerCertificate=True");

        // Descomente a linha abaixo para ver no console o SQL que o EF Core envia ao banco:
        // options.LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Command.Name }, LogLevel.Information);
    }
}

Troque SERVIDOR, USUARIO e SENHA pelos dados informados em aula. Cada parte da connection string corresponde a um campo da tela de conexão do SQL Server Management Studio: Server é o "Nome do servidor", User Id e Password são o logon e a senha da "Autenticação do SQL Server", e Database é o banco que queremos abrir. O TrustServerCertificate=True é necessário porque o servidor do laboratório usa um certificado próprio, não emitido por uma autoridade certificadora — sem ele, a conexão é recusada.

Repare que o nome do DbSet, Funcionarios, é igual ao nome da tabela no banco. Novamente é convenção: o EF Core usa o nome do DbSet como nome da tabela, e o nome de cada propriedade como nome da coluna.

Exemplo 1 — A primeira consulta

Substitua todo o conteúdo do Program.cs por:

using Microsoft.EntityFrameworkCore;

using var db = new AppDbContext();

List<Funcionario> funcionarios = db.Funcionarios.ToList();

Console.WriteLine($"Total de funcionários: {funcionarios.Count}");
foreach (Funcionario f in funcionarios)
{
    Console.WriteLine($"{f.Id,3} | {f.Nome,-20} | {f.Cargo,-14} | {f.Salario,10:N2} | {(f.Ativo ? "Ativo" : "Inativo")}");
}

Rode com dotnet run. Você não escreveu nenhuma linha de SQL, nem leu nenhuma coluna "na mão": o EF Core montou o SELECT, executou no SQL Server e transformou cada linha do resultado em um objeto Funcionario — isso é a materialização.

Desafios:

Pense e responda:

Exemplo 2 — LINQ vira SQL

Agora a consulta que vimos na teoria. Com LINQ escrevemos a consulta em C#; o EF Core interpreta essa expressão e a transforma em SQL. O método ToQueryString() mostra esse SQL sem executar a consulta. Substitua o Program.cs por:

using Microsoft.EntityFrameworkCore;

using var db = new AppDbContext();

var consulta = db.Funcionarios
    .Where(f => f.Salario > 5000)
    .Select(f => new
    {
        f.Nome,
        f.Salario
    });

Console.WriteLine("SQL gerado pelo EF Core:");
Console.WriteLine(consulta.ToQueryString());
Console.WriteLine();

var funcionarios = consulta.ToList(); // só aqui a consulta vai de fato ao banco

foreach (var f in funcionarios)
{
    Console.WriteLine($"{f.Nome,-20} {f.Salario,10:N2}");
}

Desafios (para cada um, observe como o SQL gerado muda):

Pense e responda:

Exemplo 3 — Inserindo com Add e SaveChanges

O DbContext também controla as alterações. O fluxo é: Add() → EF Core registra a alteração → SaveChanges() → EF Core gera SQL → SQL Server. Deixe o LogTo ligado e troque SEU NOME pelo seu nome de verdade:

using Microsoft.EntityFrameworkCore;

using var db = new AppDbContext();

var funcionario = new Funcionario
{
    Nome = "SEU NOME",
    Cargo = "Estagiário",
    Salario = 2000,
    Ativo = true
};

Console.WriteLine($"Id ANTES do SaveChanges: {funcionario.Id}");

db.Funcionarios.Add(funcionario);
Console.WriteLine("Add() chamado. Algum SQL apareceu no console até aqui?");

db.SaveChanges();

Console.WriteLine($"Id DEPOIS do SaveChanges: {funcionario.Id}");

Desafios:

Exemplo 4 — Alterando e removendo

Para alterar, basta buscar o objeto, mudar a propriedade e chamar SaveChanges(). Não existe um método "Update" nesse fluxo: o DbContext percebe sozinho o que mudou. Troque ID_DO_SEU_REGISTRO pelo Id que você anotou no Exemplo 3:

using Microsoft.EntityFrameworkCore;

using var db = new AppDbContext();

int meuId = ID_DO_SEU_REGISTRO;

Funcionario? funcionario = db.Funcionarios.Find(meuId);

if (funcionario is null)
{
    Console.WriteLine("Registro não encontrado.");
    return;
}

Console.WriteLine($"Antes: {funcionario.Nome} - {funcionario.Salario:N2}");

funcionario.Salario = 6000;
db.SaveChanges();

Console.WriteLine($"Depois: {funcionario.Nome} - {funcionario.Salario:N2}");

E para remover um registro seu:

using Microsoft.EntityFrameworkCore;

using var db = new AppDbContext();

int meuId = ID_DO_SEU_REGISTRO;

Funcionario? funcionario = db.Funcionarios.Find(meuId);

if (funcionario is not null)
{
    db.Funcionarios.Remove(funcionario);
    db.SaveChanges();
    Console.WriteLine("Registro removido.");
}

Confira o Id duas vezes antes de rodar a remoção. O banco é compartilhado: um Id errado apaga o registro de um colega (ou um dos dados iniciais da aula).

Desafios:

Pense e responda:

Exercícios

Resolva cada item com LINQ sobre db.Funcionarios e, para cada um, confira o SQL gerado (com ToQueryString() ou LogTo). Os itens 1 a 5 são apenas consultas — não alteram nada no banco.

  1. Liste nome e salário de todos os funcionários inativos.
  2. Liste os funcionários do cargo "Desenvolvedor" em ordem alfabética de nome.
  3. Mostre o maior, o menor e a média dos salários dos funcionários ativos (dica: Max, Min e Average).
  4. Verifique se existe algum funcionário com salário acima de 15.000 (dica: Any). Que tipo de valor esse método devolve?
  5. Para cada cargo, mostre o nome do cargo, a quantidade de funcionários e a média salarial (dica: GroupBy seguido de um Select com g.Key, g.Count() e g.Average(f => f.Salario)). Veja se o SQL gerado usa GROUP BY.
  6. Busque somente os seus registros (dica: Where com f.Nome.Contains("SEU NOME")), aplique um aumento de 10% no salário de cada um e salve com um único SaveChanges().
  7. Ao final da aula, remova todos os seus registros do banco, deixando a tabela como estava.

No item 6, antes de salvar, imprima quantos registros a consulta encontrou. Se o número for maior do que o que você inseriu, o filtro está pegando registros de outras pessoas — ajuste antes de chamar SaveChanges().

Para refletir

Hoje você consultou, inseriu, alterou e removeu dados de um SQL Server sem escrever SQL. Isso não quer dizer que o SQL sumiu: ele continua lá, só que gerado pelo EF Core a partir do LINQ e do mapeamento entre a classe Funcionario e a tabela Funcionarios. Por isso vale criar o hábito de olhar o SQL gerado — é ele que diz se o filtro está sendo feito no banco, quantas colunas estão sendo trazidas e quantos comandos estão sendo enviados. Na próxima aula teórica vamos aprofundar como o DbContext acompanha as alterações, como configurar mapeamentos que fogem das convenções e consultas LINQ mais avançadas.