<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lincoln Zocateli</title>
    <description>The latest articles on DEV Community by Lincoln Zocateli (@lzocate-li).</description>
    <link>https://dev.to/lzocate-li</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3877582%2F0005a739-39c0-4f4d-bbbb-aaef51d9ef06.png</url>
      <title>DEV Community: Lincoln Zocateli</title>
      <link>https://dev.to/lzocate-li</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lzocate-li"/>
    <language>en</language>
    <item>
      <title>Você Está Construindo a Fábrica de Cretinos Digitais?</title>
      <dc:creator>Lincoln Zocateli</dc:creator>
      <pubDate>Mon, 27 Apr 2026 00:31:32 +0000</pubDate>
      <link>https://dev.to/lzocate-li/voce-esta-construindo-a-fabrica-de-cretinos-digitais-27mm</link>
      <guid>https://dev.to/lzocate-li/voce-esta-construindo-a-fabrica-de-cretinos-digitais-27mm</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;O título do livro me pegou antes mesmo de abrir a primeira página. &lt;em&gt;A Fábrica de Cretinos Digitais&lt;/em&gt;. Não é um título de autoajuda. Não é clickbait de blog. É o título que &lt;strong&gt;Michel Desmurget&lt;/strong&gt; — neurocientista, diretor de pesquisa no INSERM (Institut National de la Santé et de la Recherche Médicale, o equivalente francês da NIH americana) — escolheu depois de décadas estudando o desenvolvimento neurológico infantil e sintetizando mais de 2.000 estudos científicos revisados por pares.&lt;/p&gt;

&lt;p&gt;O incômodo foi imediato. Não porque me sinto atacado como pai ou como cidadão. O incômodo veio de outro lugar: &lt;strong&gt;sou desenvolvedor de software&lt;/strong&gt;. Construo sistemas. Projeto APIs. Defino arquiteturas. E parte do que esse livro critica foi construído por pessoas exatamente como eu — profissionais competentes, com boas intenções, que nunca pararam para perguntar se aquilo que estavam construindo era bom para quem ia usar.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A Fábrica de Cretinos Digitais&lt;/em&gt; não é um livro sobre tecnologia. É um livro sobre o que a tecnologia — na forma e no volume em que é consumida hoje — está fazendo com o desenvolvimento cognitivo das crianças e adolescentes. E o argumento de Desmurget não é moral nem nostálgico. É científico, rigoroso e, por isso mesmo, muito difícil de rebater com o velho &lt;em&gt;“mas as crianças de hoje são mais espertos”&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Não vou fazer uma resenha. Existem resenhas. O que vou fazer aqui é contar &lt;strong&gt;por que esse livro me deixou desconfortável como profissional de TI&lt;/strong&gt;, onde os argumentos de Desmurget se encontram com as decisões que tomo no meu trabalho, e o que — se é que existe algo — eu posso fazer diferente. Se você já leu O Projeto Fênix e se identificou com a sensação de que a TI tem um problema sistêmico, prepare-se: Desmurget amplia o escopo desse problema para além das nossas organizações.&lt;/p&gt;

&lt;h2&gt;
  
  
  O Mito do Nativo Digital
&lt;/h2&gt;

&lt;p&gt;Vou começar pela mentira mais confortável que a nossa indústria abraçou: a ideia do &lt;strong&gt;nativo digital&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;O conceito foi cunhado em 2001 por Marc Prensky em um artigo chamado &lt;em&gt;“Digital Natives, Digital Immigrants”&lt;/em&gt;. Prensky era — e é — um consultor de educação e designer de games educativos. Não um neurocientista, não um pesquisador do desenvolvimento, não alguém com formação em cognição ou neurologia. Ele criou o termo para argumentar que a geração nascida em meio à tecnologia processava informação de forma diferente das gerações anteriores — e que, portanto, o sistema educacional precisava se adaptar.&lt;/p&gt;

&lt;p&gt;A ideia colou. Entrou em discursos corporativos, em políticas públicas de educação, em justificativas para colocar tablets em sala de aula. Tornou-se o argumento que a indústria de tecnologia mais repete porque é exatamente o que ela precisa que acreditemos: que mais tecnologia desde cedo é evolução, não problema.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O que Desmurget mostra, estudo após estudo, é que essa crença não tem base científica.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Nenhuma pesquisa revisada por pares demonstrou que crianças nascidas em ambientes digitais têm capacidades cognitivas superiores por causa desse ambiente. Pelo contrário: os dados mostram associação consistente entre maior tempo de tela e &lt;strong&gt;vocabulário reduzido&lt;/strong&gt;, &lt;strong&gt;menor capacidade de leitura&lt;/strong&gt;, &lt;strong&gt;escores de QI abaixo da média esperada para a faixa etária&lt;/strong&gt;, &lt;strong&gt;dificuldade de concentração&lt;/strong&gt; e &lt;strong&gt;atrasos na linguagem oral&lt;/strong&gt; em crianças pequenas.&lt;/p&gt;

&lt;p&gt;A ironia que Desmurget não deixa passar é cristalina: os executivos do Vale do Silício que constroem esses produtos colocam seus filhos em escolas Waldorf e Montessori sem telas. Steve Jobs limitava o acesso dos filhos ao iPad. O criador do &lt;em&gt;infinite scroll&lt;/em&gt; do Twitter, Aza Raskin, se arrepende publicamente da invenção. Tem algo muito revelador em saber que quem constrói esses sistemas não os usa com seus próprios filhos.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Atenção:&lt;/strong&gt; “Nativo digital” é uma construção de marketing, não de neurociência. O conceito foi criado por um consultor sem formação científica em desenvolvimento cognitivo e nunca foi validado empiricamente. A neurociência do desenvolvimento diz o oposto: o cérebro humano em formação precisa de estímulos específicos — linguagem oral rica, leitura profunda, brincadeira não estruturada, interação humana — que as telas não fornecem e frequentemente substituem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  O Que as Telas Realmente Fazem com o Cérebro
&lt;/h2&gt;

&lt;p&gt;Desmurget é preciso em algo que os críticos da tecnologia costumam errar: &lt;strong&gt;não é a tecnologia em si o problema&lt;/strong&gt;. Um adolescente de 16 anos aprendendo a programar, criando arte digital ou lendo livros em um e-reader está em uma situação completamente diferente de uma criança de 2 anos em frente a um tablet com autoplay de vídeos infantis. O problema é o &lt;strong&gt;volume, a forma e a faixa etária&lt;/strong&gt; de exposição.&lt;/p&gt;

&lt;p&gt;O cérebro humano não termina de se desenvolver aos 6, aos 10, nem aos 18 anos. O córtex pré-frontal — responsável por tomada de decisão, controle de impulso, raciocínio abstrato e regulação emocional — só completa seu desenvolvimento por volta dos &lt;strong&gt;25 anos&lt;/strong&gt;. É um órgão em construção por um quarto de século, e o que o alimenta durante esse processo molda permanentemente suas conexões.&lt;/p&gt;

&lt;p&gt;Os impactos documentados variam por faixa etária:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Faixa etária&lt;/th&gt;
&lt;th&gt;Principais impactos documentados&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0–2 anos&lt;/td&gt;
&lt;td&gt;Atraso na linguagem oral, prejuízo no reconhecimento de emoções faciais, privação de sono, substituição de interação humana&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3–6 anos&lt;/td&gt;
&lt;td&gt;Atenção fragmentada, redução do vocabulário, menor capacidade de concentração sustentada, dificuldade na aprendizagem simbólica&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7–12 anos&lt;/td&gt;
&lt;td&gt;Queda no desempenho escolar (especialmente leitura e matemática), sedentarismo, problemas de sono por exposição à luz azul, menor capacidade de leitura profunda&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adolescentes&lt;/td&gt;
&lt;td&gt;Ansiedade, depressão, comparação social patológica, dependência de validação externa (likes), impacto na formação da identidade, risco aumentado de automutilação em meninas&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;O mecanismo não é misterioso. O cérebro em formação precisa de &lt;strong&gt;leitura profunda&lt;/strong&gt; para desenvolver circuitos de compreensão e abstração. Precisa de &lt;strong&gt;conversas ricas&lt;/strong&gt; para construir vocabulário e teoria da mente. Precisa de &lt;strong&gt;brincadeira não estruturada&lt;/strong&gt; para desenvolver criatividade e resolução de problemas. Precisa de &lt;strong&gt;sono de qualidade&lt;/strong&gt; para consolidar memórias e reorganizar conexões neurais.&lt;/p&gt;

&lt;p&gt;Telas passivas — e a maioria do consumo infantil de telas é passivo, não ativo — substituem essas atividades sem fornecer os estímulos equivalentes. Não é que as telas sejam ruins em si. É que a hora assistindo a vídeos no YouTube é a hora que não foi usada para ler, brincar, conversar ou dormir.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ℹ️ Informação:&lt;/strong&gt; A OMS recomenda zero tempo de tela para crianças de 0 a 1 ano e não mais de 1 hora por dia para crianças de 2 a 4 anos. A Academia Americana de Pediatria segue recomendações similares. Essas não são opiniões — são consensos construídos sobre décadas de pesquisa. E a maioria dos pais não sabe que elas existem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A Economia da Atenção: Como Nós Construímos a Dependência
&lt;/h2&gt;

&lt;p&gt;Até aqui, Desmurget está falando de neurociência. Mas o livro vai além. Ele aponta um dedo para algo que a maioria das análises sobre telas e crianças evita nomear diretamente: &lt;strong&gt;não é acidente que os apps sejam viciantes. É design intencional.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Deixa eu explicar o mecanismo, porque ele importa para o que vou dizer na próxima seção.&lt;/p&gt;

&lt;p&gt;Em 2003, &lt;strong&gt;B.J. Fogg&lt;/strong&gt; fundou o Persuasive Technology Lab em Stanford. O objetivo declarado era estudar como computadores podiam mudar atitudes e comportamentos humanos. Fogg criou o modelo &lt;strong&gt;B=MAP&lt;/strong&gt; (Behavior = Motivation + Ability + Prompt) — um framework para tornar comportamentos mais prováveis através de tecnologia. Ele não estava pensando em crianças. Estava pensando em design. Mas o que ele treinou foi uma geração de designers de produto em como fazer as pessoas fazerem coisas que elas não planejavam fazer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nir Eyal&lt;/strong&gt; sistematizou isso no livro &lt;em&gt;Hooked&lt;/em&gt; (2014): o modelo de produto com quatro etapas — trigger, ação, recompensa variável, investimento — que toda startup usa hoje para criar hábito. O livro é um manual. Está em qualquer estante de PM e designer de produto que se preze.&lt;/p&gt;

&lt;p&gt;O elemento central do modelo é a &lt;strong&gt;recompensa variável&lt;/strong&gt;. É o mesmo mecanismo das slot machines: você não sabe o que vai encontrar quando rola o feed, quando abre as notificações, quando desliza para o próximo vídeo. Pode ser algo irrelevante. Pode ser algo que vai fazer seu coração acelerar. A incerteza é o vício. O scroll infinito não foi uma solução de UX para evitar cliques — foi a implementação perfeita de um cassino no bolso de cada usuário.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tristan Harris&lt;/strong&gt;, ex-design ethicist do Google, tentou alertar a empresa internamente em 2013 com uma apresentação chamada &lt;em&gt;“A Call to Minimize Distraction &amp;amp; Respect Users’ Attention”&lt;/em&gt;. Não funcionou. Em 2016, ele foi ao público com o artigo &lt;em&gt;“How Technology is Hijacking Your Mind”&lt;/em&gt; e fundou o Center for Humane Technology. Harris diz — e documenta — que as empresas de tecnologia estão em uma corrida armamentista pela atenção humana, usando técnicas de persuasão que são mais sofisticadas do que qualquer usuário, incluindo pais, consegue resistir conscientemente.&lt;/p&gt;

&lt;p&gt;Quem projeta esses sistemas?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nós. Devs. Engenheiros. Arquitetos de software.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;📝 Exemplo:&lt;/strong&gt; Cada vez que eu implemento um “load more on scroll” sem oferecer ao usuário uma paginação explícita com controle claro de parada, ou quando construo um sistema de notificações push sem granularidade suficiente para o usuário silenciar categorias específicas, ou quando o comportamento padrão da minha aplicação é maximizar tempo de tela em vez de entregar valor e sair do caminho — estou aplicando, conscientemente ou não, os princípios da economia da atenção.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;É desconfortável escrever isso. Mas é verdade.&lt;/p&gt;

&lt;p&gt;Esse debate sobre agência e responsabilidade é central para como vejo meu trabalho hoje. Já escrevi sobre como a IA está mudando o papel do desenvolvedor — mas talvez a questão mais urgente não seja o que a IA vai fazer com o nosso trabalho, mas o que nós, com as nossas próprias mãos, já fizemos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nós, Desenvolvedores, Temos Responsabilidade?
&lt;/h2&gt;

&lt;p&gt;Essa é a pergunta que o livro não responde por mim — e que eu precisei responder por conta própria.&lt;/p&gt;

&lt;p&gt;A resposta honesta é: sim. Mas a extensão e a natureza dessa responsabilidade são mais complexas do que um sim ou não comportam. Vou ser direto sobre os argumentos que eu mesmo já usei para me isentar — e por que nenhum deles me satisfaz mais.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Argumento #1: “Eu só implemento o que o produto manda.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;É o argumento mais comum. O PM definiu o backlog. O design aprovou os wireframes. O CEO quer crescimento de usuários ativos diários. Eu só escrevo código. A responsabilidade é de quem toma as decisões de produto.&lt;/p&gt;

&lt;p&gt;Por que não sustenta: quem escreve o código faz escolhas de implementação constantemente. Scroll infinito não se implementa sozinho — alguém decidiu não colocar um botão de pausa. Sistema de notificações sem controle granular não é um acidente de arquitetura — é uma escolha. Quando eu implemento algo que sei que vai maximizar engajamento às custas do bem-estar do usuário, e não digo nada, não pergunto nada, não proponho alternativa, estou tomando uma decisão ativa de não questionar.&lt;/p&gt;

&lt;p&gt;O Programador Pragmático que li há alguns anos dizia: &lt;em&gt;seja dono do seu código&lt;/em&gt;. Ser dono não é só sobre qualidade técnica. É sobre as consequências do que você constrói.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Argumento #2: “É responsabilidade dos pais. Ninguém obrigou ninguém a dar tablet para criança de 2 anos.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;O argumento da responsabilidade individual. E tem alguma razão: pais têm responsabilidade, sim. Mas esse argumento ignora um detalhe fundamental que Desmurget e Tristan Harris documentam exaustivamente: os sistemas que construímos foram projetados especificamente para &lt;strong&gt;vencer a resistência humana ao autocontrole&lt;/strong&gt;. Não é coincidência. É engenharia comportamental.&lt;/p&gt;

&lt;p&gt;Culpar individualmente o pai que eventualmente cede o celular para uma criança de 3 anos — quando o sistema foi desenhado por equipes de psicólogos, neurocientistas e engenheiros para ser irresistível — é desonesto. É como culpar o fumante sem mencionar as décadas em que a indústria do tabaco escondeu pesquisas, adicionou ingredientes que aumentavam a dependência e contratou médicos para endossar cigarros em comerciais de TV.&lt;/p&gt;

&lt;p&gt;Aliás, essa é exatamente a analogia que Desmurget usa: as big techs financiam pesquisas que “contradizem” os estudos independentes sobre impacto de telas, da mesma forma que a indústria do tabaco financiou pesquisas que “colocavam em dúvida” a relação entre cigarro e câncer durante décadas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Argumento #3: “Eu trabalho em B2B / enterprise. Não faço app de consumo.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Esse é o argumento que eu mesmo mais usei. Construo sistemas corporativos — APIs, pipelines, integrações. Não tenho nada a ver com Instagram ou TikTok.&lt;/p&gt;

&lt;p&gt;Mas há dois problemas. O primeiro: os padrões de design que normalizamos no consumo migram para o enterprise. Notificações agressivas, dashboards que maximizam tempo de uso em vez de valor entregue, UX que cria dependência em vez de autonomia — esses padrões existem em software corporativo tanto quanto em redes sociais.&lt;/p&gt;

&lt;p&gt;O segundo problema é mais estrutural: a indústria de tecnologia é um ecossistema. As decisões tomadas nas big techs de consumo moldam o que é considerado aceitável em toda a indústria. Quando eu não questiono esses padrões, quando trato a economia da atenção como algo que “não é comigo”, contribuo para a normalização de uma cultura que tem consequências documentadas.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Dica:&lt;/strong&gt; Tristan Harris tem uma palestra no TED que vale 15 minutos do seu tempo: &lt;em&gt;“How a handful of tech companies control billions of minds every day”&lt;/em&gt;. Se você é dev e nunca parou para pensar sobre o impacto comportamental do que constrói, é um ponto de partida honesto e não apocalíptico.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Não existe uma resposta fácil aqui. Não vou terminar esta seção com um manifesto ou com uma lista de regras. O que quero dizer é mais simples e mais desconfortável: &lt;strong&gt;a pergunta precisa ser feita, e não está sendo&lt;/strong&gt;. Na maioria das empresas onde já trabalhei, nunca ninguém — em nenhuma reunião de sprint, em nenhum refinamento, em nenhuma retrospectiva — perguntou: &lt;em&gt;“esse comportamento que estamos construindo é bom para quem vai usar?”&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Não porque as pessoas fossem mal-intencionadas. Mas porque essa pergunta simplesmente não faz parte do vocabulário padrão do desenvolvimento de software. Precisamos colocá-la lá.&lt;/p&gt;

&lt;h2&gt;
  
  
  O Que Posso Fazer Diferente Como Dev
&lt;/h2&gt;

&lt;p&gt;Depois de toda essa reflexão, a pergunta prática inevitável é: e agora, o que faço com isso?&lt;/p&gt;

&lt;p&gt;Não vou fingir que existe um checklist que resolve o problema. Mas existem escolhas concretas que posso fazer — que qualquer dev pode fazer — sem precisar de autorização de CEO ou mudança de cultura organizacional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ethical design na prática&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;O primeiro passo é reconhecer padrões enganosos (&lt;em&gt;deceptive patterns&lt;/em&gt;) quando os estou implementando. O catálogo de deceptive.design documenta dezenas deles com exemplos reais: roach motels (fácil entrar, impossível sair), confirmshaming (botões de cancelamento com texto que induzem culpa), interface interference (elementos que dificultam ações que o usuário quer fazer). A maioria dos devs já implementou pelo menos um desses sem nomear o que estava fazendo.&lt;/p&gt;

&lt;p&gt;Reconhecer não significa recusar automaticamente. Significa perguntar. &lt;em&gt;“Por que estamos fazendo isso dessa forma? Qual é a alternativa que serve o usuário igualmente bem?”&lt;/em&gt;. Às vezes a resposta será uma decisão de negócio legítima. Às vezes vai abrir uma conversa que ninguém tinha parado para ter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notificações com propósito&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Notificações são o mecanismo de interrupção mais poderoso que temos. E o padrão da maioria das aplicações é notificar para &lt;strong&gt;engajar&lt;/strong&gt;, não para &lt;strong&gt;informar&lt;/strong&gt;. A diferença é fundamental: uma notificação que informa entrega algo que o usuário precisava saber. Uma notificação que engaja interrompe o usuário para trazê-lo de volta ao app independentemente de ele precisar estar lá.&lt;/p&gt;

&lt;p&gt;Quando projeto um sistema de notificações, a pergunta que passei a fazer é: &lt;em&gt;“o usuário ficaria grato por receber isso agora, ou estaria incomodado?”&lt;/em&gt;. Se a resposta honesta for a segunda, a notificação precisa ser repensada.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;APIs de bem-estar digital que você pode usar&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Não precisa reinventar a roda. Existem APIs nativas que permitem ao desenvolvedor trabalhar a favor do usuário:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;iOS:&lt;/strong&gt; Screen Time API / Family Controls framework — permite que apps ofereçam controles de tempo e restrições que o usuário configura para si mesmo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android:&lt;/strong&gt; Digital Wellbeing API — acesso às métricas de uso do dispositivo para que o app possa se auto-regular&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web:&lt;/strong&gt; Page Visibility API — permite pausar conteúdo automaticamente quando o usuário não está olhando para a aba, em vez de continuar reproduzindo
&lt;strong&gt;Design for exit&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Facilitar que o usuário saia da sua aplicação não é antipadrão — é respeito. Botão de fechar visível. Cancelamento de assinatura sem 6 telas de confirmação. Controle claro do feed. Configurações de privacidade acessíveis em um clique, não enterradas em submenus.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Métricas além de usuários ativos diários e tempo de tela&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Quando o PM pede &lt;em&gt;“mais usuários ativos diários”&lt;/em&gt; ou &lt;em&gt;“mais tempo médio de sessão”&lt;/em&gt;, essas métricas medem quanto o usuário está usando o app — não se ele ficou satisfeito. Questionar as métricas não é ser difícil. É perguntar qual o valor real que estamos entregando. Apps de meditação, por exemplo, deveriam medir se os usuários estão dormindo melhor, não se passaram mais tempo dentro do app.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Atenção:&lt;/strong&gt; Implementar padrões enganosos não é tecnicamente neutro. É uma escolha com consequência comportamental documentada. Você não precisa ter certeza absoluta de que está errado para levantar a questão antes de implementar. A dúvida já é suficiente para valer uma conversa.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Se você quer se aprofundar na dimensão ética da engenharia de software, o livro O Programador Pragmático tem um capítulo inteiro sobre responsabilidade profissional que agora leio com outros olhos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dicas e Boas Práticas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Leia o livro&lt;/strong&gt; — &lt;em&gt;A Fábrica de Cretinos Digitais&lt;/em&gt; está disponível em português. Não é um texto acadêmico denso. É acessível, direto e perturbadoramente bem fundamentado. É uma leitura de fim de semana que vai mudar como você olha para o que constrói.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Conheça o Center for Humane Technology&lt;/strong&gt; — humanetech.com tem recursos práticos, framework de design ético e o podcast &lt;em&gt;Your Undivided Attention&lt;/em&gt;, que Tristan Harris conduz com especialistas em cognição, política e tecnologia. Não é um grupo anti-tech — é um grupo que acredita que tecnologia melhor é possível.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Audite os padrões enganosos no seu produto&lt;/strong&gt; — use o catálogo de deceptive.design como referência. Reserve uma hora para revisar seu produto com esse olhar. Você vai reconhecer coisas que você mesmo implementou sem nomear o que eram.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Proponha métricas de bem-estar&lt;/strong&gt; — engajamento mede quanto tempo o usuário passa no app, não se ele ficou satisfeito ou se o produto entregou valor. Pergunte ao seu time de produto: &lt;em&gt;qual métrica captura valor real entregue ao usuário?&lt;/em&gt; É uma conversa que poucos times já tiveram.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tenha a conversa com o time&lt;/strong&gt; — não precisa ser um discurso ou um manifesto. Uma pergunta já basta: &lt;em&gt;“esse comportamento que estamos construindo é bom para o usuário ou apenas para o nosso KPI?”&lt;/em&gt;. Plantar a pergunta já é suficiente para começar uma mudança de cultura.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Separe crianças de adultos no design&lt;/strong&gt; — se seu produto pode ser acessado por menores, as responsabilidades mudam de patamar. Além das implicações éticas, existem obrigações legais: &lt;strong&gt;COPPA&lt;/strong&gt; (Children’s Online Privacy Protection Act, nos EUA), &lt;strong&gt;LGPD&lt;/strong&gt; (Lei Geral de Proteção de Dados, no Brasil) e &lt;strong&gt;GDPR&lt;/strong&gt; (na Europa) têm disposições específicas sobre dados de menores. Ignorar isso não é só uma questão de ética — é risco jurídico.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Não sou anti-tecnologia. Isso precisava ser dito explicitamente, porque qualquer reflexão crítica sobre o impacto das telas costuma ser enquadrada assim. Sou desenvolvedor. Vivo de tecnologia, acredito no que ela pode fazer, e escolhi essa carreira porque genuinamente me interessa construir coisas que funcionem bem.&lt;/p&gt;

&lt;p&gt;Mas ler &lt;em&gt;A Fábrica de Cretinos Digitais&lt;/em&gt; me obrigou a separar duas coisas que eu tratava como sinônimas: &lt;strong&gt;acreditar em tecnologia&lt;/strong&gt; e &lt;strong&gt;acreditar que qualquer coisa que construímos com tecnologia é neutra ou benéfica por padrão&lt;/strong&gt;. Não é.&lt;/p&gt;

&lt;p&gt;O que Desmurget argumenta — e o que os dados que ele apresenta sustentam — é que a indústria de tecnologia criou, nas últimas duas décadas, um ambiente otimizado para maximizar engajamento independentemente do custo humano. Não por malícia coletiva. Mas por incentivos econômicos que nunca foram questionados com a seriedade necessária, porque os que poderiam questionar — os próprios profissionais que constroem esses sistemas — nunca foram encorajados a fazer essa pergunta.&lt;/p&gt;

&lt;p&gt;O paralelo com o que escrevi sobre O Projeto Fênix é inevitável. Lá, a TI da Parts Unlimited não estava destruindo a empresa por malícia — estava destruindo por desconhecimento sistêmico, por incentivos errados, por falta de vocabulário para nomear o problema. Aqui, a situação tem uma camada a mais: o problema está documentado, o vocabulário existe, e a pergunta simplesmente não é feita.&lt;/p&gt;

&lt;p&gt;Daqui a dez anos, quando olhar para o que construiu, qual frase você quer que descreva o seu trabalho? &lt;em&gt;“Maximizei o tempo de tela dos usuários”&lt;/em&gt; ou &lt;em&gt;“construí coisas que entregaram valor real para as pessoas que as usaram”&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;Eu sei qual das duas me deixa mais confortável.&lt;/p&gt;

&lt;p&gt;Se você leu até aqui e tem uma opinião diferente — ou se acha que estou exagerando no peso da responsabilidade individual do dev — deixe nos comentários. Esse debate precisa de mais vozes, especialmente de quem está dentro da indústria.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leia Também
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;O Projeto Fênix: a TI que eu vivo todos os dias&lt;/li&gt;
&lt;li&gt;O Programador Pragmático: lições práticas do livro&lt;/li&gt;
&lt;li&gt;IA vai substituir desenvolvedores? Minha opinião honesta&lt;/li&gt;
&lt;li&gt;IA, LLM, RAG, Agents e MCP: guia profissional&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Referências
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A Fábrica de Cretinos Digitais — Michel Desmurget (Goodreads) — obra de referência principal do artigo&lt;/li&gt;
&lt;li&gt;INSERM — Institut National de la Santé et de la Recherche Médicale — instituição onde Desmurget é diretor de pesquisa&lt;/li&gt;
&lt;li&gt;Center for Humane Technology — organização fundada por Tristan Harris com recursos de design ético e o podcast Your Undivided Attention&lt;/li&gt;
&lt;li&gt;WHO: Guidelines on physical activity, sedentary behaviour and sleep for children under 5 years of age — diretrizes da Organização Mundial da Saúde sobre tempo de tela por faixa etária&lt;/li&gt;
&lt;li&gt;American Academy of Pediatrics — Where We Stand: Screen Time — recomendações por faixa etária da Academia Americana de Pediatria&lt;/li&gt;
&lt;li&gt;TED Talk: Tristan Harris — How a handful of tech companies control billions of minds every day — palestra essencial sobre economia da atenção (15 min)&lt;/li&gt;
&lt;li&gt;Deceptive Design — Hall of Shame — catálogo de padrões enganosos com exemplos reais identificados em produtos conhecidos&lt;/li&gt;
&lt;li&gt;At a Waldorf School in Silicon Valley, Technology Can Wait — NYT — reportagem sobre executivos do Vale do Silício que escolhem escolas sem telas para seus filhos
📬&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;👉 &lt;strong&gt;Artigo completo com todos os exemplos de código:&lt;/strong&gt; &lt;a href="https://zocate.li/posts/2026/fabrica-cretinos-digitais-desmurget-responsabilidade-dev/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog" rel="noopener noreferrer"&gt;Você Está Construindo a Fábrica de Cretinos Digitais?&lt;/a&gt;&lt;/p&gt;

</description>
      <category>portuguese</category>
      <category>livros</category>
      <category>carreira</category>
      <category>opiniao</category>
    </item>
    <item>
      <title>Gargalo em Banco de Dados: Mensageria e Paginação</title>
      <dc:creator>Lincoln Zocateli</dc:creator>
      <pubDate>Thu, 16 Apr 2026 01:10:49 +0000</pubDate>
      <link>https://dev.to/lzocate-li/gargalo-em-banco-de-dados-mensageria-e-paginacao-33g6</link>
      <guid>https://dev.to/lzocate-li/gargalo-em-banco-de-dados-mensageria-e-paginacao-33g6</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;Todo desenvolvedor .NET que trabalha com aplicações de médio ou grande porte já esbarrou em pelo menos um destes cenários: uma rotina noturna que precisa &lt;strong&gt;gravar 50 mil pedidos&lt;/strong&gt; no banco, uma consulta de relatório que traz &lt;strong&gt;300 mil registros&lt;/strong&gt; de uma única vez e trava a aplicação, ou uma API que demora 8 segundos para responder porque o EF Core está executando 10 mil &lt;code&gt;INSERT&lt;/code&gt; individuais em sequência.&lt;/p&gt;

&lt;p&gt;O &lt;strong&gt;SQL Server&lt;/strong&gt; e o &lt;strong&gt;Oracle&lt;/strong&gt; são bancos robustos e maduros, mas nenhum deles foi projetado para receber milhares de gravações em rajada, nem para retornar centenas de milhares de linhas de uma só vez sem custo. O problema, na maioria dos casos, não é o banco — é a forma como a aplicação C# interage com ele.&lt;/p&gt;

&lt;p&gt;Neste artigo vamos explorar as duas faces do gargalo: &lt;strong&gt;escrita em massa&lt;/strong&gt; e &lt;strong&gt;leitura de grandes volumes&lt;/strong&gt;. Para cada uma, você vai ver a causa raiz, as particularidades de SQL Server e Oracle com &lt;strong&gt;EF Core 8.0+&lt;/strong&gt;, e a solução prática — &lt;strong&gt;mensageria&lt;/strong&gt; para gravação e &lt;strong&gt;paginação eficiente&lt;/strong&gt; para leitura. Todo o código é produção-ready e compatível com .NET 8/9.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pré-requisitos:&lt;/strong&gt; Conhecimento básico de C# e EF Core. Recomenda-se ter lido o artigo sobre programação assíncrona com C# antes de continuar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📦 Código-fonte:&lt;/strong&gt; A implementação completa deste artigo está no repositório blog-zocateli-sample no GitHub. Clone, explore e adapte ao seu contexto.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  O Gargalo de Escrita: Por Que Gravar Milhares de Registros Dói
&lt;/h2&gt;

&lt;h3&gt;
  
  
  O Custo Real de Cada INSERT
&lt;/h3&gt;

&lt;p&gt;Quando você salva uma lista de entidades com o EF Core da forma mais comum, algo assim acontece:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Abordagem ingênua — InsertOne por vez&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pedido&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;listaDe50MilPedidos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pedidos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// PROBLEMA: 1 roundtrip por registro!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada &lt;code&gt;SaveChangesAsync()&lt;/code&gt; dentro do loop representa:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Uma transação aberta → commit → fechada&lt;/strong&gt; no banco&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Um roundtrip de rede&lt;/strong&gt; (latência de 1–5ms por chamada)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log do banco de dados&lt;/strong&gt; sendo escrito para cada operação&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Locks de linha&lt;/strong&gt; sendo alocados e liberados 50 mil vezes
Para 50.000 pedidos com 2ms de latência por roundtrip, isso representa 100 segundos de processamento puro de I/O. E isso é no melhor cenário, sem contenção.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Particularidades: SQL Server vs Oracle
&lt;/h3&gt;

&lt;p&gt;Ainda que a solução seja similar nos dois bancos, há diferenças importantes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspecto&lt;/th&gt;
&lt;th&gt;SQL Server&lt;/th&gt;
&lt;th&gt;Oracle&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bulk Insert nativo&lt;/td&gt;
&lt;td&gt;BULK INSERT / SqlBulkCopy&lt;/td&gt;
&lt;td&gt;ODP.NET BulkCopy / INSERT ALL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tamanho máximo de lote padrão&lt;/td&gt;
&lt;td&gt;1.000 linhas por INSERT&lt;/td&gt;
&lt;td&gt;Depende do ArrayBindCount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sequências / Identity&lt;/td&gt;
&lt;td&gt;IDENTITY ou SEQUENCE&lt;/td&gt;
&lt;td&gt;Apenas SEQUENCE (obrigatório)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rollback de bulk&lt;/td&gt;
&lt;td&gt;Pode ser minimamente logado&lt;/td&gt;
&lt;td&gt;Sempre logado (redo log)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EF Core provider&lt;/td&gt;
&lt;td&gt;Microsoft.EntityFrameworkCore.SqlServer&lt;/td&gt;
&lt;td&gt;Oracle.EntityFrameworkCore&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No &lt;strong&gt;Oracle&lt;/strong&gt;, um detalhe crítico é que o provider oficial (&lt;code&gt;Oracle.EntityFrameworkCore 8.x&lt;/code&gt;) exige que você configure a geração de chaves via &lt;code&gt;SEQUENCE&lt;/code&gt; + &lt;code&gt;TRIGGER&lt;/code&gt; (ou &lt;code&gt;GENERATED ALWAYS AS IDENTITY&lt;/code&gt; no Oracle 12c+). Ignorar isso em gravações em massa vai gerar N chamadas extras ao banco só para obter os IDs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Oracle: configuração correta no OnModelCreating&lt;/span&gt;
&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnModelCreating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelBuilder&lt;/span&gt; &lt;span class="n"&gt;modelBuilder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;modelBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Pedido&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HasKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Oracle 12c+: identity nativo, evita roundtrip extra por ID&lt;/span&gt;
        &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseIdentityColumn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Alternativa para Oracle 11g/legado&lt;/span&gt;
        &lt;span class="c1"&gt;// entity.Property(e =&amp;gt; e.Id)&lt;/span&gt;
        &lt;span class="c1"&gt;//       .HasDefaultValueSql("SEQ_PEDIDOS.NEXTVAL");&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A Solução Imediata: SaveChanges em Lote com EF Core 8
&lt;/h3&gt;

&lt;p&gt;Antes de introduzir filas, a primeira otimização é mover o &lt;code&gt;SaveChangesAsync()&lt;/code&gt; para fora do loop e usar o &lt;strong&gt;chunk&lt;/strong&gt; para não sobrecarregar o contexto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ Melhor: batch por chunk + 1 SaveChanges por lote&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;GravarPedidosEmLoteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Pedido&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pedidos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;tamanhoLote&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pedidos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tamanhoLote&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pedidos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRangeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Limpar o ChangeTracker para não acumular entidades em memória&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChangeTracker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 &lt;strong&gt;Dica:&lt;/strong&gt; O método &lt;code&gt;Chunk()&lt;/code&gt; foi introduzido no .NET 6. Combinado com &lt;code&gt;ChangeTracker.Clear()&lt;/code&gt;, evita que o contexto EF Core cresça indefinidamente ao rastrear dezenas de milhares de entidades.&lt;/p&gt;

&lt;h3&gt;
  
  
  ExecuteInsertAsync e BulkInsert no EF Core 8
&lt;/h3&gt;

&lt;p&gt;O EF Core 7 trouxe &lt;code&gt;ExecuteUpdateAsync&lt;/code&gt; e &lt;code&gt;ExecuteDeleteAsync&lt;/code&gt;. O EF Core 8 melhorou o suporte a operações em massa. Para cenários de altíssima performance, use a biblioteca &lt;strong&gt;EFCore.BulkExtensions&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ BulkInsert para SQL Server e Oracle (via EFCore.BulkExtensions)&lt;/span&gt;
&lt;span class="c1"&gt;// dotnet add package EFCore.BulkExtensions&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;BulkInsertPedidosAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Pedido&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pedidos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bulkConfig&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;BulkConfig&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;BatchSize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;UseTempDB&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="c1"&gt;// SQL Server: tabela temporária para staging&lt;/span&gt;
        &lt;span class="n"&gt;SetOutputIdentity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// Preenche os IDs gerados pelo banco&lt;/span&gt;
        &lt;span class="n"&gt;PreserveInsertOrder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BulkInsertAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedidos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bulkConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com &lt;code&gt;BulkInsert&lt;/code&gt;, 50.000 registros que levavam 100 segundos com &lt;code&gt;SaveChanges&lt;/code&gt; individual passam a ser gravados em &lt;strong&gt;2–4 segundos&lt;/strong&gt; no SQL Server, e em &lt;strong&gt;3–6 segundos&lt;/strong&gt; no Oracle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mensageria: A Solução Arquitetural para Gravação em Massa
&lt;/h2&gt;

&lt;p&gt;Otimizar o próprio &lt;code&gt;INSERT&lt;/code&gt; resolve o sintoma, mas não a causa raiz. O problema real é que a &lt;strong&gt;aplicação está tentando processar um volume enorme de forma síncrona&lt;/strong&gt;, bloqueando a thread e a requisição enquanto grava. A solução arquitetural correta é &lt;strong&gt;desacoplar a recepção da gravação&lt;/strong&gt; usando uma fila de mensagens.&lt;/p&gt;

&lt;h3&gt;
  
  
  Como a Mensageria Resolve o Problema
&lt;/h3&gt;

&lt;p&gt;Em vez de gravar diretamente no banco ao receber os dados, a aplicação:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Publica&lt;/strong&gt; os registros em uma fila (RabbitMQ, Azure Service Bus, etc.) — operação rápida (~1ms por mensagem)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retorna imediatamente&lt;/strong&gt; para o cliente com &lt;code&gt;202 Accepted&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Um &lt;strong&gt;Consumer&lt;/strong&gt; separado lê a fila em lotes e grava no banco com bulk insert&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essa separação traz benefícios além da performance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Resiliência:&lt;/strong&gt; se o banco cair temporariamente, as mensagens ficam na fila. Naão há perda de dados.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Controle de fluxo:&lt;/strong&gt; o consumer pode processar na velocidade que o banco suporta&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backpressure natural:&lt;/strong&gt; a fila amortece picos de carga&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observabilidade:&lt;/strong&gt; você pode monitorar o tamanho da fila e saber exatamente o backlog pendente&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Implementação com RabbitMQ.Client
&lt;/h3&gt;

&lt;p&gt;A biblioteca oficial &lt;code&gt;RabbitMQ.Client&lt;/code&gt; é 100% open-source (Apache 2.0) e fornece acesso direto ao protocolo AMQP. Combinada com o &lt;code&gt;BackgroundService&lt;/code&gt; do .NET, implementamos um consumer que acumula mensagens em lote e grava tudo de uma vez com &lt;code&gt;BulkInsert&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// dotnet add package RabbitMQ.Client&lt;/span&gt;
&lt;span class="c1"&gt;// dotnet add package EFCore.BulkExtensions&lt;/span&gt;

&lt;span class="c1"&gt;// --- Mensagem ---&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;PedidoCriadoMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DateTime&lt;/span&gt; &lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// --- Producer ---&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PedidoProducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IConnection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;QueueName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"pedidos-queue"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;PublicarAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;PedidoCriadoMessage&lt;/span&gt; &lt;span class="n"&gt;mensagem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateChannelAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QueueDeclareAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;QueueName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;durable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;exclusive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;autoDelete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SerializeToUtf8Bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mensagem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;BasicProperties&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;DeliveryMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DeliveryModes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Persistent&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BasicPublishAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;routingKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;QueueName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;mandatory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;basicProperties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// --- Consumer em lote (BackgroundService) ---&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PedidoConsumerWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;IConnection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IServiceScopeFactory&lt;/span&gt; &lt;span class="n"&gt;scopeFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoConsumerWorker&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;QueueName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"pedidos-queue"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;TamanhoLote&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateChannelAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QueueDeclareAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;QueueName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;durable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;exclusive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;autoDelete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Backpressure: limita mensagens em voo para não sobrecarregar a memória&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BasicQosAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;prefetchSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;prefetchCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TamanhoLote&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;global&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;lote&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;(&lt;/span&gt;&lt;span class="kt"&gt;ulong&lt;/span&gt; &lt;span class="n"&gt;Tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PedidoCriadoMessage&lt;/span&gt; &lt;span class="n"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;)&amp;gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AsyncEventingBasicConsumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReceivedAsync&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ea&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoCriadoMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;ea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;)!;&lt;/span&gt;
            &lt;span class="n"&gt;lote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;ea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeliveryTag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;TamanhoLote&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ProcessarLoteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BasicConsumeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;QueueName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;autoAck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Timer de flush: força o processamento mesmo que o lote não esteja cheio&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;timer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PeriodicTimer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitForNextTickAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ProcessarLoteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ProcessarLoteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;IChannel&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;(&lt;/span&gt;&lt;span class="kt"&gt;ulong&lt;/span&gt; &lt;span class="n"&gt;Tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PedidoCriadoMessage&lt;/span&gt; &lt;span class="n"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;)&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;lote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;lote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pedidos&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Pedido&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Id&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ClienteId&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Valor&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;DataCriacao&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;
        &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scopeFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateScope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dbContext&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bulkConfig&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;BulkConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;BatchSize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;dbContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BulkInsertAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedidos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bulkConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// ACK múltiplo: confirma todo o lote de uma só vez&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BasicAckAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;deliveryTag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;[^&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;multiple&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Lote de {Count} pedidos gravado"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pedidos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Registrando no Program.cs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Program.cs&lt;/span&gt;
&lt;span class="c1"&gt;// dotnet add package RabbitMQ.Client&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IConnection&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ConnectionFactory&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;HostName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;UserName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"guest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"guest"&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="c1"&gt;// CreateConnectionAsync retorna Task — resolvemos aqui para o DI container&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateConnectionAsync&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;GetAwaiter&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;GetResult&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoProducer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoConsumerWorker&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ &lt;strong&gt;Atenção:&lt;/strong&gt; No Oracle, o &lt;code&gt;BulkInsert&lt;/code&gt; do EFCore.BulkExtensions requer o &lt;strong&gt;Oracle Data Provider for .NET (ODP.NET)&lt;/strong&gt;. Certifique-se de registrar o provider correto e de que as sequences foram configuradas corretamente no modelo, ou a inserção em massa vai falhar com erro de constraint de chave primária.&lt;/p&gt;

&lt;h2&gt;
  
  
  RabbitMQ vs Azure Service Bus: Qual Escolher?
&lt;/h2&gt;

&lt;p&gt;As duas opções resolvem o mesmo problema — desacoplar produção e consumo de mensagens — mas com modelos operacionais e econômicos bem distintos. A escolha depende do seu contexto: infraestrutura self-hosted vs cloud managed, custo variável vs fixo, e grau de controle desejado.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Critério&lt;/th&gt;
&lt;th&gt;RabbitMQ&lt;/th&gt;
&lt;th&gt;Azure Service Bus&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tipo&lt;/td&gt;
&lt;td&gt;Open-source (MPL 2.0), self-hosted&lt;/td&gt;
&lt;td&gt;PaaS gerenciado pela Microsoft&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protocolo&lt;/td&gt;
&lt;td&gt;AMQP 0-9-1 (nativo), AMQP 1.0, MQTT, STOMP&lt;/td&gt;
&lt;td&gt;AMQP 1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hospedagem&lt;/td&gt;
&lt;td&gt;Container próprio, VM, Kubernetes&lt;/td&gt;
&lt;td&gt;Azure (sem infra para gerenciar)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custo&lt;/td&gt;
&lt;td&gt;Infraestrutura + operação (seu time)&lt;/td&gt;
&lt;td&gt;Pay-per-use (~$0,10/milhão de ops)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filas&lt;/td&gt;
&lt;td&gt;Queues, Exchanges, Bindings&lt;/td&gt;
&lt;td&gt;Queues e Topics/Subscriptions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tamanho máximo de mensagem&lt;/td&gt;
&lt;td&gt;128 MB (padrão)&lt;/td&gt;
&lt;td&gt;256 KB (Standard) / 100 MB (Premium)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retenção de mensagens&lt;/td&gt;
&lt;td&gt;Até o disco encher / TTL configurável&lt;/td&gt;
&lt;td&gt;14 dias (máximo)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dead-letter queue&lt;/td&gt;
&lt;td&gt;✅ Configurável&lt;/td&gt;
&lt;td&gt;✅ Nativo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sessions (ordenação garantida)&lt;/td&gt;
&lt;td&gt;✅ Via x-single-active-consumer&lt;/td&gt;
&lt;td&gt;✅ Service Bus Sessions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retry automático&lt;/td&gt;
&lt;td&gt;Manual (Dead-letter + requeue)&lt;/td&gt;
&lt;td&gt;Nativo (MaxDeliveryCount)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Escalabilidade horizontal&lt;/td&gt;
&lt;td&gt;Manual (cluster Erlang)&lt;/td&gt;
&lt;td&gt;Automática&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Biblioteca .NET&lt;/td&gt;
&lt;td&gt;RabbitMQ.Client (Apache 2.0)&lt;/td&gt;
&lt;td&gt;Azure.Messaging.ServiceBus (MIT)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Melhor para&lt;/td&gt;
&lt;td&gt;On-premise, multi-cloud, custo controlado&lt;/td&gt;
&lt;td&gt;Ecossistema Azure, equipe pequena, SLA garantido&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Implementação Equivalente com Azure Service Bus
&lt;/h3&gt;

&lt;p&gt;Para migrar do RabbitMQ para o Azure Service Bus em ambiente Azure, o padrão de producer/consumer é similar, usando &lt;code&gt;Azure.Messaging.ServiceBus&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// dotnet add package Azure.Messaging.ServiceBus&lt;/span&gt;

&lt;span class="c1"&gt;// --- Producer com envio em lote nativo ---&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PedidoServiceBusProducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ServiceBusSender&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;PublicarLoteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoCriadoMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mensagens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// O ServiceBusMessageBatch respeita o limite de tamanho automaticamente&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateMessageBatchAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;mensagens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sbMsg&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ServiceBusMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;BinaryData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromObjectAsJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;ContentType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;MessageId&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryAddMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sbMsg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;$"Mensagem &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; excede o limite do lote"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendMessagesAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// --- Consumer com BackgroundService ---&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PedidoServiceBusWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ServiceBusProcessor&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IServiceScopeFactory&lt;/span&gt; &lt;span class="n"&gt;scopeFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoServiceBusWorker&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProcessMessageAsync&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;
                          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToObjectFromJson&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoCriadoMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

            &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scopeFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateScope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dbContext&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceProvider&lt;/span&gt;
                                      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pedido&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Pedido&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Id&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;ClienteId&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Valor&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;

            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;dbContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BulkInsertAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Pedido&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pedido&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// Confirma o processamento — remove da fila&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CompleteMessageAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProcessErrorAsync&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"Erro ao processar mensagem do Service Bus"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartProcessingAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Infinite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;StopAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StopProcessingAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StopAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Program.cs&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ServiceBusClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Endpoint=sb://meu-namespace.servicebus.windows.net/;..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateSender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pedidos-queue"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ServiceBusClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Endpoint=sb://meu-namespace.servicebus.windows.net/;..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pedidos-queue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ServiceBusProcessorOptions&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;MaxConcurrentCalls&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// Paralelismo no consumer&lt;/span&gt;
        &lt;span class="n"&gt;AutoCompleteMessages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;// Controle manual de ACK&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoServiceBusWorker&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 &lt;strong&gt;Dica:&lt;/strong&gt; Em produção no Azure, prefira autenticação via &lt;strong&gt;Managed Identity&lt;/strong&gt; em vez de connection string, usando &lt;code&gt;new ServiceBusClient("meu-namespace.servicebus.windows.net", new DefaultAzureCredential())&lt;/code&gt;. Isso elimina segredos na configuração.&lt;/p&gt;

&lt;h2&gt;
  
  
  O Gargalo de Leitura: Por Que Trazer Tudo de Uma Vez é Perigoso
&lt;/h2&gt;

&lt;h3&gt;
  
  
  O Problema da Query Sem Limite
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ NUNCA faça isso em produção com tabelas grandes&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;todosPedidos&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pedidos&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Itens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cliente&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Pode trazer 500 mil registros para a memória!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa query aparentemente inocente pode:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consumir gigabytes de RAM&lt;/strong&gt; no servidor da aplicação&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bloquear o banco&lt;/strong&gt; com um table scan de longa duração&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gerar timeouts&lt;/strong&gt; em ambientes com alta concorrência&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Saturar a rede&lt;/strong&gt; entre aplicação e banco com tráfego desnecessário&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Travar o GC do .NET&lt;/strong&gt; com objetos grandes que precisam ir para o LOH (Large Object Heap)
No Oracle, um aspecto adicional é o uso do &lt;strong&gt;ROWNUM&lt;/strong&gt; (Oracle 11g) vs &lt;strong&gt;FETCH FIRST &amp;amp;mldr; ROWS ONLY&lt;/strong&gt; (Oracle 12c+), que afeta como a paginação é expressa em SQL. O provider &lt;code&gt;Oracle.EntityFrameworkCore&lt;/code&gt; abstrai isso, mas é importante entender o que está sendo gerado.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tipos de Paginação: Offset vs Keyset (Cursor)
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;📖 &lt;strong&gt;Artigo dedicado:&lt;/strong&gt; Para um guia completo sobre &lt;strong&gt;todas as estratégias de paginação&lt;/strong&gt; (Offset, Keyset, Cursor Server-Side, Time-based e Token Opaco) com SQL Server, Oracle e PostgreSQL — incluindo código EF Core 8+ para cada combinação —, veja: Paginação em APIs REST com C# e EF Core 8: Todos os Tipos, Todos os Bancos. Esta seção apresenta os conceitos essenciais; o artigo linked aprofunda &lt;strong&gt;quando e por que&lt;/strong&gt; usar cada abordagem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Existem duas estratégias principais de paginação, e cada uma tem casos de uso distintos:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Característica&lt;/th&gt;
&lt;th&gt;Offset Pagination&lt;/th&gt;
&lt;th&gt;Keyset (Cursor) Pagination&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SQL gerado&lt;/td&gt;
&lt;td&gt;OFFSET N ROWS FETCH NEXT M&lt;/td&gt;
&lt;td&gt;WHERE id &amp;gt; :lastId&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance em páginas iniciais&lt;/td&gt;
&lt;td&gt;✅ Rápida&lt;/td&gt;
&lt;td&gt;✅ Rápida&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance em páginas tardias&lt;/td&gt;
&lt;td&gt;❌ Lenta (scan cresce)&lt;/td&gt;
&lt;td&gt;✅ Constante&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Suporte a salto de página&lt;/td&gt;
&lt;td&gt;✅ Direto&lt;/td&gt;
&lt;td&gt;❌ Apenas sequencial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Registros novos entre páginas&lt;/td&gt;
&lt;td&gt;❌ Pode duplicar/omitir&lt;/td&gt;
&lt;td&gt;✅ Consistente&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caso de uso ideal&lt;/td&gt;
&lt;td&gt;UI com números de página&lt;/td&gt;
&lt;td&gt;Scroll infinito / APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Paginação por Offset: Simples e Adequada para UIs
&lt;/h2&gt;

&lt;p&gt;A paginação por offset é a mais familiar, e o EF Core a gera corretamente para SQL Server e Oracle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Modelo de request padronizado&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;PaginacaoRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Pagina&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;TamanhoPagina&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Garante limites seguros&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;TamanhoSeguro&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TamanhoPagina&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;OffsetSeguro&lt;/span&gt;  &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Pagina&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;TamanhoSeguro&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Modelo de resposta padronizado&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;PaginaResultado&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dados&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;PaginaAtual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;TamanhoPagina&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;TotalRegistros&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;TotalPaginas&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Serviço de consulta paginada&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PedidoQueryService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PaginaResultado&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoResumoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ListarPedidosAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;PaginacaoRequest&lt;/span&gt; &lt;span class="n"&gt;paginacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;clienteId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Construir query base com filtros opcionais&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pedidos&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsNoTracking&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;           &lt;span class="c1"&gt;// Leitura: sempre AsNoTracking!&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;clienteId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;clienteId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// COUNT separado para o total (EF Core 8 otimiza isso)&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LongCountAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Dados da página&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// Orderby obrigatório para paginação consistente&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Skip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paginacao&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OffsetSeguro&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paginacao&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TamanhoSeguro&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PedidoResumoDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;   &lt;span class="c1"&gt;// Projection: só os campos necessários&lt;/span&gt;
                &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PaginaResultado&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoResumoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="n"&gt;Dados&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;PaginaAtual&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;paginacao&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pagina&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;TamanhoPagina&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;paginacao&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TamanhoSeguro&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;TotalRegistros&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;TotalPaginas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ceiling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;paginacao&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TamanhoSeguro&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 &lt;strong&gt;Dica:&lt;/strong&gt; Sempre use &lt;code&gt;.Select()&lt;/code&gt; com uma &lt;strong&gt;projection&lt;/strong&gt; (DTO ou record), nunca retorne a entidade completa em queries de listagem. Isso reduz o volume de dados transferidos e evita o carregamento de navegações desnecessárias.&lt;/p&gt;

&lt;h3&gt;
  
  
  SQL Gerado: SQL Server vs Oracle
&lt;/h3&gt;

&lt;p&gt;O EF Core 8 gera SQL correto e otimizado para ambos os bancos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- SQL Server (gerado pelo EF Core 8)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'cliente-123'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;
&lt;span class="k"&gt;OFFSET&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt; &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;FETCH&lt;/span&gt; &lt;span class="k"&gt;NEXT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;ONLY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Oracle 12c+ (gerado pelo Oracle.EntityFrameworkCore 8)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;clienteId_0&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;
&lt;span class="k"&gt;OFFSET&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt; &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;FETCH&lt;/span&gt; &lt;span class="k"&gt;NEXT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;ONLY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ &lt;strong&gt;Atenção:&lt;/strong&gt; Para &lt;strong&gt;Oracle 11g&lt;/strong&gt; (sem &lt;code&gt;FETCH FIRST&lt;/code&gt;), o provider gera uma query com &lt;code&gt;ROWNUM&lt;/code&gt; aninhado, que pode ter custo adicional em tabelas muito grandes. Considere migrar para Oracle 12c+ ou usar views materializadas para relatórios pesados.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keyset Pagination: Alta Performance para Scroll Infinito e APIs
&lt;/h2&gt;

&lt;p&gt;Quando o usuário navega para a página 500 de um resultado com 10 mil itens, o banco precisa fazer um &lt;strong&gt;scan de 10.000 linhas&lt;/strong&gt; só para pular as primeiras 9.980. Com keyset pagination, você usa o valor da última linha buscada como ponto de partida da próxima consulta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Request com cursor (keyset)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;KeysetRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;UltimoId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;UltimaDataCriacao&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Limite&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Resultado com cursor para a próxima página&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;KeysetResultado&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dados&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;TemProximaPagina&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ProximoCursorId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ProximoCursorData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PedidoKeysetService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;KeysetResultado&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoResumoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ListarComCursorAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;KeysetRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Filtro de keyset: busca registros "após" o cursor&lt;/span&gt;
        &lt;span class="c1"&gt;// Usando (DataCriacao, Id) como chave composta para estabilidade&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pedidos&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsNoTracking&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
                &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UltimaDataCriacao&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt;
                &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UltimaDataCriacao&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UltimaDataCriacao&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
                 &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CompareTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UltimoId&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThenBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Busca Limite + 1 para saber se há próxima página&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Limite&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PedidoResumoDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;temProxima&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Limite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temProxima&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RemoveAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dados&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Remove o item extra&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ultimo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LastOrDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;KeysetResultado&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoResumoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="n"&gt;Dados&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;TemProximaPagina&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;temProxima&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ProximoCursorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ultimo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ProximoCursorData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ultimo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este padrão mantém &lt;strong&gt;performance constante&lt;/strong&gt; independente do número da página, pois o SQL gerado usa um simples &lt;code&gt;WHERE&lt;/code&gt; com índice em vez de &lt;code&gt;OFFSET&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- SQL gerado (SQL Server / Oracle)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;TOP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2025-06-01'&lt;/span&gt;
   &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2025-06-01'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'abc-123'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 &lt;strong&gt;Dica:&lt;/strong&gt; Crie um &lt;strong&gt;índice composto&lt;/strong&gt; nas colunas de keyset para garantir que a query use index seek em vez de table scan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- SQL Server&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX_Pedidos_Keyset&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;INCLUDE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Oracle&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX_Pedidos_Keyset&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Juntando Tudo: A Arquitetura Completa
&lt;/h2&gt;

&lt;p&gt;Com as duas soluções combinadas, a arquitetura da aplicação fica assim:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Escrita:&lt;/strong&gt; API recebe → publica na fila → retorna &lt;code&gt;202 Accepted&lt;/code&gt; → Consumer processa em lote com BulkInsert&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leitura:&lt;/strong&gt; API recebe request paginado → executa query com Skip/Take (offset) ou WHERE (keyset) → retorna somente os dados necessários
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Endpoint ASP.NET Core — escrita assíncrona&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/pedidos/lote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FromBody&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CriarPedidoRequest&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;PedidoService&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhenAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CriarPedidoAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

    &lt;span class="c1"&gt;// 202 Accepted: os pedidos foram enfileirados, não necessariamente gravados&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Accepted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/pedidos/status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Endpoint ASP.NET Core — leitura paginada&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/pedidos"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;AsParameters&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;PaginacaoRequest&lt;/span&gt; &lt;span class="n"&gt;paginacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FromQuery&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;clienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;PedidoQueryService&lt;/span&gt; &lt;span class="n"&gt;queryService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;queryService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ListarPedidosAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paginacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dicas e Boas Práticas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Índices adequados:&lt;/strong&gt; Toda coluna usada em &lt;code&gt;OrderBy&lt;/code&gt;, &lt;code&gt;Where&lt;/code&gt; ou keyset cursor deve ter índice. No Oracle, use índices funcionais para colunas com transformações (ex.: &lt;code&gt;UPPER(nome)&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AsNoTracking sempre em leituras:&lt;/strong&gt; Desativa o Change Tracker para queries de leitura, reduzindo uso de memória e CPU em até 30%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evite &lt;code&gt;Count()&lt;/code&gt; em tabelas grandes:&lt;/strong&gt; Para keyset pagination, você não precisa do total de registros. Para offset, considere cachear o total por alguns segundos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Defina timeouts explícitos:&lt;/strong&gt; Configure &lt;code&gt;context.Database.SetCommandTimeout(30)&lt;/code&gt; ou use &lt;code&gt;WithTimeout&lt;/code&gt; por consulta para evitar queries longas bloqueando a aplicação indefinidamente.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitore o tamanho da fila:&lt;/strong&gt; Configure alertas para quando a fila ultrapassar N mensagens — é o sinal de que o consumer não está dando conta do volume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;IAsyncEnumerable&lt;/code&gt; para streaming:&lt;/strong&gt; Para exportações de CSV ou processamento de grandes volumes que não precisam de paginação, use &lt;code&gt;AsAsyncEnumerable()&lt;/code&gt; para processar linha por linha sem carregar tudo na memória:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Streaming com IAsyncEnumerable — sem carregar tudo na memória&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pedido&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pedidos&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsNoTracking&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsAsyncEnumerable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithCancellation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;exportador&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EscreverLinhaAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transações explícitas para consistência:&lt;/strong&gt; Ao usar BulkInsert com múltiplas tabelas (ex.: gravar &lt;code&gt;Pedido&lt;/code&gt; e &lt;code&gt;ItensPedido&lt;/code&gt; em conjunto), envolva a operação em uma transação explícita para garantir atomicidade.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Os gargalos de banco de dados com EF Core raramente são culpa do banco em si. Na esmagadora maioria dos casos, o problema está na forma como a aplicação interage com ele: &lt;strong&gt;inserindo um registro por vez&lt;/strong&gt; em vez de em lote, e &lt;strong&gt;trazendo todo o resultado&lt;/strong&gt; em vez de paginar.&lt;/p&gt;

&lt;p&gt;A combinação de &lt;strong&gt;mensageria&lt;/strong&gt; (RabbitMQ ou Azure Service Bus) para desacoplar gravação em massa e &lt;strong&gt;paginação eficiente&lt;/strong&gt; (offset para UIs, keyset para APIs e scroll infinito) resolve os dois gargalos de forma elegante, escalável e resiliente. Com EF Core 8.0+ e as técnicas apresentadas aqui, você consegue suportar volumes muito maiores sem mudar a estrutura do banco, apenas ajustando a forma como a aplicação se comunica com ele.&lt;/p&gt;

&lt;p&gt;O próximo passo natural é observabilidade: instrumentar as queries lentas com Application Performance Monitoring (APM), criar alertas para queries acima de N segundos, e mapear os índices faltantes via Query Store (SQL Server) ou AWR (Oracle).&lt;/p&gt;

&lt;h2&gt;
  
  
  Leia Também
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;EF Core 8 com Fluent API: Mapeamento, ORM e Desacoplamento Total&lt;/li&gt;
&lt;li&gt;EF Core Migrations em Multi-Projeto: Secrets, Scaffolding e Gestão em Times&lt;/li&gt;
&lt;li&gt;Full-Text Search em APIs REST com C#: SQL Server, PostgreSQL e Oracle&lt;/li&gt;
&lt;li&gt;Arquitetura de Software e os Padrões GoF: do Código à Nuvem, do Monólito ao Microserviço&lt;/li&gt;
&lt;li&gt;Design de APIs REST: Sem Verbos na URL, Métodos HTTP e Binding de Parâmetros no ASP.NET Core&lt;/li&gt;
&lt;li&gt;Paginação em APIs REST com C# e EF Core 8: Todos os Tipos, Todos os Bancos&lt;/li&gt;
&lt;li&gt;Programação Assíncrona em C#: async/await do Fundamento à Produção&lt;/li&gt;
&lt;li&gt;Paralelismo em C#: Parallel, PLINQ e Tasks do Fundamento à Produção&lt;/li&gt;
&lt;li&gt;.NET Worker e Background Service para Alto Volume&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Referências
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;EF Core — Bulk Operations (Microsoft Docs) — Estratégias oficiais de atualização eficiente com EF Core&lt;/li&gt;
&lt;li&gt;EFCore.BulkExtensions — GitHub — Biblioteca open-source para bulk insert/update/delete no EF Core&lt;/li&gt;
&lt;li&gt;RabbitMQ.Client — NuGet / GitHub — Biblioteca oficial .NET para RabbitMQ (Apache 2.0)&lt;/li&gt;
&lt;li&gt;Azure.Messaging.ServiceBus — Documentação — Guia oficial do Azure Service Bus com .NET&lt;/li&gt;
&lt;li&gt;RabbitMQ vs Azure Service Bus — Comparativo — Visão geral do Azure Service Bus e quando usá-lo&lt;/li&gt;
&lt;li&gt;Oracle EF Core Provider — GitHub — Exemplos oficiais do provider Oracle para EF Core&lt;/li&gt;
&lt;li&gt;Use the query store (SQL Server) — Monitoramento de queries lentas no SQL Server via Query Store&lt;/li&gt;
&lt;li&gt;Repositório blog-zocateli-sample — Messaging — Código-fonte completo dos exemplos deste artigo
📬&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;👉 &lt;strong&gt;Artigo completo com todos os exemplos de código:&lt;/strong&gt; &lt;a href="https://zocate.li/posts/2026/gargalo-banco-dados-efcore-mensageria-paginacao/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog" rel="noopener noreferrer"&gt;Gargalo em Banco de Dados: Mensageria e Paginação&lt;/a&gt;&lt;/p&gt;

</description>
      <category>portuguese</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>efcore</category>
    </item>
    <item>
      <title>pip É Lento, Poetry Complexo: UV Chegou Resolvendo o Python</title>
      <dc:creator>Lincoln Zocateli</dc:creator>
      <pubDate>Thu, 16 Apr 2026 01:00:50 +0000</pubDate>
      <link>https://dev.to/lzocate-li/pip-e-lento-poetry-complexo-uv-chegou-resolvendo-o-python-2c9m</link>
      <guid>https://dev.to/lzocate-li/pip-e-lento-poetry-complexo-uv-chegou-resolvendo-o-python-2c9m</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;Se você já trabalhou com Python por mais de cinco minutos, provavelmente já viveu esse ritual: criar um ambiente virtual com &lt;code&gt;venv&lt;/code&gt;, instalar pacotes com &lt;code&gt;pip&lt;/code&gt;, descobrir que precisa do &lt;code&gt;pip-tools&lt;/code&gt; para gerar um lockfile, usar &lt;code&gt;pyenv&lt;/code&gt; para trocar de versão do Python, considerar o &lt;code&gt;Poetry&lt;/code&gt; mas desistir da complexidade, e eventualmente aceitar que o gerenciamento de dependências em Python é um exercício de paciência.&lt;/p&gt;

&lt;p&gt;O ecossistema Python sempre foi assim — &lt;strong&gt;poderoso, mas fragmentado&lt;/strong&gt;. Cada ferramenta resolve um pedaço do problema. Nenhuma resolve tudo. E o desenvolvedor fica no meio, colando peças como um quebra-cabeça infinito.&lt;/p&gt;

&lt;p&gt;Isso mudou. Em fevereiro de 2024, a &lt;strong&gt;Astral&lt;/strong&gt; — a mesma empresa por trás do Ruff, o linter Python mais rápido do mundo — lançou o &lt;strong&gt;UV&lt;/strong&gt;: um gerenciador de pacotes, ambientes virtuais e versões do Python escrito em &lt;strong&gt;Rust&lt;/strong&gt;, até &lt;strong&gt;100 vezes mais rápido&lt;/strong&gt; que o &lt;code&gt;pip&lt;/code&gt;. E ele não veio para ser mais uma peça do quebra-cabeça. Ele veio para ser &lt;strong&gt;o quebra-cabeça inteiro&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Neste artigo, vamos explorar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A história do Python — quem criou, quando, e quem mantém hoje&lt;/li&gt;
&lt;li&gt;Por que Python ganhou tanta notoriedade nos últimos anos&lt;/li&gt;
&lt;li&gt;O problema de fragmentação que o UV resolve&lt;/li&gt;
&lt;li&gt;O que é o UV, quem criou, e como usá-lo na prática&lt;/li&gt;
&lt;li&gt;E a pergunta que não cala: &lt;strong&gt;em aplicações complexas, com padrões arquiteturais e modularidade, Python compete com C#?&lt;/strong&gt;
Se você já usa Python ou está avaliando se deve usá-lo, este artigo é para você.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Python: Uma Breve História
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Origens
&lt;/h3&gt;

&lt;p&gt;O Python foi criado por &lt;strong&gt;Guido van Rossum&lt;/strong&gt;, um programador holandês que trabalhava no &lt;strong&gt;CWI&lt;/strong&gt; (Centrum Wiskunde &amp;amp; Informatica) em Amsterdã. O desenvolvimento começou no final de &lt;strong&gt;1989&lt;/strong&gt;, e a primeira versão pública — Python 0.9.0 — foi lançada em &lt;strong&gt;fevereiro de 1991&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;O nome não tem nada a ver com cobras. Guido era fã do grupo de comédia britânico &lt;strong&gt;Monty Python&lt;/strong&gt;, e queria um nome curto, misterioso e levemente irreverente. Funcionou.&lt;/p&gt;

&lt;h3&gt;
  
  
  Filosofia
&lt;/h3&gt;

&lt;p&gt;Desde o início, Python foi projetado com uma filosofia clara: &lt;strong&gt;legibilidade acima de tudo&lt;/strong&gt;. Onde outras linguagens usam chaves &lt;code&gt;{}&lt;/code&gt; e ponto-e-vírgula &lt;code&gt;;&lt;/code&gt;, Python usa &lt;strong&gt;indentação&lt;/strong&gt;. A ideia era que o código deveria ser tão fácil de ler quanto de escrever.&lt;/p&gt;

&lt;p&gt;Essa filosofia foi formalizada no &lt;strong&gt;Zen of Python&lt;/strong&gt; (PEP 20), que você pode ver rodando &lt;code&gt;import this&lt;/code&gt; no interpretador:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Readability counts.
There should be one -- and preferably only one -- obvious way to do it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 O Zen of Python não é apenas poesia — ele guia decisões de design da linguagem até hoje.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Guido e o BDFL
&lt;/h3&gt;

&lt;p&gt;Por quase três décadas, Guido atuou como &lt;strong&gt;BDFL&lt;/strong&gt; — &lt;em&gt;Benevolent Dictator For Life&lt;/em&gt; (Ditador Benevolente Vitalício). Ele tinha a palavra final sobre qualquer decisão de design da linguagem.&lt;/p&gt;

&lt;p&gt;Isso mudou em &lt;strong&gt;julho de 2018&lt;/strong&gt;, quando a controversa &lt;strong&gt;PEP 572&lt;/strong&gt; (que introduziu o operador &lt;code&gt;:=&lt;/code&gt;, apelidado de &lt;em&gt;walrus operator&lt;/em&gt;) gerou debates tão intensos na comunidade que Guido decidiu se afastar. Ele renunciou ao título de BDFL e se aposentou da liderança direta.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quem mantém Python hoje
&lt;/h3&gt;

&lt;p&gt;Desde 2019, Python é governado por um &lt;strong&gt;Steering Council&lt;/strong&gt; — um comitê eleito de cinco membros que toma as decisões de design e direção da linguagem. O conselho é eleito pela comunidade de core developers a cada release. Por trás do Steering Council está a &lt;strong&gt;Python Software Foundation (PSF)&lt;/strong&gt;, uma organização sem fins lucrativos que cuida da infraestrutura, eventos, financiamento e proteção da marca Python.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ Guido van Rossum trabalhou na Dropbox (2013–2019) e depois na Microsoft (2020–2023), onde contribuiu significativamente para a performance do CPython (o interpretador padrão). Hoje segue como &lt;em&gt;core developer&lt;/em&gt; emeritus, mas sem poder de veto.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Versões marcantes
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ano&lt;/th&gt;
&lt;th&gt;Versão&lt;/th&gt;
&lt;th&gt;Marco&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1991&lt;/td&gt;
&lt;td&gt;0.9.0&lt;/td&gt;
&lt;td&gt;Primeira versão pública&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2000&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;
&lt;td&gt;List comprehensions, garbage collector&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2008&lt;/td&gt;
&lt;td&gt;3.0&lt;/td&gt;
&lt;td&gt;Quebra de compatibilidade com Python 2 (migração dolorosa que durou mais de uma década)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2015&lt;/td&gt;
&lt;td&gt;3.5&lt;/td&gt;
&lt;td&gt;async/await nativo, type hints (PEP 484)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2016&lt;/td&gt;
&lt;td&gt;3.6&lt;/td&gt;
&lt;td&gt;f-strings (f"Hello {name}") — mudou a forma de formatar strings para sempre&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2020&lt;/td&gt;
&lt;td&gt;3.9&lt;/td&gt;
&lt;td&gt;Merge de dicionários com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2022&lt;/td&gt;
&lt;td&gt;3.11&lt;/td&gt;
&lt;td&gt;Melhorias de performance de 10-60% (projeto Faster CPython de Guido na Microsoft)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2023&lt;/td&gt;
&lt;td&gt;3.12&lt;/td&gt;
&lt;td&gt;Per-interpreter GIL (início do caminho para remover o GIL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024&lt;/td&gt;
&lt;td&gt;3.13&lt;/td&gt;
&lt;td&gt;Free-threaded mode experimental (sem GIL)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ A migração de Python 2 para Python 3 foi uma das mais dolorosas da história da computação. Levou &lt;strong&gt;mais de 12 anos&lt;/strong&gt; — Python 2 só teve seu EOL (End of Life) em janeiro de 2020. Essa experiência traumática moldou a comunidade: desde então, Python evita quebras de compatibilidade a todo custo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Por Que Python Ganhou Tanta Notoriedade
&lt;/h2&gt;

&lt;p&gt;Se em 2010 alguém te dissesse que Python seria a linguagem mais popular do mundo em poucos anos, você provavelmente duvidaria. Naquela época, o reinado era de Java, C e C++. Python era visto como linguagem de script, boa para automação e pouco mais. O que mudou?&lt;/p&gt;

&lt;h3&gt;
  
  
  A revolução dos dados e da IA
&lt;/h3&gt;

&lt;p&gt;A ascensão da &lt;strong&gt;ciência de dados&lt;/strong&gt;, do &lt;strong&gt;machine learning&lt;/strong&gt; e da &lt;strong&gt;inteligência artificial&lt;/strong&gt; colocou Python no centro do mapa. Não porque Python fosse a linguagem mais rápida ou mais robusta — mas porque o &lt;strong&gt;ecossistema&lt;/strong&gt; se formou ao redor dela:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Área&lt;/th&gt;
&lt;th&gt;Bibliotecas Python dominantes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Computação numérica&lt;/td&gt;
&lt;td&gt;NumPy, SciPy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Análise de dados&lt;/td&gt;
&lt;td&gt;Pandas, Polars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Machine Learning&lt;/td&gt;
&lt;td&gt;scikit-learn, XGBoost, LightGBM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deep Learning&lt;/td&gt;
&lt;td&gt;TensorFlow, PyTorch, Keras&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IA Generativa&lt;/td&gt;
&lt;td&gt;LangChain, Hugging Face Transformers, OpenAI SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visualização&lt;/td&gt;
&lt;td&gt;Matplotlib, Seaborn, Plotly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notebooks&lt;/td&gt;
&lt;td&gt;Jupyter, Google Colab&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Quando TensorFlow (Google, 2015) e PyTorch (Meta, 2016) escolheram Python como interface principal, o destino estava selado. Pesquisadores, cientistas de dados e engenheiros de ML se concentraram em Python, e o efeito de rede fez o resto.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adoção acadêmica
&lt;/h3&gt;

&lt;p&gt;Universidades no mundo inteiro passaram a ensinar programação com Python em vez de Java ou C. O MIT trocou seu curso introdutório para Python em 2009. Stanford seguiu. A consequência: uma &lt;strong&gt;geração inteira de desenvolvedores&lt;/strong&gt; cresceu com Python como primeira linguagem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Versatilidade
&lt;/h3&gt;

&lt;p&gt;Python não é apenas para IA. Ele tem presença relevante em:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Web&lt;/strong&gt;: Django (full-stack), FastAPI (APIs modernas), Flask (microframework)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automação e DevOps&lt;/strong&gt;: Ansible, scripts de infraestrutura, CI/CD&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scripting&lt;/strong&gt;: substituição de Bash para tarefas complexas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;APIs e microsserviços&lt;/strong&gt;: com FastAPI + Pydantic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Educação&lt;/strong&gt;: pela sintaxe limpa e barreira de entrada baixa&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Python em números (2025–2026)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Indicador&lt;/th&gt;
&lt;th&gt;Posição / Métrica&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TIOBE Index&lt;/td&gt;
&lt;td&gt;#1 (líder desde 2021)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stack Overflow Survey 2025&lt;/td&gt;
&lt;td&gt;Top 3 linguagens mais usadas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Octoverse 2025&lt;/td&gt;
&lt;td&gt;#1 em novos repositórios&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PyPI (repositório oficial)&lt;/td&gt;
&lt;td&gt;600.000+ pacotes publicados&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Downloads mensais (PyPI)&lt;/td&gt;
&lt;td&gt;15+ bilhões&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Python não é necessariamente a melhor linguagem para cada problema. Mas é a linguagem com &lt;strong&gt;o menor atrito para começar&lt;/strong&gt; — e isso conta muito quando o objetivo é validar ideias, prototipar ou iterar rapidamente.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  O Problema Que UV Resolve
&lt;/h2&gt;

&lt;p&gt;Agora que entendemos por que Python é tão popular, vamos ao ponto que todo desenvolvedor Python conhece bem: o &lt;strong&gt;caos do gerenciamento de pacotes e ambientes&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  O ecossistema antes do UV
&lt;/h3&gt;

&lt;p&gt;A história do gerenciamento de dependências em Python é uma história de &lt;strong&gt;fragmentação&lt;/strong&gt;. Cada ferramenta resolve um pedaço do problema, mas nenhuma resolve tudo:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ferramenta&lt;/th&gt;
&lt;th&gt;O que faz&lt;/th&gt;
&lt;th&gt;O que NÃO faz&lt;/th&gt;
&lt;th&gt;Problema&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;pip&lt;/td&gt;
&lt;td&gt;Instala pacotes&lt;/td&gt;
&lt;td&gt;Não gerencia ambientes, não faz lockfile nativo&lt;/td&gt;
&lt;td&gt;Lento (resolução em Python puro), sem reprodutibilidade&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;virtualenv / venv&lt;/td&gt;
&lt;td&gt;Cria ambientes virtuais&lt;/td&gt;
&lt;td&gt;Não instala pacotes, não gerencia versões do Python&lt;/td&gt;
&lt;td&gt;É apenas uma peça do quebra-cabeça&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pip-tools&lt;/td&gt;
&lt;td&gt;Gera lockfile (requirements.txt pinado)&lt;/td&gt;
&lt;td&gt;Não gerencia ambientes nem versões do Python&lt;/td&gt;
&lt;td&gt;Exige combinação manual com pip + venv&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pipenv&lt;/td&gt;
&lt;td&gt;Combina pip + virtualenv + lockfile&lt;/td&gt;
&lt;td&gt;Performance ruim, manutenção instável&lt;/td&gt;
&lt;td&gt;Praticamente abandonado pela comunidade&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Poetry&lt;/td&gt;
&lt;td&gt;Gerencia pacotes, venv, lockfile, build&lt;/td&gt;
&lt;td&gt;Não gerencia versões do Python&lt;/td&gt;
&lt;td&gt;Complexo, lento, resolve tudo mas com custo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conda&lt;/td&gt;
&lt;td&gt;Gerencia pacotes + ambientes + Python + C/Fortran&lt;/td&gt;
&lt;td&gt;Mundo paralelo ao PyPI&lt;/td&gt;
&lt;td&gt;Conflitos com pip, ecossistema isolado&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pyenv&lt;/td&gt;
&lt;td&gt;Gerencia versões do Python&lt;/td&gt;
&lt;td&gt;Não instala pacotes, não gerencia ambientes&lt;/td&gt;
&lt;td&gt;Mais uma ferramenta para aprender&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pipx&lt;/td&gt;
&lt;td&gt;Executa ferramentas Python isoladas&lt;/td&gt;
&lt;td&gt;Não é para projetos&lt;/td&gt;
&lt;td&gt;Escopo limitado&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  O fluxo típico antes do UV
&lt;/h3&gt;

&lt;p&gt;Para montar um projeto Python “direito” antes do UV, o desenvolvedor precisava combinara múltiplas ferramentas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ❌ Fluxo fragmentado (antes do UV)&lt;/span&gt;

&lt;span class="c"&gt;# 1. Instalar a versão certa do Python&lt;/span&gt;
pyenv &lt;span class="nb"&gt;install &lt;/span&gt;3.12.1
pyenv &lt;span class="nb"&gt;local &lt;/span&gt;3.12.1

&lt;span class="c"&gt;# 2. Criar ambiente virtual&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate    &lt;span class="c"&gt;# Linux/macOS&lt;/span&gt;
&lt;span class="c"&gt;# .venv\Scripts\Activate.ps1 # Windows&lt;/span&gt;

&lt;span class="c"&gt;# 3. Instalar dependências&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;fastapi uvicorn

&lt;span class="c"&gt;# 4. Gerar lockfile (se usando pip-tools)&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;pip-tools
pip-compile requirements.in &lt;span class="nt"&gt;-o&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# 5. Executar ferramentas de linting (se usando pipx)&lt;/span&gt;
pipx &lt;span class="nb"&gt;install &lt;/span&gt;ruff
ruff check &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cinco ferramentas diferentes. Cinco comandos de instalação. Cinco coisas para documentar e explicar para quem chega no projeto. Esse é o problema que o UV resolve.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ O Poetry tentou unificar parte desse fluxo, e para muitos projetos funciona bem. Mas a resolução de dependências é lenta (escrita em Python), o &lt;code&gt;pyproject.toml&lt;/code&gt; tem convenções próprias que divergem dos padrões PEP, e ele ainda exige &lt;code&gt;pyenv&lt;/code&gt; separado para gerenciar versões do Python.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  UV: O Que É e Quem Criou
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A Astral e Charlie Marsh
&lt;/h3&gt;

&lt;p&gt;O UV foi criado pela &lt;strong&gt;Astral&lt;/strong&gt;, uma empresa fundada por &lt;strong&gt;Charlie Marsh&lt;/strong&gt; em 2023. Charlie é um engenheiro de software americano que antes de fundar a Astral trabalhava com infraestrutura Python e percebeu que as ferramentas fundamentais do ecossistema estavam décadas atrás do que outras linguagens ofereciam.&lt;/p&gt;

&lt;p&gt;A Astral já era conhecida pelo &lt;strong&gt;Ruff&lt;/strong&gt; — um linter e formatter para Python escrito em &lt;strong&gt;Rust&lt;/strong&gt; que é &lt;strong&gt;100 a 300 vezes mais rápido&lt;/strong&gt; que ferramentas equivalentes como Flake8 e Black. O sucesso do Ruff provou que reescrever tooling Python em Rust gerava ganhos reais e imensos.&lt;/p&gt;

&lt;p&gt;Em &lt;strong&gt;fevereiro de 2024&lt;/strong&gt;, a Astral lançou o UV com a mesma premissa: pegar o que o ecossistema Python fazia de forma lenta e fragmentada e reescrever em Rust, &lt;strong&gt;rápido e unificado&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 A Astral recebeu investimento de US$ 4 milhões da Accel para desenvolver o UV e o Ruff. Charlie Marsh é ex-engenheiro do Spring Health e do Khan Academy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  O que o UV faz
&lt;/h3&gt;

&lt;p&gt;O UV é um &lt;strong&gt;toolkit unificado para Python&lt;/strong&gt;, escrito em Rust. Ele substitui — sozinho — as seguintes ferramentas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────┐
│                         UV                              │
│                                                         │
│   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│   │ Gerenciador  │  │  Ambientes   │  │  Versões do  │  │
│   │ de pacotes   │  │  virtuais    │  │  Python      │  │
│   │ (pip,        │  │ (virtualenv, │  │ (pyenv)      │  │
│   │  pip-tools)  │  │  venv)       │  │              │  │
│   └──────────────┘  └──────────────┘  └──────────────┘  │
│                                                         │
│   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│   │ Gerenciador  │  │  Execução    │  │  Lockfile    │  │
│   │ de projetos  │  │  de scripts  │  │  nativo      │  │
│   │ (poetry)     │  │  e tools     │  │  (uv.lock)   │  │
│   │              │  │  (pipx)      │  │              │  │
│   └──────────────┘  └──────────────┘  └──────────────┘  │
│                                                         │
└─────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Em termos concretos:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capacidade&lt;/th&gt;
&lt;th&gt;Ferramenta que substitui&lt;/th&gt;
&lt;th&gt;Comando UV&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Instalar pacotes&lt;/td&gt;
&lt;td&gt;pip install&lt;/td&gt;
&lt;td&gt;uv add / uv pip install&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Criar ambiente virtual&lt;/td&gt;
&lt;td&gt;python -m venv&lt;/td&gt;
&lt;td&gt;uv venv (automático)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gerar lockfile&lt;/td&gt;
&lt;td&gt;pip-compile (pip-tools)&lt;/td&gt;
&lt;td&gt;uv lock (automático)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gerenciar versões do Python&lt;/td&gt;
&lt;td&gt;pyenv install&lt;/td&gt;
&lt;td&gt;uv python install&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Criar projeto&lt;/td&gt;
&lt;td&gt;poetry init&lt;/td&gt;
&lt;td&gt;uv init&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Executar ferramentas&lt;/td&gt;
&lt;td&gt;pipx run&lt;/td&gt;
&lt;td&gt;uvx&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Executar scripts&lt;/td&gt;
&lt;td&gt;python script.py&lt;/td&gt;
&lt;td&gt;uv run script.py&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Velocidade
&lt;/h3&gt;

&lt;p&gt;A diferença de velocidade não é incremental — é &lt;strong&gt;de outra ordem de grandeza&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operação&lt;/th&gt;
&lt;th&gt;pip&lt;/th&gt;
&lt;th&gt;UV&lt;/th&gt;
&lt;th&gt;Fator&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Instalar dependências (cache frio)&lt;/td&gt;
&lt;td&gt;~22s&lt;/td&gt;
&lt;td&gt;~1.2s&lt;/td&gt;
&lt;td&gt;18x mais rápido&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instalar dependências (cache quente)&lt;/td&gt;
&lt;td&gt;~5s&lt;/td&gt;
&lt;td&gt;~0.06s&lt;/td&gt;
&lt;td&gt;80x mais rápido&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resolução de dependências&lt;/td&gt;
&lt;td&gt;segundos a minutos&lt;/td&gt;
&lt;td&gt;milissegundos&lt;/td&gt;
&lt;td&gt;10-100x mais rápido&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Criar ambiente virtual&lt;/td&gt;
&lt;td&gt;~1.5s&lt;/td&gt;
&lt;td&gt;~0.01s&lt;/td&gt;
&lt;td&gt;150x mais rápido&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;📌 Esses números variam conforme o projeto e o ambiente, mas a ordem de grandeza é consistente. O UV faz em milissegundos o que o pip faz em segundos — e em segundos o que o pip faz em minutos.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Essa velocidade não é acidente. O UV é escrito em &lt;strong&gt;Rust&lt;/strong&gt;, com resolução de dependências implementada em um solver SAT otimizado (PubGrub), cache agressivo, downloads paralelos e zero overhead de interpretador Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  UV na Prática
&lt;/h2&gt;

&lt;p&gt;Chega de teoria. Vamos ver o UV funcionando.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instalação
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Windows (PowerShell)&lt;/span&gt;
powershell &lt;span class="nt"&gt;-ExecutionPolicy&lt;/span&gt; ByPass &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"irm https://astral.sh/uv/install.ps1 | iex"&lt;/span&gt;

&lt;span class="c"&gt;# Linux / macOS&lt;/span&gt;
curl &lt;span class="nt"&gt;-LsSf&lt;/span&gt; https://astral.sh/uv/install.sh | sh

&lt;span class="c"&gt;# Via pip (se preferir — mas perde parte da graça)&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;uv

&lt;span class="c"&gt;# Via Homebrew (macOS)&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;uv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Após instalar, verifique:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uv 0.6.x
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Criar um projeto novo
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv init meu-projeto-api
&lt;span class="nb"&gt;cd &lt;/span&gt;meu-projeto-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O UV gera a seguinte estrutura:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;meu-projeto-api/
├── .python-version      # Versão do Python fixada
├── pyproject.toml        # Metadados e dependências do projeto
├── README.md
└── main.py               # Entrypoint básico
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;pyproject.toml&lt;/code&gt; gerado segue os &lt;strong&gt;padrões PEP&lt;/strong&gt; (621, 517):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[project]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"meu-projeto-api"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1.0"&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Add your description here"&lt;/span&gt;
&lt;span class="py"&gt;readme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"README.md"&lt;/span&gt;
&lt;span class="py"&gt;requires-python&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;3.12&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="py"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 Diferente do Poetry, que usa chaves proprietárias como &lt;code&gt;[tool.poetry]&lt;/code&gt;, o UV segue os padrões PEP oficiais. Isso significa que seu &lt;code&gt;pyproject.toml&lt;/code&gt; é compatível com qualquer ferramenta que siga as PEPs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Adicionar dependências
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Adicionar dependências de produção&lt;/span&gt;
uv add fastapi uvicorn[standard]

&lt;span class="c"&gt;# Adicionar dependências de desenvolvimento&lt;/span&gt;
uv add &lt;span class="nt"&gt;--dev&lt;/span&gt; pytest ruff mypy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O que acontece por baixo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O UV &lt;strong&gt;atualiza o &lt;code&gt;pyproject.toml&lt;/code&gt;&lt;/strong&gt; com as novas dependências&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolve todas as dependências&lt;/strong&gt; (em milissegundos)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gera/atualiza o &lt;code&gt;uv.lock&lt;/code&gt;&lt;/strong&gt; — um lockfile com hashes e versões exatas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cria o ambiente virtual&lt;/strong&gt; (&lt;code&gt;.venv/&lt;/code&gt;) automaticamente, se não existir&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instala tudo&lt;/strong&gt; no ambiente virtual
Tudo em &lt;strong&gt;um único comando&lt;/strong&gt;. Em &lt;strong&gt;menos de dois segundos&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Executar o projeto
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Executar diretamente (UV ativa o venv automaticamente)&lt;/span&gt;
uv run uvicorn main:app &lt;span class="nt"&gt;--reload&lt;/span&gt;

&lt;span class="c"&gt;# Ou executar qualquer script Python&lt;/span&gt;
uv run python main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 Não precisa ativar o ambiente virtual manualmente. O &lt;code&gt;uv run&lt;/code&gt; faz isso por você. Acabou o &lt;code&gt;source .venv/bin/activate&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Gerenciar versões do Python
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Listar versões disponíveis&lt;/span&gt;
uv python list

&lt;span class="c"&gt;# Instalar uma versão específica&lt;/span&gt;
uv python &lt;span class="nb"&gt;install &lt;/span&gt;3.12

&lt;span class="c"&gt;# Fixar a versão do projeto&lt;/span&gt;
uv python pin 3.12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O UV baixa e gerencia as versões do Python sem precisar do &lt;code&gt;pyenv&lt;/code&gt;. Funciona em &lt;strong&gt;Windows, Linux e macOS&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Executar ferramentas sem instalar
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Executar o Ruff sem instalar no projeto&lt;/span&gt;
uvx ruff check &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Executar o Black sem instalar&lt;/span&gt;
uvx black &lt;span class="nt"&gt;--check&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Executar qualquer ferramenta do PyPI&lt;/span&gt;
uvx httpie https://api.github.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;uvx&lt;/code&gt; é equivalente ao &lt;code&gt;npx&lt;/code&gt; do Node.js — executa ferramentas Python em ambientes temporários, sem poluir seu projeto.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fluxo completo com UV
&lt;/h3&gt;

&lt;p&gt;Lembra do fluxo fragmentado com cinco ferramentas? Agora com UV:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ✅ Fluxo unificado com UV&lt;/span&gt;

&lt;span class="c"&gt;# 1. Criar projeto (já fixa a versão do Python)&lt;/span&gt;
uv init meu-projeto
&lt;span class="nb"&gt;cd &lt;/span&gt;meu-projeto

&lt;span class="c"&gt;# 2. Instalar dependências (cria venv + lockfile automaticamente)&lt;/span&gt;
uv add fastapi uvicorn[standard]
uv add &lt;span class="nt"&gt;--dev&lt;/span&gt; pytest ruff

&lt;span class="c"&gt;# 3. Executar (ativa venv automaticamente)&lt;/span&gt;
uv run uvicorn main:app &lt;span class="nt"&gt;--reload&lt;/span&gt;

&lt;span class="c"&gt;# 4. Rodar testes&lt;/span&gt;
uv run pytest

&lt;span class="c"&gt;# 5. Linting (sem instalar no projeto)&lt;/span&gt;
uvx ruff check &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Uma ferramenta. Cinco linhas. Zero fricção.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Quando Usar UV
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cenários ideais
&lt;/h3&gt;

&lt;p&gt;O UV é a escolha certa quando você:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Está começando um projeto novo&lt;/strong&gt; em Python — o &lt;code&gt;uv init&lt;/code&gt; dá a estrutura padrão perfeita&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quer velocidade no CI/CD&lt;/strong&gt; — instalar dependências em 1 segundo em vez de 30 faz diferença em pipelines&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trabalha com múltiplas versões do Python&lt;/strong&gt; — &lt;code&gt;uv python install&lt;/code&gt; substitui pyenv sem fricção&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quer reprodutibilidade&lt;/strong&gt; — o &lt;code&gt;uv.lock&lt;/code&gt; garante que todos no time usem exatamente as mesmas versões&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Precisa de algo simples&lt;/strong&gt; — um comando para tudo, sem configuração complexa&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mantém monorepos Python&lt;/strong&gt; — o UV suporta workspaces no estilo Cargo/npm&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quer padronizar o toolchain&lt;/strong&gt; — uma ferramenta para gerenciar tudo, de venv a lockfile&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cenários onde ainda avaliar
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cenário&lt;/th&gt;
&lt;th&gt;Recomendação&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Projetos com Conda/Anaconda&lt;/td&gt;
&lt;td&gt;O UV não substitui Conda para dependências C/Fortran (BLAS, LAPACK, etc.). Se seu projeto depende pesadamente do ecossistema Conda, avalie a migração com cuidado&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Projetos legados muito grandes&lt;/td&gt;
&lt;td&gt;A migração de um projeto grande com Poetry ou Pipenv pode exigir ajustes. Funciona, mas teste em branches separados&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ambientes corporativos restritos&lt;/td&gt;
&lt;td&gt;Se sua empresa bloqueia binários externos, o UV (que é um binário Rust) pode precisar de aprovação de segurança&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 O UV é compatível com o &lt;code&gt;pip&lt;/code&gt;. Você pode usar &lt;code&gt;uv pip install -r requirements.txt&lt;/code&gt; como substituto direto do pip — mesma interface, velocidade de Rust. Essa é a forma mais segura de experimentar sem mudar nada no projeto.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Python vs C# em Aplicações Complexas
&lt;/h2&gt;

&lt;p&gt;Essa é a pergunta que motivou metade deste artigo: &lt;strong&gt;quando a aplicação precisa ser modular, com padrões arquiteturais como Clean Architecture, CQRS, Hexagonal — vale a pena usar Python em vez de C#? Existe ganho real?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A resposta honesta é: &lt;strong&gt;depende do que você chama de “ganho”&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Comparativo completo
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimensão&lt;/th&gt;
&lt;th&gt;Python&lt;/th&gt;
&lt;th&gt;C#&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tipagem&lt;/td&gt;
&lt;td&gt;Dinâmica (com hints opcionais via mypy, Pydantic)&lt;/td&gt;
&lt;td&gt;Estática forte (compilador garante)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compilação&lt;/td&gt;
&lt;td&gt;Interpretado — erros aparecem em runtime&lt;/td&gt;
&lt;td&gt;Compilado — erros aparecem antes do deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Injeção de Dependência&lt;/td&gt;
&lt;td&gt;Terceiros: dependency-injector, python-inject, manual&lt;/td&gt;
&lt;td&gt;Nativa no framework (IServiceCollection)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interfaces / Contratos&lt;/td&gt;
&lt;td&gt;ABC (Abstract Base Class) — não é enforcement forte&lt;/td&gt;
&lt;td&gt;interface — contrato real, verificado pelo compilador&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Padrões Arquiteturais&lt;/td&gt;
&lt;td&gt;Possível, mas exige disciplina manual. Sem framework opinativo para Clean Architecture&lt;/td&gt;
&lt;td&gt;Ecossistema maduro: MediatR (CQRS), templates de Clean Architecture, Minimal APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ORM&lt;/td&gt;
&lt;td&gt;SQLAlchemy, Django ORM, Tortoise&lt;/td&gt;
&lt;td&gt;Entity Framework Core — superior em migrações, Fluent API e integração&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testes&lt;/td&gt;
&lt;td&gt;pytest (excelente, maduro)&lt;/td&gt;
&lt;td&gt;xUnit, NUnit, MSTest (todos maduros)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;GIL limita paralelismo real (melhorando com 3.13+), I/O-bound é bom com asyncio&lt;/td&gt;
&lt;td&gt;Paralelismo real, Tasks, async/await maduro, alta performance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tooling / IDE&lt;/td&gt;
&lt;td&gt;PyCharm, VS Code (bom, mas refactoring limitado pela tipagem dinâmica)&lt;/td&gt;
&lt;td&gt;Visual Studio, Rider, VS Code (refactoring robusto com tipagem estática)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ecossistema Enterprise&lt;/td&gt;
&lt;td&gt;Azure SDK, AWS SDK — funcional, mas menos integrado&lt;/td&gt;
&lt;td&gt;Azure nativo, ferramentas Microsoft integradas, ecossistema enterprise completo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Modularidade&lt;/td&gt;
&lt;td&gt;Módulos e pacotes Python são flexíveis, mas não há enforcement de boundaries&lt;/td&gt;
&lt;td&gt;Assemblies, namespaces, projetos separados com referências explícitas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documentação / API&lt;/td&gt;
&lt;td&gt;Swagger via FastAPI (automático), Sphinx&lt;/td&gt;
&lt;td&gt;Swagger nativo, XML docs, source generators&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Python para aplicações arquiteturais: é possível, mas&amp;amp;mldr;
&lt;/h3&gt;

&lt;p&gt;Sim, você &lt;strong&gt;pode&lt;/strong&gt; implementar Clean Architecture, Hexagonal, CQRS e qualquer padrão em Python. Frameworks como &lt;strong&gt;Django&lt;/strong&gt; (opinativo, MTV) e &lt;strong&gt;FastAPI&lt;/strong&gt; (moderno, async, Pydantic) oferecem bases sólidas. Existem bibliotecas para injeção de dependência, validação de tipos e separação de camadas.&lt;/p&gt;

&lt;p&gt;Porém, há diferenças fundamentais na &lt;strong&gt;experiência de desenvolvimento&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Python: Injeção de dependência com dependency-injector
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dependency_injector&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;providers&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dependency_injector.wiring&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Provide&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inject&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeclarativeContainer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;user_repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Singleton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;UserRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;connection_string&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connection_string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user_repository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@inject&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UserService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Provide&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_service&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Lincoln&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// C#: Injeção de dependência nativa no .NET&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IUserRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UserRepository&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IUserService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// No controller ou endpoint, o framework injeta automaticamente&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IUserService&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Lincoln"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A diferença não é apenas de sintaxe — é de &lt;strong&gt;filosofia&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Em &lt;strong&gt;C#&lt;/strong&gt;, a DI é parte do framework. Você não precisa escolher uma biblioteca, configurar wiring, ou decorar métodos. Funciona “out of the box”&lt;/li&gt;
&lt;li&gt;Em &lt;strong&gt;Python&lt;/strong&gt;, a DI é uma biblioteca terceira. Funciona, mas adiciona complexidade, exige configuração e não tem suporte nativo do framework
Essa diferença se repete em quase todos os padrões:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Padrão&lt;/th&gt;
&lt;th&gt;Em C#&lt;/th&gt;
&lt;th&gt;Em Python&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Interfaces&lt;/td&gt;
&lt;td&gt;interface IService — contrato forte, verificado em compilação&lt;/td&gt;
&lt;td&gt;class IService(ABC) — convenção, não enforcement real&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;Padrão maduro com EF Core, genéricos tipados&lt;/td&gt;
&lt;td&gt;Implementável, mas sem tipo-segurança forte&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CQRS&lt;/td&gt;
&lt;td&gt;MediatR (biblioteca consagrada), separação clara&lt;/td&gt;
&lt;td&gt;Implementação manual, sem biblioteca dominante&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mediator&lt;/td&gt;
&lt;td&gt;MediatR&lt;/td&gt;
&lt;td&gt;Sem equivalente consolidado&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Options Pattern&lt;/td&gt;
&lt;td&gt;IOptions nativo&lt;/td&gt;
&lt;td&gt;Configuração manual ou Pydantic Settings&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Onde Python vence — sem discussão
&lt;/h3&gt;

&lt;p&gt;Há domínios onde Python simplesmente &lt;strong&gt;não tem concorrente real&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IA e Machine Learning&lt;/strong&gt;: TensorFlow, PyTorch, Hugging Face — o ecossistema inteiro é Python-first&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ciência de dados&lt;/strong&gt;: Pandas, NumPy, Jupyter — C# nem aparece na conversa&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prototipagem rápida&lt;/strong&gt;: de zero a uma API funcional em minutos com FastAPI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scripts e automação&lt;/strong&gt;: substituir Bash, processar arquivos, interagir com APIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DevOps&lt;/strong&gt;: Ansible, scripts de infraestrutura, ferramentas CLI&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Onde C# vence — quando complexidade importa
&lt;/h3&gt;

&lt;p&gt;Para aplicações que exigem &lt;strong&gt;manutenção a longo prazo por equipes grandes&lt;/strong&gt;, com &lt;strong&gt;padrões arquiteturais rigorosos&lt;/strong&gt;, C# (e, de forma mais ampla, o .NET) oferece vantagens reais:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Refactoring seguro&lt;/strong&gt;: tipagem estática forte permite renomear, extrair e mover código com confiança&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compilação como guardião&lt;/strong&gt;: erros de tipo, interfaces não implementadas e dependências ausentes são detectados antes de executar qualquer código&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DI e middleware nativos&lt;/strong&gt;: o pipeline do ASP.NET Core é maduro, testável e extensível&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boundaries forçados&lt;/strong&gt;: assemblies e projetos separados criam fronteiras arquiteturais reais, não apenas convenções&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ecossistema enterprise&lt;/strong&gt;: integração nativa com Azure, SQL Server, Identity, e todo o ecossistema Microsoft&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  O veredicto
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;Python e C# não são concorrentes — são complementares.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cenário&lt;/th&gt;
&lt;th&gt;Linguagem recomendada&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IA, ML, Deep Learning&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ciência de dados, análise&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scripts, automação, DevOps&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prototipagem rápida de APIs&lt;/td&gt;
&lt;td&gt;Python (FastAPI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aplicações enterprise com padrões rigorosos&lt;/td&gt;
&lt;td&gt;C# (.NET)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clean Architecture, Hexagonal, CQRS&lt;/td&gt;
&lt;td&gt;C# (ecossistema mais maduro)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aplicações com equipes grandes e manutenção longa&lt;/td&gt;
&lt;td&gt;C# (tipagem estática = segurança)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;APIs de alta performance com I/O intensivo&lt;/td&gt;
&lt;td&gt;C# (async/await maduro, Kestrel)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsserviços com orquestração complexa&lt;/td&gt;
&lt;td&gt;Ambos (depende do ecossistema da empresa)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A pergunta certa não é “Python ou C#?” — é &lt;strong&gt;“qual problema estou resolvendo?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Se o problema é IA, dados ou prototipagem: &lt;strong&gt;Python, sem pensar duas vezes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Se o problema é uma aplicação modular, com camadas, padrões e equipe que precisa de refactoring seguro em dois anos: &lt;strong&gt;C# vai te dar menos dor de cabeça&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;E se o problema exige os dois? Use os dois. Microsserviço de IA em Python, orquestração e API principal em C# — isso não é heresia, é pragmatismo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Considerações Importantes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  UV Ainda É Pré-1.0
&lt;/h3&gt;

&lt;p&gt;O UV está em versão &lt;strong&gt;0.6.x&lt;/strong&gt; (março 2026). Apesar de estável para uso em produção e adotado por milhares de projetos, a versão pré-1.0 significa que &lt;strong&gt;breaking changes podem acontecer&lt;/strong&gt; entre releases menores. A Astral mitiga isso com changelogs detalhados e migração assistida, mas é um ponto a considerar antes de adotar em ambientes corporativos conservadores. Acompanhe os releases no GitHub para saber quando a 1.0 chegar.&lt;/p&gt;

&lt;h3&gt;
  
  
  O &lt;code&gt;pyproject.toml&lt;/code&gt; Como Fonte de Verdade
&lt;/h3&gt;

&lt;p&gt;O UV adota o &lt;code&gt;pyproject.toml&lt;/code&gt; seguindo as PEPs 517, 518 e 621 — os padrões oficiais do Python para metadados de projeto. Isso significa que seu projeto UV &lt;strong&gt;não fica preso ao UV&lt;/strong&gt;: qualquer ferramenta compatível com essas PEPs (incluindo pip e poetry) consegue ler e usar seu &lt;code&gt;pyproject.toml&lt;/code&gt;. Se um dia você precisar migrar, o caminho de saída está garantido.&lt;/p&gt;

&lt;h3&gt;
  
  
  UV e Docker
&lt;/h3&gt;

&lt;p&gt;Para projetos que rodam em containers, o UV brilha especialmente na etapa de build. Um Dockerfile otimizado com UV:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;

&lt;span class="c"&gt;# Instalar UV como binário estático (sem dependências)&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; pyproject.toml uv.lock ./&lt;/span&gt;

&lt;span class="c"&gt;# Instalar dependências (camada separada para cache)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;uv &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="nt"&gt;--frozen&lt;/span&gt; &lt;span class="nt"&gt;--no-dev&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src/ ./src/&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["uv", "run", "uvicorn", "meu_api.main:app", "--host", "0.0.0.0"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O ganho em &lt;strong&gt;tempo de build&lt;/strong&gt; é brutal — especialmente em pipelines de CI/CD onde cada segundo de execução tem custo real.&lt;/p&gt;

&lt;h3&gt;
  
  
  UV vs Cargo vs dotnet CLI
&lt;/h3&gt;

&lt;p&gt;Se você vem de Rust ou .NET, vai perceber que o UV segue a mesma filosofia de CLI unificada:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspecto&lt;/th&gt;
&lt;th&gt;UV (Python)&lt;/th&gt;
&lt;th&gt;Cargo (Rust)&lt;/th&gt;
&lt;th&gt;dotnet CLI (C#)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Criar projeto&lt;/td&gt;
&lt;td&gt;uv init&lt;/td&gt;
&lt;td&gt;cargo new&lt;/td&gt;
&lt;td&gt;dotnet new&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adicionar dependência&lt;/td&gt;
&lt;td&gt;uv add&lt;/td&gt;
&lt;td&gt;cargo add&lt;/td&gt;
&lt;td&gt;dotnet add package&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Executar&lt;/td&gt;
&lt;td&gt;uv run&lt;/td&gt;
&lt;td&gt;cargo run&lt;/td&gt;
&lt;td&gt;dotnet run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lockfile&lt;/td&gt;
&lt;td&gt;uv.lock&lt;/td&gt;
&lt;td&gt;Cargo.lock&lt;/td&gt;
&lt;td&gt;packages.lock.json&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build&lt;/td&gt;
&lt;td&gt;— (interpretado)&lt;/td&gt;
&lt;td&gt;cargo build&lt;/td&gt;
&lt;td&gt;dotnet build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testes&lt;/td&gt;
&lt;td&gt;uv run pytest&lt;/td&gt;
&lt;td&gt;cargo test&lt;/td&gt;
&lt;td&gt;dotnet test&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;O UV é, para o Python, o que o &lt;code&gt;cargo&lt;/code&gt; é para o Rust: &lt;strong&gt;a ferramenta que a comunidade esperou por décadas&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sobre a Adoção e Comunidade
&lt;/h3&gt;

&lt;p&gt;Números que mostram a tração do UV:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;+40.000 estrelas&lt;/strong&gt; no GitHub (março 2026)&lt;/li&gt;
&lt;li&gt;Adotado pelo &lt;strong&gt;FastAPI&lt;/strong&gt; como gerenciador de pacotes recomendado na documentação oficial&lt;/li&gt;
&lt;li&gt;Integrado ao &lt;strong&gt;GitHub Actions&lt;/strong&gt; como opção oficial para setup de Python&lt;/li&gt;
&lt;li&gt;Suportado oficialmente no &lt;strong&gt;VS Code&lt;/strong&gt; e &lt;strong&gt;PyCharm&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Releases semanais com changelog detalhado e semver estrito&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Quando Migrar e Quando Esperar
&lt;/h3&gt;

&lt;p&gt;Se você está em um projeto com &lt;strong&gt;pip puro&lt;/strong&gt; e sente a dor da lentidão e da falta de lockfile: &lt;strong&gt;migre agora&lt;/strong&gt;. O custo é baixo — &lt;code&gt;uv pip install -r requirements.txt&lt;/code&gt; funciona como drop-in replacement do pip.&lt;/p&gt;

&lt;p&gt;Se você está com &lt;strong&gt;Poetry&lt;/strong&gt; e o fluxo funciona sem dor: &lt;strong&gt;avalie com calma&lt;/strong&gt;. A migração vale a pena, mas não é urgente se o projeto já está estável.&lt;/p&gt;

&lt;p&gt;Se a empresa exige &lt;strong&gt;ferramentas auditadas e versionadas (1.0+)&lt;/strong&gt;: espere a versão 1.0 do UV. Mas acompanhe de perto — ela está próxima.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;O &lt;strong&gt;UV&lt;/strong&gt; é, sem exagero, o maior avanço no tooling Python dos últimos anos. Ele resolve um problema que a comunidade arrastava há mais de uma década — a fragmentação absurda de ferramentas para fazer algo que deveria ser simples: &lt;strong&gt;instalar pacotes, criar um ambiente e executar código&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Com o UV, você tem &lt;strong&gt;uma ferramenta&lt;/strong&gt; que substitui &lt;code&gt;pip&lt;/code&gt;, &lt;code&gt;virtualenv&lt;/code&gt;, &lt;code&gt;pyenv&lt;/code&gt;, &lt;code&gt;pip-tools&lt;/code&gt;, &lt;code&gt;pipx&lt;/code&gt; e parte do &lt;code&gt;Poetry&lt;/code&gt;. Escrita em Rust, com velocidade absurda e aderência aos padrões PEP. Se você está começando um projeto Python novo, não há motivo para não usar UV.&lt;/p&gt;

&lt;p&gt;Quanto ao comparativo com C#: Python é uma linguagem extraordinária para prototipagem, IA, dados e automação. Mas quando a conversa é sobre &lt;strong&gt;aplicações enterprise complexas com padrões arquiteturais rigorosos e manutenção a longo prazo&lt;/strong&gt;, C# (e o .NET) oferece vantagens estruturais que são difíceis de ignorar — tipagem estática forte, DI nativa, compilação como guardião, e tooling de refactoring superior.&lt;/p&gt;

&lt;p&gt;A maturidade de uma escolha técnica está em reconhecer: &lt;strong&gt;não existe linguagem perfeita para tudo&lt;/strong&gt;. Existe a linguagem certa para o problema certo. E ter UV no arsenal torna Python significativamente mais viável para projetos sérios — porque a parte que sempre foi ruim (o gerenciamento de pacotes) finalmente ficou excelente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Referências
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;UV — Documentação Oficial&lt;/li&gt;
&lt;li&gt;Astral — Empresa por trás do UV e Ruff&lt;/li&gt;
&lt;li&gt;Python.org — Site oficial do Python&lt;/li&gt;
&lt;li&gt;Python Software Foundation (PSF)&lt;/li&gt;
&lt;li&gt;PEP 20 — The Zen of Python&lt;/li&gt;
&lt;li&gt;Ruff — Linter Python escrito em Rust&lt;/li&gt;
&lt;li&gt;TIOBE Index&lt;/li&gt;
&lt;li&gt;Makefile: Automatizando tarefas para Python, Hugo e Docker — artigo relacionado neste blog&lt;/li&gt;
&lt;li&gt;Padrões GoF: Código à Nuvem, Monólito ao Microserviço — artigo relacionado neste blog
📬&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;👉 &lt;strong&gt;Artigo completo com todos os exemplos de código:&lt;/strong&gt; &lt;a href="https://zocate.li/posts/2026/uv-python-gerenciador-pacotes-comparativo-csharp/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog" rel="noopener noreferrer"&gt;pip É Lento, Poetry Complexo: UV Chegou Resolvendo o Python&lt;/a&gt;&lt;/p&gt;

</description>
      <category>portuguese</category>
      <category>python</category>
      <category>uv</category>
      <category>pip</category>
    </item>
    <item>
      <title>Zero JavaScript: CRUD Completo com Blazor WASM e Radzen</title>
      <dc:creator>Lincoln Zocateli</dc:creator>
      <pubDate>Wed, 15 Apr 2026 02:52:07 +0000</pubDate>
      <link>https://dev.to/lzocate-li/zero-javascript-crud-completo-com-blazor-wasm-e-radzen-1f12</link>
      <guid>https://dev.to/lzocate-li/zero-javascript-crud-completo-com-blazor-wasm-e-radzen-1f12</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;No artigo anterior desta série, analisei se &lt;strong&gt;Blazor WebAssembly&lt;/strong&gt; está pronto para produção corporativa comparando-o com Angular. A conclusão foi nuançada — Blazor WASM é viável para cenários corporativos internos, mas tem trade-offs que precisam ser avaliados caso a caso. Hoje vou provar na prática construindo um &lt;strong&gt;CRUD completo de produtos&lt;/strong&gt; com DataGrid paginado, formulários com validação, dialogs, notificações e inline editing — &lt;strong&gt;tudo em C#, sem escrever uma linha de JavaScript&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;O componente de UI que escolhi é o Radzen Blazor, uma biblioteca open source (licença MIT) com mais de 70 componentes gratuitos. A razão principal: Radzen entrega uma experiência visual madura para cenários CRUD corporativos, com DataGrid, formulários, validação, dialogs e notificações prontos para uso. O JavaScript que roda internamente (&lt;code&gt;Radzen.Blazor.js&lt;/code&gt;) é da própria biblioteca — o desenvolvedor nunca toca em JS diretamente.&lt;/p&gt;

&lt;p&gt;O que vou construir neste tutorial:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API REST&lt;/strong&gt; com Minimal API para Produtos e Categorias (10 endpoints)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blazor WASM Standalone&lt;/strong&gt; consumindo a API via &lt;code&gt;HttpClient&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DataGrid paginado&lt;/strong&gt; com busca, ordering e ações por linha&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Formulário em dialog&lt;/strong&gt; reutilizável para criação e edição&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inline editing&lt;/strong&gt; para Categorias (pattern alternativo ao dialog)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exclusão com confirmação&lt;/strong&gt; e notificações visuais
Todo o código está no repositório blog-zocateli-sample no GitHub. A ideia é que você clone, rode e forme sua própria opinião. Este artigo é o &lt;strong&gt;2º da série “Frontend Moderno”&lt;/strong&gt; — meu objetivo é demonstrar que o ecossistema de componentes Blazor já suporta cenários reais de produção, com uma experiência de desenvolvimento familiar para quem vem do .NET.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ℹ️ Informação:&lt;/strong&gt; Radzen Blazor é open source (MIT) e inclui 70+ componentes free. O &lt;code&gt;Radzen.Blazor.js&lt;/code&gt; é JavaScript interno da biblioteca — o desenvolvedor nunca escreve JavaScript diretamente. A versão usada neste tutorial é a &lt;strong&gt;10.2.0&lt;/strong&gt; com &lt;strong&gt;.NET 10&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Pré-requisitos
&lt;/h2&gt;

&lt;p&gt;Para acompanhar este tutorial, você vai precisar de:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;.NET 10 SDK&lt;/strong&gt; (10.0.201 ou superior) — download oficial&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IDE&lt;/strong&gt;: VS Code com extensão C# Dev Kit, ou Visual Studio 2022 17.14+&lt;/li&gt;
&lt;li&gt;Conhecimento básico de &lt;strong&gt;C#&lt;/strong&gt; e &lt;strong&gt;REST APIs&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Terminal (PowerShell, bash ou zsh)
Clone o repositório com todo o código pronto:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/lzocateli/blog-zocateli-sample.git
&lt;span class="nb"&gt;cd &lt;/span&gt;blog-zocateli-sample
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verifique se o SDK está instalado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output esperado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10.0.201
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Dica:&lt;/strong&gt; Se você usa o VS Code com Dev Containers, o &lt;code&gt;.devcontainer/&lt;/code&gt; do repositório já tem o .NET 10 SDK configurado. Basta abrir o projeto no container e tudo estará pronto.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Criando o Projeto Blazor WASM Standalone
&lt;/h2&gt;

&lt;p&gt;O template &lt;code&gt;blazorwasm&lt;/code&gt; do .NET cria uma aplicação Blazor WebAssembly Standalone — uma SPA que roda inteiramente no browser via WebAssembly, sem servidor ASP.NET Core hospedando. Diferente do modelo Hosted (que inclui um projeto Server), o Standalone é uma SPA pura que consome APIs externas via HTTP, exatamente como uma aplicação Angular ou React.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scaffolding do projeto
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new blazorwasm &lt;span class="nt"&gt;--name&lt;/span&gt; BlogSamples.BlazorWasm &lt;span class="nt"&gt;--output&lt;/span&gt; frontend/blazor-wasm &lt;span class="nt"&gt;--framework&lt;/span&gt; net10.0
dotnet sln add frontend/blazor-wasm
dotnet add frontend/blazor-wasm package Radzen.Blazor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O primeiro comando cria o projeto, o segundo adiciona à solution e o terceiro instala o Radzen Blazor — a biblioteca de componentes UI. O &lt;code&gt;.csproj&lt;/code&gt; resultante fica enxuto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.NET.Sdk.BlazorWebAssembly"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net10.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Nullable&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/Nullable&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ImplicitUsings&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/ImplicitUsings&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.AspNetCore.Components.WebAssembly"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"10.0.5"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.AspNetCore.Components.WebAssembly.DevServer"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"10.0.5"&lt;/span&gt; &lt;span class="na"&gt;PrivateAssets=&lt;/span&gt;&lt;span class="s"&gt;"all"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Radzen.Blazor"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"10.2.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configurando Program.cs
&lt;/h3&gt;

&lt;p&gt;O &lt;code&gt;Program.cs&lt;/code&gt; é o entry point da SPA. Aqui configuro o &lt;code&gt;HttpClient&lt;/code&gt; com a URL base da API (via &lt;code&gt;appsettings.json&lt;/code&gt;), registro os services HTTP tipados e adiciono os componentes Radzen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Components.Web&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Components.WebAssembly.Hosting&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;BlogSamples.BlazorWasm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;BlogSamples.BlazorWasm.Services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Radzen&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebAssemblyHostBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RootComponents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"#app"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RootComponents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HeadOutlet&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"head::after"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Configurar HttpClient com URL base da API&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;apiBaseUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"ApiBaseUrl"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"http://localhost:5101"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiBaseUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Services HTTP tipados&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProdutoApiService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CategoriaApiService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Radzen Components (DialogService, NotificationService, etc.)&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRadzenComponents&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A chamada &lt;code&gt;AddRadzenComponents()&lt;/code&gt; registra automaticamente &lt;code&gt;DialogService&lt;/code&gt;, &lt;code&gt;NotificationService&lt;/code&gt;, &lt;code&gt;TooltipService&lt;/code&gt; e &lt;code&gt;ContextMenuService&lt;/code&gt; no container de DI. Sem ela, os dialogs e notificações não funcionam.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layout com Radzen
&lt;/h3&gt;

&lt;p&gt;O &lt;code&gt;MainLayout.razor&lt;/code&gt; define a estrutura visual da aplicação — header com toggle de sidebar, navegação lateral com &lt;code&gt;RadzenPanelMenu&lt;/code&gt;, área de conteúdo e footer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@inherits LayoutComponentBase

&amp;lt;RadzenLayout&amp;gt;
    &amp;lt;RadzenHeader&amp;gt;
        &amp;lt;RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center"
                     Gap="0.5rem" class="rz-p-2"&amp;gt;
            &amp;lt;RadzenSidebarToggle Click="@(() =&amp;gt; sidebarExpanded = !sidebarExpanded)" /&amp;gt;
            &amp;lt;RadzenText Text="Blog Samples — Blazor WASM" TextStyle="TextStyle.H5"
                        class="rz-m-0" /&amp;gt;
        &amp;lt;/RadzenStack&amp;gt;
    &amp;lt;/RadzenHeader&amp;gt;

    &amp;lt;RadzenSidebar @bind-Expanded="@sidebarExpanded"&amp;gt;
        &amp;lt;RadzenPanelMenu&amp;gt;
            &amp;lt;RadzenPanelMenuItem Text="Dashboard" Icon="dashboard" Path="/" /&amp;gt;
            &amp;lt;RadzenPanelMenuItem Text="Produtos" Icon="inventory_2" Path="/produtos" /&amp;gt;
            &amp;lt;RadzenPanelMenuItem Text="Categorias" Icon="category" Path="/categorias" /&amp;gt;
        &amp;lt;/RadzenPanelMenu&amp;gt;
    &amp;lt;/RadzenSidebar&amp;gt;

    &amp;lt;RadzenBody&amp;gt;
        &amp;lt;div class="rz-p-4"&amp;gt;
            @Body
        &amp;lt;/div&amp;gt;
    &amp;lt;/RadzenBody&amp;gt;

    &amp;lt;RadzenFooter&amp;gt;
        &amp;lt;RadzenText Text="© 2026 Blog Samples — zocate.li" TextStyle="TextStyle.Caption"
                    class="rz-p-2" /&amp;gt;
    &amp;lt;/RadzenFooter&amp;gt;
&amp;lt;/RadzenLayout&amp;gt;

&amp;lt;RadzenComponents /&amp;gt;

@code {
    bool sidebarExpanded = true;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O componente &lt;code&gt;&amp;lt;RadzenComponents /&amp;gt;&lt;/code&gt; no final é obrigatório — ele renderiza os containers para dialogs, notificações e tooltips. Sem ele, &lt;code&gt;DialogService.OpenAsync()&lt;/code&gt; e &lt;code&gt;NotificationService.Notify()&lt;/code&gt; não exibem nada na tela.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Atenção:&lt;/strong&gt; O &lt;code&gt;&amp;lt;RadzenComponents /&amp;gt;&lt;/code&gt; deve estar &lt;strong&gt;dentro do layout&lt;/strong&gt;, não no &lt;code&gt;App.razor&lt;/code&gt;. Colocá-lo fora do layout pode causar problemas de renderização com dialogs e notificações.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Para que o tema visual funcione, o &lt;code&gt;App.razor&lt;/code&gt; precisa incluir &lt;code&gt;&amp;lt;RadzenTheme Theme="material" /&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;RadzenTheme Theme="material" /&amp;gt;
&amp;lt;Router AppAssembly="typeof(Program).Assembly"&amp;gt;
    &amp;lt;!-- ... --&amp;gt;
&amp;lt;/Router&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O tema material do Radzen inclui toda a estilização necessária — cores, tipografia, espaçamento, ícones Material Design. Não é necessário importar Bootstrap ou qualquer outro framework CSS.&lt;/p&gt;

&lt;h2&gt;
  
  
  A API REST — Domínio Produtos
&lt;/h2&gt;

&lt;p&gt;Para o Blazor WASM consumir dados, criei um domínio &lt;strong&gt;Produtos&lt;/strong&gt; com Minimal API no projeto principal. São 10 endpoints organizados em dois grupos:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Verbo&lt;/th&gt;
&lt;th&gt;Rota&lt;/th&gt;
&lt;th&gt;Descrição&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/api/produtos?pagina=1&amp;amp;tamanhoPagina=20&amp;amp;filtro=&lt;/td&gt;
&lt;td&gt;Listar com paginação e filtro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/api/produtos/{id}&lt;/td&gt;
&lt;td&gt;Obter por ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/api/produtos&lt;/td&gt;
&lt;td&gt;Criar produto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;/api/produtos/{id}&lt;/td&gt;
&lt;td&gt;Atualizar produto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;/api/produtos/{id}&lt;/td&gt;
&lt;td&gt;Remover produto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/api/categorias&lt;/td&gt;
&lt;td&gt;Listar todas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/api/categorias/{id}&lt;/td&gt;
&lt;td&gt;Obter por ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/api/categorias&lt;/td&gt;
&lt;td&gt;Criar categoria&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;/api/categorias/{id}&lt;/td&gt;
&lt;td&gt;Atualizar categoria&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;/api/categorias/{id}&lt;/td&gt;
&lt;td&gt;Remover categoria&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;O &lt;code&gt;ProdutoEndpoints.cs&lt;/code&gt; usa &lt;code&gt;MapGroup&lt;/code&gt; para organizar as rotas e &lt;code&gt;ProducesResponseType&lt;/code&gt; para documentar no Swagger:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProdutoEndpoints&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;MapProdutoEndpoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IEndpointRouteBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;produtos&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/produtos"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Produtos"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;produtos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;IProdutoService&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;pagina&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;tamanhoPagina&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;filtro&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ListarProdutosAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pagina&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tamanhoPagina&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filtro&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ListarProdutos"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Produces&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PagedResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProdutoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="n"&gt;produtos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CriarProdutoRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IProdutoService&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;produto&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CriarProdutoAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreatedAtRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ObterProduto"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;produto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;produto&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CriarProduto"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Produces&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProdutoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="m"&gt;201&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProducesValidationProblem&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// ... PUT, DELETE, e endpoints de Categorias seguem o mesmo pattern&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Os DTOs de request usam &lt;strong&gt;DataAnnotations&lt;/strong&gt; para validação server-side, garantindo que a API valide os dados mesmo que o client-side seja bypassed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;CriarProdutoRequest&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ErrorMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Nome é obrigatório"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;StringLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MinimumLength&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Nome&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Descricao&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrorMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Preço deve ser maior que zero"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Preco&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxValue&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;QuantidadeEstoque&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrorMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Selecione uma categoria"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;CategoriaId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;Ativo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A implementação do service usa &lt;code&gt;ConcurrentDictionary&lt;/code&gt; como storage in-memory (decisão de design para manter o tutorial focado no Blazor WASM, sem dependência de banco de dados). O seed inicial inclui 8 categorias e mais de 50 produtos distribuídos entre elas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuração CORS
&lt;/h3&gt;

&lt;p&gt;Como o Blazor WASM Standalone roda em uma porta diferente da API (5200 vs 5101), é obrigatório configurar CORS no &lt;code&gt;Program.cs&lt;/code&gt; da API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddCors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BlazorWasm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithOrigins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:5200"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"https://localhost:7200"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowAnyHeader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowAnyMethod&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// No pipeline:&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseCors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BlazorWasm"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Dica:&lt;/strong&gt; Configurar CORS é &lt;strong&gt;obrigatório&lt;/strong&gt; para Blazor WASM Standalone. Sem configuração explícita, o browser bloqueará as requisições cross-origin. Em produção, substitua os origins por domínios reais.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Para testar a API isoladamente, rode &lt;code&gt;dotnet run --project src/BlogSamples&lt;/code&gt; e acesse &lt;code&gt;http://localhost:5101/docs&lt;/code&gt; — o Swagger mostra todos os endpoints de Produtos e Categorias.&lt;/p&gt;

&lt;p&gt;O diagrama abaixo mostra a arquitetura completa — o Blazor WASM no browser se comunica com a API REST via &lt;code&gt;HttpClient&lt;/code&gt; (JSON) atravessando a barreira de CORS:&lt;/p&gt;

&lt;h2&gt;
  
  
  Services HTTP — Consumindo a API
&lt;/h2&gt;

&lt;p&gt;O pattern que uso para consumir a API é &lt;strong&gt;service tipado com &lt;code&gt;HttpClient&lt;/code&gt; injetado via primary constructor&lt;/strong&gt;. Cada service encapsula as chamadas HTTP para um domínio específico, usando os métodos de extensão do &lt;code&gt;System.Net.Http.Json&lt;/code&gt; — &lt;code&gt;GetFromJsonAsync&lt;/code&gt;, &lt;code&gt;PostAsJsonAsync&lt;/code&gt; e &lt;code&gt;PutAsJsonAsync&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net.Http.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;BlogSamples.BlazorWasm.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;BlogSamples.BlazorWasm.Services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProdutoApiService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PagedResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProdutoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ListarAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;pagina&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;tamanhoPagina&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;filtro&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"api/produtos?pagina=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pagina&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;tamanhoPagina=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tamanhoPagina&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filtro&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;$"&amp;amp;filtro=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EscapeDataString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filtro&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PagedResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProdutoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PagedResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProdutoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProdutoDto&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ObterPorIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProdutoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;$"api/produtos/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProdutoDto&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CriarAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CriarProdutoRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsJsonAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/produtos"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProdutoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProdutoDto&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;AtualizarAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AtualizarProdutoRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PutAsJsonAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"api/produtos/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProdutoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;RemoverAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DeleteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"api/produtos/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alguns pontos importantes sobre este pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Uri.EscapeDataString&lt;/code&gt;&lt;/strong&gt; no filtro previne injeção de parâmetros na query string. Nunca concatene strings diretamente em URLs sem encoding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;EnsureSuccessStatusCode()&lt;/code&gt;&lt;/strong&gt; lança &lt;code&gt;HttpRequestException&lt;/code&gt; se a API retornar erro (4xx, 5xx). No componente Blazor, capturo essa exceção para exibir notificação de erro ao usuário.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Primary constructor&lt;/strong&gt; (&lt;code&gt;HttpClient http&lt;/code&gt;) evita o boilerplate de campo + construtor. O &lt;code&gt;HttpClient&lt;/code&gt; é resolvido pelo container de DI com a &lt;code&gt;BaseAddress&lt;/code&gt; configurada no &lt;code&gt;Program.cs&lt;/code&gt;.
O &lt;code&gt;CategoriaApiService&lt;/code&gt; segue exatamente o mesmo pattern, com métodos &lt;code&gt;ListarAsync&lt;/code&gt;, &lt;code&gt;CriarAsync&lt;/code&gt;, &lt;code&gt;AtualizarAsync&lt;/code&gt; e &lt;code&gt;RemoverAsync&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No Angular, &lt;code&gt;HttpClient&lt;/code&gt; com interceptors e operadores RxJS oferece ergonomia similar. Em Blazor, a experiência é equivalente — &lt;code&gt;DelegatingHandler&lt;/code&gt; serve como interceptor para autenticação, logging ou retry. A diferença principal é que Blazor usa &lt;code&gt;async/await&lt;/code&gt; nativo do C# em vez de &lt;code&gt;Observable&lt;/code&gt; do RxJS.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ℹ️ Informação:&lt;/strong&gt; Os models do Blazor WASM são &lt;strong&gt;classes&lt;/strong&gt; (não records). Radzen Blazor usa two-way binding (&lt;code&gt;@bind-Value&lt;/code&gt;) que requer setters mutáveis. Records com &lt;code&gt;init&lt;/code&gt; não funcionam para edição em formulários Radzen.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  DataGrid de Produtos com Radzen
&lt;/h2&gt;

&lt;p&gt;O &lt;code&gt;RadzenDataGrid&lt;/code&gt; é o componente central deste tutorial. Ele suporta paginação server-side, sorting, filtering, templates customizados por coluna e integração direta com o pattern de &lt;code&gt;LoadData&lt;/code&gt; — um callback que o grid chama toda vez que precisa de dados novos (ao mudar de página, ordenar ou filtrar).&lt;/p&gt;

&lt;p&gt;Aqui está o &lt;code&gt;Produtos.razor&lt;/code&gt; completo — vou explicar cada parte:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page "/produtos"

&amp;lt;PageTitle&amp;gt;Produtos — Blog Samples&amp;lt;/PageTitle&amp;gt;

&amp;lt;RadzenText TextStyle="TextStyle.H3" class="rz-mb-4"&amp;gt;Produtos&amp;lt;/RadzenText&amp;gt;

&amp;lt;RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center"
             Gap="1rem" class="rz-mb-4"&amp;gt;
    &amp;lt;RadzenTextBox Placeholder="Buscar produtos..." @bind-Value="filtro"
                   Change="@OnFiltroChanged" Style="width: 300px;" /&amp;gt;
    &amp;lt;RadzenButton Text="Novo Produto" Icon="add" ButtonStyle="ButtonStyle.Primary"
                  Click="@(() =&amp;gt; AbrirFormulario(null))" /&amp;gt;
&amp;lt;/RadzenStack&amp;gt;

&amp;lt;RadzenDataGrid @ref="grid" TItem="ProdutoDto"
                Data="@produtos" Count="@totalRegistros"
                LoadData="@CarregarDados"
                AllowPaging="true" PageSize="20"
                AllowSorting="true"
                PagerHorizontalAlign="HorizontalAlign.Center"
                IsLoading="@isLoading"
                Style="width: 100%;"&amp;gt;
    &amp;lt;Columns&amp;gt;
        &amp;lt;RadzenDataGridColumn TItem="ProdutoDto" Property="Id" Title="ID"
                              Width="70px" TextAlign="TextAlign.Center" Sortable="false" /&amp;gt;

        &amp;lt;RadzenDataGridColumn TItem="ProdutoDto" Property="Nome" Title="Nome"
                              MinWidth="200px" /&amp;gt;

        &amp;lt;RadzenDataGridColumn TItem="ProdutoDto" Property="CategoriaNome" Title="Categoria"
                              Width="150px" /&amp;gt;

        &amp;lt;RadzenDataGridColumn TItem="ProdutoDto" Property="Preco" Title="Preço"
                              Width="130px" TextAlign="TextAlign.End"
                              FormatString="{0:C2}" /&amp;gt;

        &amp;lt;RadzenDataGridColumn TItem="ProdutoDto" Property="QuantidadeEstoque" Title="Estoque"
                              Width="100px" TextAlign="TextAlign.Center" /&amp;gt;

        &amp;lt;RadzenDataGridColumn TItem="ProdutoDto" Property="Ativo" Title="Status"
                              Width="100px" TextAlign="TextAlign.Center" Sortable="false"&amp;gt;
            &amp;lt;Template Context="produto"&amp;gt;
                &amp;lt;RadzenBadge BadgeStyle="@(produto.Ativo ? BadgeStyle.Success : BadgeStyle.Light)"
                             Text="@(produto.Ativo ? "Ativo" : "Inativo")" /&amp;gt;
            &amp;lt;/Template&amp;gt;
        &amp;lt;/RadzenDataGridColumn&amp;gt;

        &amp;lt;RadzenDataGridColumn TItem="ProdutoDto" Title="Ações" Width="140px"
                              TextAlign="TextAlign.Center" Sortable="false"&amp;gt;
            &amp;lt;Template Context="produto"&amp;gt;
                &amp;lt;RadzenButton Icon="edit" ButtonStyle="ButtonStyle.Light"
                              Size="ButtonSize.Small"
                              Click="@(() =&amp;gt; AbrirFormulario(produto.Id))"
                              class="rz-mr-1" /&amp;gt;
                &amp;lt;RadzenButton Icon="delete" ButtonStyle="ButtonStyle.Danger"
                              Size="ButtonSize.Small"
                              Click="@(() =&amp;gt; ConfirmarExclusao(produto))" /&amp;gt;
            &amp;lt;/Template&amp;gt;
        &amp;lt;/RadzenDataGridColumn&amp;gt;
    &amp;lt;/Columns&amp;gt;
&amp;lt;/RadzenDataGrid&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vou detalhar os pontos-chave:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;LoadData="@CarregarDados"&lt;/code&gt;&lt;/strong&gt; — o grid chama este callback ao inicializar, ao mudar de página e ao ordenar. Recebe &lt;code&gt;LoadDataArgs&lt;/code&gt; com &lt;code&gt;Skip&lt;/code&gt;, &lt;code&gt;Top&lt;/code&gt; e informações de ordering. Essa é a chave para paginação server-side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Data&lt;/code&gt; + &lt;code&gt;Count&lt;/code&gt;&lt;/strong&gt; — &lt;code&gt;Data&lt;/code&gt; recebe a página atual de itens; &lt;code&gt;Count&lt;/code&gt; informa o total de registros. O grid calcula o número de páginas automaticamente.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;FormatString="{0:C2}"&lt;/code&gt;&lt;/strong&gt; — formata o preço como moeda. O Blazor WASM usa a cultura configurada no browser, então em pt-BR exibe “R$ 1.299,00”.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Template&lt;/code&gt;&lt;/strong&gt; — colunas customizadas. Uso &lt;code&gt;RadzenBadge&lt;/code&gt; para exibir o status como badge verde/cinza e botões de ação (Editar, Excluir) com ícones Material Design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;IsLoading&lt;/code&gt;&lt;/strong&gt; — exibe um spinner enquanto a API está sendo chamada. Melhora significativamente a UX em conexões lentas.
O &lt;code&gt;@code&lt;/code&gt; block contém a lógica:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;RadzenDataGrid&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProdutoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;!;&lt;/span&gt;
    &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProdutoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;produtos&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;totalRegistros&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;filtro&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Inject&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;ProdutoApiService&lt;/span&gt; &lt;span class="n"&gt;ProdutoService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;!;&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Inject&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;DialogService&lt;/span&gt; &lt;span class="n"&gt;DialogService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;!;&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Inject&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;NotificationService&lt;/span&gt; &lt;span class="n"&gt;NotificationService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;!;&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;CarregarDados&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LoadDataArgs&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pagina&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Skip&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Top&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tamanhoPagina&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Top&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ProdutoService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ListarAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pagina&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tamanhoPagina&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filtro&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;produtos&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Itens&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;totalRegistros&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalRegistros&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnFiltroChanged&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O método &lt;code&gt;CarregarDados&lt;/code&gt; converte &lt;code&gt;Skip/Top&lt;/code&gt; (pattern do Radzen) para &lt;code&gt;pagina/tamanhoPagina&lt;/code&gt; (pattern da minha API). Quando o usuário digita no campo de busca, &lt;code&gt;OnFiltroChanged&lt;/code&gt; volta para a primeira página com &lt;code&gt;grid.FirstPage(true)&lt;/code&gt; — o &lt;code&gt;true&lt;/code&gt; força o reload que chama &lt;code&gt;CarregarDados&lt;/code&gt; novamente com o filtro atualizado.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Atenção:&lt;/strong&gt; O evento &lt;code&gt;LoadData&lt;/code&gt; do &lt;code&gt;RadzenDataGrid&lt;/code&gt; é chamado toda vez que o grid precisa de dados — paging, sorting, filtering. Não confunda com &lt;code&gt;Data&lt;/code&gt; binding direto, que é para dados client-side. Se você usar &lt;code&gt;Data&lt;/code&gt; com uma lista completa e &lt;code&gt;AllowPaging&lt;/code&gt;, a paginação será client-side (todos os dados carregados de uma vez). Com &lt;code&gt;LoadData&lt;/code&gt; + &lt;code&gt;Count&lt;/code&gt;, a paginação é server-side (apenas a página atual é carregada).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Formulário de Criação e Edição
&lt;/h2&gt;

&lt;p&gt;O &lt;code&gt;ProdutoForm.razor&lt;/code&gt; é um componente reutilizável que serve tanto para &lt;strong&gt;criar&lt;/strong&gt; quanto para &lt;strong&gt;editar&lt;/strong&gt; produtos. A distinção é feita pelo &lt;code&gt;Parameter&lt;/code&gt; &lt;code&gt;ProdutoId&lt;/code&gt;: se for &lt;code&gt;null&lt;/code&gt;, é criação; se tiver valor, é edição. O formulário é aberto em um &lt;strong&gt;dialog modal&lt;/strong&gt; via &lt;code&gt;DialogService.OpenAsync&amp;lt;ProdutoForm&amp;gt;()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;RadzenTemplateForm TItem="CriarProdutoRequest" Data="@model" Submit="@OnSubmit"&amp;gt;
    &amp;lt;RadzenStack Gap="1rem"&amp;gt;
        &amp;lt;RadzenFormField Text="Nome" Variant="Variant.Outlined"&amp;gt;
            &amp;lt;ChildContent&amp;gt;
                &amp;lt;RadzenTextBox @bind-Value="model.Nome" MaxLength="200" /&amp;gt;
            &amp;lt;/ChildContent&amp;gt;
            &amp;lt;Helper&amp;gt;
                &amp;lt;RadzenRequiredValidator Component="Nome"
                                         Text="Nome é obrigatório" /&amp;gt;
            &amp;lt;/Helper&amp;gt;
        &amp;lt;/RadzenFormField&amp;gt;

        &amp;lt;RadzenFormField Text="Descrição" Variant="Variant.Outlined"&amp;gt;
            &amp;lt;ChildContent&amp;gt;
                &amp;lt;RadzenTextArea @bind-Value="model.Descricao" Rows="3" /&amp;gt;
            &amp;lt;/ChildContent&amp;gt;
        &amp;lt;/RadzenFormField&amp;gt;

        &amp;lt;RadzenRow Gap="1rem"&amp;gt;
            &amp;lt;RadzenColumn Size="6"&amp;gt;
                &amp;lt;RadzenFormField Text="Preço (R$)" Variant="Variant.Outlined"
                                Style="width: 100%;"&amp;gt;
                    &amp;lt;ChildContent&amp;gt;
                        &amp;lt;RadzenNumeric TValue="decimal" @bind-Value="model.Preco"
                                       Min="0.01m" Format="N2" /&amp;gt;
                    &amp;lt;/ChildContent&amp;gt;
                    &amp;lt;Helper&amp;gt;
                        &amp;lt;RadzenNumericRangeValidator Component="Preco" Min="0.01"
                                                     Text="Preço deve ser maior que zero" /&amp;gt;
                    &amp;lt;/Helper&amp;gt;
                &amp;lt;/RadzenFormField&amp;gt;
            &amp;lt;/RadzenColumn&amp;gt;
            &amp;lt;RadzenColumn Size="6"&amp;gt;
                &amp;lt;RadzenFormField Text="Estoque" Variant="Variant.Outlined"
                                Style="width: 100%;"&amp;gt;
                    &amp;lt;ChildContent&amp;gt;
                        &amp;lt;RadzenNumeric TValue="int" @bind-Value="model.QuantidadeEstoque"
                                       Min="0" /&amp;gt;
                    &amp;lt;/ChildContent&amp;gt;
                &amp;lt;/RadzenFormField&amp;gt;
            &amp;lt;/RadzenColumn&amp;gt;
        &amp;lt;/RadzenRow&amp;gt;

        &amp;lt;RadzenFormField Text="Categoria" Variant="Variant.Outlined"&amp;gt;
            &amp;lt;ChildContent&amp;gt;
                &amp;lt;RadzenDropDown TValue="int" @bind-Value="model.CategoriaId"
                                Data="@categorias" TextProperty="Nome"
                                ValueProperty="Id"
                                Placeholder="Selecione uma categoria..."
                                AllowFiltering="true" /&amp;gt;
            &amp;lt;/ChildContent&amp;gt;
            &amp;lt;Helper&amp;gt;
                &amp;lt;RadzenRequiredValidator Component="CategoriaId"
                                         Text="Selecione uma categoria"
                                         DefaultValue="0" /&amp;gt;
            &amp;lt;/Helper&amp;gt;
        &amp;lt;/RadzenFormField&amp;gt;

        &amp;lt;RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center"
                     Gap="0.5rem"&amp;gt;
            &amp;lt;RadzenSwitch @bind-Value="model.Ativo" /&amp;gt;
            &amp;lt;RadzenText Text="@(model.Ativo ? "Ativo" : "Inativo")" /&amp;gt;
        &amp;lt;/RadzenStack&amp;gt;

        &amp;lt;RadzenStack Orientation="Orientation.Horizontal"
                     JustifyContent="JustifyContent.End" Gap="0.5rem"&amp;gt;
            &amp;lt;RadzenButton Text="Cancelar" ButtonStyle="ButtonStyle.Light"
                          Click="@(() =&amp;gt; DialogService.Close(false))"
                          ButtonType="ButtonType.Button" /&amp;gt;
            &amp;lt;RadzenButton Text="Salvar" ButtonStyle="ButtonStyle.Primary"
                          ButtonType="ButtonType.Submit" Icon="save" /&amp;gt;
        &amp;lt;/RadzenStack&amp;gt;
    &amp;lt;/RadzenStack&amp;gt;
&amp;lt;/RadzenTemplateForm&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vou destacar os pontos mais importantes do formulário:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;RadzenTemplateForm&amp;lt;CriarProdutoRequest&amp;gt;&lt;/code&gt;&lt;/strong&gt; — encapsula todo o formulário com validação. O &lt;code&gt;Submit&lt;/code&gt; event só é disparado se todos os validators passarem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;RadzenRequiredValidator&lt;/code&gt;&lt;/strong&gt; e &lt;strong&gt;&lt;code&gt;RadzenNumericRangeValidator&lt;/code&gt;&lt;/strong&gt; — validação client-side com feedback visual automático (bordas vermelhas, mensagem de erro abaixo do campo). A validação server-side via DataAnnotations na API é a segunda camada de proteção.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;RadzenDropDown&amp;lt;int&amp;gt;&lt;/code&gt;&lt;/strong&gt; para categorias — &lt;code&gt;Data&lt;/code&gt; recebe a lista, &lt;code&gt;TextProperty&lt;/code&gt; e &lt;code&gt;ValueProperty&lt;/code&gt; mapeiam as propriedades do DTO. &lt;code&gt;AllowFiltering="true"&lt;/code&gt; habilita busca inline no dropdown (útil quando há muitas categorias).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;RadzenSwitch&lt;/code&gt;&lt;/strong&gt; com label dinâmico — exibe “Ativo” ou “Inativo” conforme o estado do toggle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Botão Cancelar&lt;/strong&gt; com &lt;code&gt;ButtonType="ButtonType.Button"&lt;/code&gt; — sem isso, o click do Cancelar dispara o submit do formulário.
O &lt;code&gt;@code&lt;/code&gt; block contém a lógica de inicialização e submit:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Parameter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ProdutoId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Inject&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;ProdutoApiService&lt;/span&gt; &lt;span class="n"&gt;ProdutoService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;!;&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Inject&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;CategoriaApiService&lt;/span&gt; &lt;span class="n"&gt;CategoriaService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;!;&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Inject&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;DialogService&lt;/span&gt; &lt;span class="n"&gt;DialogService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;!;&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Inject&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;NotificationService&lt;/span&gt; &lt;span class="n"&gt;NotificationService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;!;&lt;/span&gt;

    &lt;span class="n"&gt;CriarProdutoRequest&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CategoriaDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;categorias&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnInitializedAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;categorias&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;CategoriaService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ListarAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProdutoId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;produto&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ProdutoService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ObterPorIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProdutoId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;produto&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;CriarProdutoRequest&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;Nome&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;produto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;Descricao&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;produto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Descricao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;Preco&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;produto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Preco&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;QuantidadeEstoque&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;produto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QuantidadeEstoque&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;CategoriaId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;produto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CategoriaId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;Ativo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;produto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ativo&lt;/span&gt;
                &lt;span class="p"&gt;};&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnSubmit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProdutoId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AtualizarProdutoRequest&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;Nome&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;Descricao&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Descricao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;Preco&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Preco&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;QuantidadeEstoque&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QuantidadeEstoque&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;CategoriaId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CategoriaId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;Ativo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ativo&lt;/span&gt;
                &lt;span class="p"&gt;};&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ProdutoService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AtualizarAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProdutoId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ProdutoService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CriarAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;DialogService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpRequestException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;NotificationService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;NotificationMessage&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Severity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NotificationSeverity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Summary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Erro ao salvar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Detail&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Não foi possível salvar o produto."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Duration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;6000&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O fluxo completo é: usuário clica “Novo Produto” → &lt;code&gt;DialogService.OpenAsync&amp;lt;ProdutoForm&amp;gt;()&lt;/code&gt; abre o dialog → o form carrega categorias e, se for edição, carrega o produto → usuário preenche/edita → validators verificam → &lt;code&gt;OnSubmit&lt;/code&gt; chama a API → &lt;code&gt;DialogService.Close(true)&lt;/code&gt; fecha o dialog → o grid detecta &lt;code&gt;resultado is true&lt;/code&gt; e chama &lt;code&gt;grid.Reload()&lt;/code&gt; → dados atualizados.&lt;/p&gt;

&lt;p&gt;Para abrir o dialog a partir de &lt;code&gt;Produtos.razor&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;AbrirFormulario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;produtoId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;titulo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;produtoId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasValue&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s"&gt;"Editar Produto"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Novo Produto"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;DialogService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProdutoForm&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;titulo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"ProdutoId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;produtoId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;DialogOptions&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Width&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"600px"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;CloseDialogOnOverlayClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;CloseDialogOnEsc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Reload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O dicionário &lt;code&gt;{ "ProdutoId", produtoId }&lt;/code&gt; passa o parâmetro para o componente &lt;code&gt;ProdutoForm&lt;/code&gt;. O &lt;code&gt;DialogOptions&lt;/code&gt; controla a largura do dialog e se ele fecha ao clicar fora ou pressionar Escape.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;📝 Exemplo:&lt;/strong&gt; Fluxo de edição — clique no botão “Editar” de um produto → dialog abre com título “Editar Produto” → campos preenchidos com dados atuais → altere o preço → clique Salvar → notificação verde “Produto atualizado” → grid recarrega com preço novo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Exclusão com Confirmação e Notificações
&lt;/h2&gt;

&lt;p&gt;Toda operação destrutiva deve ter uma etapa de confirmação. O &lt;code&gt;DialogService.Confirm()&lt;/code&gt; do Radzen renderiza um dialog nativo com botões customizáveis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ConfirmarExclusao&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProdutoDto&lt;/span&gt; &lt;span class="n"&gt;produto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;confirmado&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;DialogService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Confirm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;$"Deseja excluir o produto \"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;produto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nome&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\"?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Confirmar Exclusão"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ConfirmOptions&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;OkButtonText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Excluir"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;CancelButtonText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Cancelar"&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;confirmado&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ProdutoService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RemoverAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;produto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;NotificationService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;NotificationMessage&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Severity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NotificationSeverity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Summary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Produto excluído"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Detail&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"\"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;produto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nome&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\" foi removido com sucesso."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Duration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;4000&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Reload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpRequestException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;NotificationService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;NotificationMessage&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Severity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NotificationSeverity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Summary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Erro ao excluir"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Detail&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Não foi possível excluir o produto. Tente novamente."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Duration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;6000&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As notificações do Radzen (&lt;code&gt;NotificationService.Notify&lt;/code&gt;) aparecem como toasts no canto da tela. Uso &lt;code&gt;NotificationSeverity.Success&lt;/code&gt; com duração de 4 segundos para operações bem-sucedidas e &lt;code&gt;NotificationSeverity.Error&lt;/code&gt; com 6 segundos para erros — mais tempo para o usuário ler a mensagem. O pattern de &lt;code&gt;try/catch&lt;/code&gt; com &lt;code&gt;HttpRequestException&lt;/code&gt; é simples mas eficaz: se a API retornar erro (ex: produto não encontrado), o catch exibe feedback imediato ao usuário.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gerenciamento de Categorias — Inline Editing
&lt;/h2&gt;

&lt;p&gt;Para demonstrar um pattern alternativo ao dialog, a tela de Categorias usa &lt;strong&gt;inline editing&lt;/strong&gt; — o usuário edita diretamente na grid, sem abrir modal. Esse approach funciona bem para entidades simples com poucos campos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page "/categorias"

&amp;lt;RadzenDataGrid @ref="grid" TItem="CategoriaDto"
                Data="@categorias" AllowSorting="true"
                EditMode="DataGridEditMode.Single"
                RowUpdate="@OnRowUpdate" RowCreate="@OnRowCreate"
                Style="width: 100%;"&amp;gt;
    &amp;lt;Columns&amp;gt;
        &amp;lt;RadzenDataGridColumn TItem="CategoriaDto" Property="Id" Title="ID"
                              Width="70px" TextAlign="TextAlign.Center" /&amp;gt;

        &amp;lt;RadzenDataGridColumn TItem="CategoriaDto" Property="Nome" Title="Nome"
                              MinWidth="200px"&amp;gt;
            &amp;lt;EditTemplate Context="cat"&amp;gt;
                &amp;lt;RadzenTextBox @bind-Value="cat.Nome" Style="width: 100%;" /&amp;gt;
            &amp;lt;/EditTemplate&amp;gt;
        &amp;lt;/RadzenDataGridColumn&amp;gt;

        &amp;lt;RadzenDataGridColumn TItem="CategoriaDto" Property="Descricao"
                              Title="Descrição" MinWidth="250px"&amp;gt;
            &amp;lt;EditTemplate Context="cat"&amp;gt;
                &amp;lt;RadzenTextBox @bind-Value="cat.Descricao" Style="width: 100%;" /&amp;gt;
            &amp;lt;/EditTemplate&amp;gt;
        &amp;lt;/RadzenDataGridColumn&amp;gt;

        &amp;lt;RadzenDataGridColumn TItem="CategoriaDto" Property="Ativo" Title="Status"
                              Width="100px" TextAlign="TextAlign.Center"&amp;gt;
            &amp;lt;Template Context="cat"&amp;gt;
                &amp;lt;RadzenBadge BadgeStyle="@(cat.Ativo ? BadgeStyle.Success : BadgeStyle.Light)"
                             Text="@(cat.Ativo ? "Ativo" : "Inativo")" /&amp;gt;
            &amp;lt;/Template&amp;gt;
            &amp;lt;EditTemplate Context="cat"&amp;gt;
                &amp;lt;RadzenSwitch @bind-Value="cat.Ativo" /&amp;gt;
            &amp;lt;/EditTemplate&amp;gt;
        &amp;lt;/RadzenDataGridColumn&amp;gt;

        &amp;lt;RadzenDataGridColumn TItem="CategoriaDto" Title="Ações" Width="180px"
                              TextAlign="TextAlign.Center" Sortable="false"&amp;gt;
            &amp;lt;Template Context="cat"&amp;gt;
                &amp;lt;RadzenButton Icon="edit" ButtonStyle="ButtonStyle.Light"
                              Size="ButtonSize.Small"
                              Click="@(() =&amp;gt; EditarLinha(cat))" class="rz-mr-1" /&amp;gt;
                &amp;lt;RadzenButton Icon="delete" ButtonStyle="ButtonStyle.Danger"
                              Size="ButtonSize.Small"
                              Click="@(() =&amp;gt; ConfirmarExclusao(cat))" /&amp;gt;
            &amp;lt;/Template&amp;gt;
            &amp;lt;EditTemplate Context="cat"&amp;gt;
                &amp;lt;RadzenButton Icon="check" ButtonStyle="ButtonStyle.Success"
                              Size="ButtonSize.Small"
                              Click="@(() =&amp;gt; SalvarLinha(cat))" class="rz-mr-1" /&amp;gt;
                &amp;lt;RadzenButton Icon="close" ButtonStyle="ButtonStyle.Light"
                              Size="ButtonSize.Small"
                              Click="@CancelarEdicao" /&amp;gt;
            &amp;lt;/EditTemplate&amp;gt;
        &amp;lt;/RadzenDataGridColumn&amp;gt;
    &amp;lt;/Columns&amp;gt;
&amp;lt;/RadzenDataGrid&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A diferença principal está no &lt;code&gt;EditMode="DataGridEditMode.Single"&lt;/code&gt;: quando o usuário clica em “Editar”, a linha entra em modo de edição — os campos de texto e o switch aparecem no lugar dos valores estáticos. Os botões mudam de “Editar/Excluir” para “Salvar/Cancelar”.&lt;/p&gt;

&lt;p&gt;A lógica de edição inline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;RadzenDataGrid&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CategoriaDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;!;&lt;/span&gt;
    &lt;span class="n"&gt;IList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CategoriaDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;categorias&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="n"&gt;CategoriaDto&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;categoriaEditando&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;InserirNova&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;nova&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;CategoriaDto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Ativo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="n"&gt;categorias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nova&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;categoriaEditando&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nova&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EditRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nova&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;EditarLinha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CategoriaDto&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;categoriaEditando&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EditRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;SalvarLinha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CategoriaDto&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UpdateRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnRowUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CategoriaDto&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AtualizarCategoriaRequest&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Nome&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Descricao&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Descricao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Ativo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ativo&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;CategoriaService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AtualizarAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;categoriaEditando&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;CarregarDados&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnRowCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CategoriaDto&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;CriarCategoriaRequest&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Nome&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Descricao&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Descricao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Ativo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ativo&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;CategoriaService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CriarAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;categoriaEditando&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;CarregarDados&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O fluxo para “Nova Categoria” é: inserir um objeto vazio na posição 0 da lista → &lt;code&gt;grid.EditRow()&lt;/code&gt; coloca essa linha em modo de edição → usuário preenche → &lt;code&gt;SalvarLinha&lt;/code&gt; chama &lt;code&gt;grid.UpdateRow()&lt;/code&gt; → &lt;code&gt;OnRowCreate&lt;/code&gt; é disparado (porque &lt;code&gt;Id == 0&lt;/code&gt;) → API chamada → dados recarregados.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quando usar inline editing vs dialog:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cenário&lt;/th&gt;
&lt;th&gt;Inline Editing&lt;/th&gt;
&lt;th&gt;Dialog&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Entidades simples (2-4 campos)&lt;/td&gt;
&lt;td&gt;✅ Ideal&lt;/td&gt;
&lt;td&gt;Overkill&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entidades complexas (5+ campos, dropdowns)&lt;/td&gt;
&lt;td&gt;Confuso&lt;/td&gt;
&lt;td&gt;✅ Ideal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Campos com validação elaborada&lt;/td&gt;
&lt;td&gt;Limitado&lt;/td&gt;
&lt;td&gt;✅ Mais espaço visual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edição rápida e frequente&lt;/td&gt;
&lt;td&gt;✅ Menos cliques&lt;/td&gt;
&lt;td&gt;Mais cliques&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UX mobile&lt;/td&gt;
&lt;td&gt;⚠️ Pode ficar apertado&lt;/td&gt;
&lt;td&gt;✅ Melhor em telas pequenas&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Dicas e Boas Práticas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Centralize chamadas HTTP em services tipados&lt;/strong&gt; — nunca injete &lt;code&gt;HttpClient&lt;/code&gt; diretamente no componente &lt;code&gt;.razor&lt;/code&gt;. Isso viola separação de responsabilidades e dificulta testes. Services como &lt;code&gt;ProdutoApiService&lt;/code&gt; encapsulam URLs, serialização e tratamento de erros em um único lugar reutilizável.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use DataAnnotations + validators Radzen para validação dupla&lt;/strong&gt; — &lt;code&gt;RadzenRequiredValidator&lt;/code&gt; e &lt;code&gt;RadzenNumericRangeValidator&lt;/code&gt; validam no client; DataAnnotations no DTO de request validam no server. Se alguém bypassar o UI e chamar a API diretamente, a validação server-side ainda protege os dados.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;RadzenNotification para TODA ação&lt;/strong&gt; — feedback visual consistente em sucesso (“Produto criado”) e erro (“Não foi possível salvar”). Defina duração diferente: 4 segundos para sucesso, 6+ para erros (o usuário precisa de mais tempo para ler a mensagem de erro).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;LoadData event para paginação server-side&lt;/strong&gt; — nunca carregue todos os dados no client com uma lista completa. Com &lt;code&gt;LoadData&lt;/code&gt;, apenas a página atual é transferida pela rede. Para um catálogo com 10.000 produtos, carregar tudo na memória do browser é inviável; com paginação server-side, cada request traz apenas 20 registros.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Componentize forms reutilizáveis&lt;/strong&gt; — &lt;code&gt;ProdutoForm&lt;/code&gt; serve para criação E edição. A distinção é um &lt;code&gt;Parameter&lt;/code&gt; nullable (&lt;code&gt;int? ProdutoId&lt;/code&gt;). Esse pattern elimina duplicação de código e garante consistência entre os fluxos de criação e edição.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configure &lt;code&gt;appsettings.json&lt;/code&gt; para URL da API&lt;/strong&gt; — nunca faça hardcode de URLs. O &lt;code&gt;wwwroot/appsettings.json&lt;/code&gt; no Blazor WASM funciona como arquivo de configuração por ambiente. Em produção, substitua por &lt;code&gt;appsettings.Production.json&lt;/code&gt; com a URL real da API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Implemente loading state no DataGrid&lt;/strong&gt; — a propriedade &lt;code&gt;IsLoading&lt;/code&gt; do &lt;code&gt;RadzenDataGrid&lt;/code&gt; exibe um spinner enquanto a API é chamada. Sem feedback visual, o usuário não sabe se a ação foi disparada ou se a aplicação travou. Defina &lt;code&gt;isLoading = true&lt;/code&gt; antes da chamada e &lt;code&gt;false&lt;/code&gt; depois.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configure AOT + Trimming para produção&lt;/strong&gt; — o bundle size do Blazor WASM é uma preocupação real. Para produção, habilite AOT compilation e trimming no &lt;code&gt;.csproj&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;RunAOTCompilation&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/RunAOTCompilation&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PublishTrimmed&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/PublishTrimmed&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AOT compila o IL para WebAssembly nativo (execução mais rápida), e trimming remove código não utilizado (bundle menor). O trade-off é tempo de build significativamente maior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Neste tutorial, construí um CRUD completo com &lt;strong&gt;Blazor WebAssembly .NET 10&lt;/strong&gt; e &lt;strong&gt;Radzen Blazor&lt;/strong&gt;: DataGrid com paginação server-side e busca, formulários com validação client-side e server-side, dialogs modais para criação e edição, inline editing para entidades simples, exclusão com confirmação e notificações visuais para feedback — tudo em C#, sem escrever uma linha de JavaScript pelo desenvolvedor.&lt;/p&gt;

&lt;p&gt;Radzen Blazor entrega componentes visuais maduros para o cenário CRUD corporativo. O &lt;code&gt;RadzenDataGrid&lt;/code&gt; com &lt;code&gt;LoadData&lt;/code&gt; resolve paginação server-side de forma elegante. Os validators (&lt;code&gt;RadzenRequiredValidator&lt;/code&gt;, &lt;code&gt;RadzenNumericRangeValidator&lt;/code&gt;) funcionam bem para validação básica. O &lt;code&gt;DialogService&lt;/code&gt; e &lt;code&gt;NotificationService&lt;/code&gt; cobrem o fluxo completo de interação com o usuário.&lt;/p&gt;

&lt;p&gt;Mas é importante ser transparente sobre as limitações que encontrei durante o desenvolvimento:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Radzen.Blazor.js&lt;/code&gt; é necessário&lt;/strong&gt; — apesar do slogan “zero JavaScript”, a biblioteca depende de JS interno para renderizar componentes complexos. Não é JavaScript escrito pelo desenvolvedor, mas é JS rodando no browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two-way binding requer classes, não records&lt;/strong&gt; — &lt;code&gt;@bind-Value&lt;/code&gt; do Radzen precisa de setters mutáveis. Records com &lt;code&gt;init&lt;/code&gt; não funcionam para formulários de edição. Isso força o uso de classes para DTOs no Blazor WASM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;O bundle size continua significativo&lt;/strong&gt; — o runtime do .NET + Radzen + a aplicação resultam em um download inicial considerável. AOT e trimming ajudam, mas não resolvem completamente.
Como analisei no artigo anterior, Blazor WASM é viável para contextos corporativos — e este tutorial demonstra que o ecossistema de componentes suporta cenários reais. A decisão entre Blazor WASM e frameworks JavaScript como Angular ou React depende do perfil da equipe, requisitos de SEO/SSR e tolerância ao bundle size, conforme discuti no comparativo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clone o repositório, rode a API e o Blazor WASM, e forme sua própria opinião. O código completo está em &lt;code&gt;frontend/blazor-wasm/&lt;/code&gt; e &lt;code&gt;src/BlogSamples/Produtos/&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leia Também
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Seu Próximo Frontend Será C#? A Verdade Sobre Blazor WASM — comparativo teórico Blazor vs Angular (1º artigo da série “Frontend Moderno”)&lt;/li&gt;
&lt;li&gt;Design de APIs REST: Verbos HTTP e Parameter Binding — a estrutura de Minimal API que o Blazor WASM consome neste tutorial&lt;/li&gt;
&lt;li&gt;Log Sem Contexto é Ruído: Logging Estruturado no .NET 8 — ecossistema .NET maduro com logging, métricas e tracing&lt;/li&gt;
&lt;li&gt;Autenticação e Autorização: JWT, OAuth2 e OpenID Connect — próximo passo: proteger a SPA Blazor com Entra ID&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Referências
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Blazor WebAssembly — Documentação oficial — Microsoft Learn, referência completa sobre Blazor&lt;/li&gt;
&lt;li&gt;ASP.NET Core 10.0 Release Notes — novidades do .NET 10 para web&lt;/li&gt;
&lt;li&gt;Radzen Blazor Components — Get Started — setup e guia inicial da biblioteca&lt;/li&gt;
&lt;li&gt;Radzen Blazor — GitHub — código fonte (MIT) com exemplos e issues&lt;/li&gt;
&lt;li&gt;Radzen DataGrid — documentação e demos interativos do componente central&lt;/li&gt;
&lt;li&gt;blog-zocateli-sample — GitHub — repositório com todo o código deste artigo&lt;/li&gt;
&lt;li&gt;WebAssembly — especificação oficial do padrão W3C&lt;/li&gt;
&lt;li&gt;Blazor WASM Standalone Deployment — guia de deploy para produção
📬&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;👉 &lt;strong&gt;Artigo completo com todos os exemplos de código:&lt;/strong&gt; &lt;a href="https://zocate.li/posts/2026/blazor-wasm-crud-radzen-tutorial-dotnet10/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog" rel="noopener noreferrer"&gt;Zero JavaScript: CRUD Completo com Blazor WASM e Radzen&lt;/a&gt;&lt;/p&gt;

</description>
      <category>portuguese</category>
      <category>blazor</category>
      <category>webassembly</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Paginação em APIs REST com C# e EF Core: Guia Prático</title>
      <dc:creator>Lincoln Zocateli</dc:creator>
      <pubDate>Wed, 15 Apr 2026 02:45:56 +0000</pubDate>
      <link>https://dev.to/lzocate-li/paginacao-em-apis-rest-com-c-e-ef-core-guia-pratico-5of</link>
      <guid>https://dev.to/lzocate-li/paginacao-em-apis-rest-com-c-e-ef-core-guia-pratico-5of</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;Paginar resultados parece simples: adicione &lt;code&gt;.Skip(offset).Take(pageSize)&lt;/code&gt; e pronto. Mas quando a tabela tem 10 milhões de registros, o usuário está na página 5.000, o banco é Oracle 11g, ou o cliente exige scroll infinito sem duplicatas — essa simplicidade desaparece rapidamente.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Paginação é uma das decisões arquiteturais mais impactantes em APIs REST&lt;/strong&gt;, e a escolha errada pode significar queries de 8 segundos, resultados inconsistentes ou um backend que trava sob carga. Existem pelo menos cinco estratégias distintas, cada uma com trade-offs claros: &lt;strong&gt;Offset&lt;/strong&gt;, &lt;strong&gt;Keyset (Seek)&lt;/strong&gt;, &lt;strong&gt;Cursor de banco&lt;/strong&gt;, &lt;strong&gt;Token opaco&lt;/strong&gt; e &lt;strong&gt;Time-based&lt;/strong&gt;. Cada banco de dados — &lt;strong&gt;SQL Server&lt;/strong&gt;, &lt;strong&gt;Oracle&lt;/strong&gt; e &lt;strong&gt;PostgreSQL&lt;/strong&gt; — implementa essas estratégias com sintaxes e comportamentos ligeiramente diferentes.&lt;/p&gt;

&lt;p&gt;Neste artigo você vai entender em profundidade &lt;strong&gt;quando usar cada estratégia&lt;/strong&gt;, como cada banco a implementa, e como codificar tudo com &lt;strong&gt;EF Core 8.0+&lt;/strong&gt; em C#. Se você quer uma visão mais ampla sobre gargalos de leitura e escrita em banco de dados, leia também o artigo Gargalo em Banco de Dados com C# e EF Core: Mensageria e Paginação.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pré-requisitos:&lt;/strong&gt; C# intermediário, EF Core básico, familiaridade com SQL. Recomenda-se também o artigo sobre programação assíncrona com C#.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📦 Código-fonte:&lt;/strong&gt; A implementação completa deste artigo está no repositório blog-zocateli-sample no GitHub. Clone, explore e adapte ao seu contexto.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Por Que Paginação Errada Destrói a Performance
&lt;/h2&gt;

&lt;p&gt;Antes de ver as estratégias, vale entender o que acontece internamente em cada banco quando você pagina:&lt;/p&gt;

&lt;h3&gt;
  
  
  O Problema do OFFSET Profundo
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- SQL Server: página 5000 com 20 itens por página&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Nome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Valor&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;DataCriacao&lt;/span&gt;
&lt;span class="k"&gt;OFFSET&lt;/span&gt; &lt;span class="mi"&gt;99980&lt;/span&gt; &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;FETCH&lt;/span&gt; &lt;span class="k"&gt;NEXT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;ONLY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O banco não “pula” para a linha 99.981 magicamente. Ele precisa:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ordenar&lt;/strong&gt; (ou usar o índice de ordenação)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Varrer 99.980 linhas&lt;/strong&gt; e descartá-las&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retornar&lt;/strong&gt; as próximas 20
O custo cresce linearmente com o offset. Para &lt;code&gt;OFFSET 0&lt;/code&gt; o custo é quase zero; para &lt;code&gt;OFFSET 1.000.000&lt;/code&gt; o custo pode ser segundos. Isso é o &lt;strong&gt;problema do late pagination&lt;/strong&gt;, e se manifesta em qualquer banco.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Por Que Ordering Estável é Mandatório
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ SEM OrderBy — resultado não determinístico&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pedidos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Skip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;40&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ COM OrderBy — resultado determinístico + índice pode ser usado&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pedidos&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThenBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;// Desempate pelo campo único garante estabilidade&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Skip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;40&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sem &lt;code&gt;OrderBy&lt;/code&gt;, o banco pode retornar os 20 itens em qualquer ordem, e você corre o risco de ver os mesmos registros em páginas diferentes ou pular registros. Isso é especialmente crítico no &lt;strong&gt;Oracle&lt;/strong&gt;, que tem comportamento de ordenação menos previsível que o SQL Server por padrão.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estratégia 1: Offset Pagination (SKIP / TAKE)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Quando Usar
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;UIs com número de páginas visível (página 1, 2, 3&amp;amp;mldr;)&lt;/li&gt;
&lt;li&gt;Relatórios com totais exatos necessários&lt;/li&gt;
&lt;li&gt;Volumes pequenos ou médios (até ~100k registros na tabela)&lt;/li&gt;
&lt;li&gt;Quando o usuário precisa saltar diretamente para qualquer página&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  O SQL por Banco
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- SQL Server (2012+)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DataCriacao&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;
&lt;span class="k"&gt;OFFSET&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt; &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;FETCH&lt;/span&gt; &lt;span class="k"&gt;NEXT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;ONLY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Oracle 12c+&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DataCriacao&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;
&lt;span class="k"&gt;OFFSET&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt; &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;FETCH&lt;/span&gt; &lt;span class="k"&gt;NEXT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;ONLY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Oracle 11g (sem suporte nativo — ROW_NUMBER workaround)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ROWNUM&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rn&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DataCriacao&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ROWNUM&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;rn&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- PostgreSQL&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DataCriacao&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="k"&gt;OFFSET&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 &lt;strong&gt;Dica:&lt;/strong&gt; O provider &lt;code&gt;Oracle.EntityFrameworkCore&lt;/code&gt; detecta automaticamente a versão do Oracle e gera a sintaxe correta (com &lt;code&gt;FETCH FIRST&lt;/code&gt; para 12c+ ou &lt;code&gt;ROWNUM&lt;/code&gt; para 11g). Não precisa escrever SQL raw para isso.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementação com EF Core 8
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Models reutilizáveis para toda a API&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;PaginacaoRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Pagina&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;TamanhoPagina&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;TamanhoSeguro&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TamanhoPagina&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;OffsetSeguro&lt;/span&gt;  &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Pagina&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;TamanhoSeguro&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;PaginaResultado&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;  &lt;span class="n"&gt;Dados&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt;               &lt;span class="n"&gt;PaginaAtual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt;               &lt;span class="n"&gt;TamanhoPagina&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;long&lt;/span&gt;              &lt;span class="n"&gt;TotalRegistros&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt;               &lt;span class="n"&gt;TotalPaginas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt;              &lt;span class="n"&gt;TemProxima&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt;              &lt;span class="n"&gt;TemAnterior&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PedidoOffsetService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PaginaResultado&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ListarAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;PaginacaoRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pedidos&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsNoTracking&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThenBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// EF Core 8: ExecuteCount() separado sem materializar a coleção&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LongCountAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Skip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OffsetSeguro&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TamanhoSeguro&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PedidoDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;totalPaginas&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ceiling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TamanhoSeguro&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PaginaResultado&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="n"&gt;Dados&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="n"&gt;dados&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;PaginaAtual&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pagina&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;TamanhoPagina&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TamanhoSeguro&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;TotalRegistros&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;TotalPaginas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;totalPaginas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;TemProxima&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pagina&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;totalPaginas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;TemAnterior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pagina&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ &lt;strong&gt;Atenção:&lt;/strong&gt; Para tabelas com mais de 500k linhas, o &lt;code&gt;LongCountAsync()&lt;/code&gt; por si só pode ser lento (table scan). Uma estratégia comum é &lt;strong&gt;cachear o total&lt;/strong&gt; por 30–60 segundos, ou usar uma coluna de contagem materializada.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estratégia 2: Keyset Pagination (Seek / WHERE-based)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Quando Usar
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;APIs REST com scroll infinito ou “next page” token&lt;/li&gt;
&lt;li&gt;Tabelas com milhões de registros onde o OFFSET degrada&lt;/li&gt;
&lt;li&gt;Feeds de dados em tempo real onde novos itens são inseridos constantemente&lt;/li&gt;
&lt;li&gt;Exportação assíncrona de grandes volumes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Como Funciona
&lt;/h3&gt;

&lt;p&gt;Em vez de dizer “pule N linhas”, você diz “me dê os registros &lt;strong&gt;após&lt;/strong&gt; este ponto de referência”. O banco usa o índice diretamente para posicionar-se no cursor, sem varrer os registros anteriores.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- SQL Server / Oracle 12c+ / PostgreSQL — keyset com chave composta&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;TOP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DataCriacao&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2025-06-01T12:00:00'&lt;/span&gt;
   &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2025-06-01T12:00:00'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'abc-def-123'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;TOP 21&lt;/code&gt; (ou &lt;code&gt;LIMIT 21&lt;/code&gt;) é intencional: busca-se um registro a mais para saber se há próxima página, sem fazer um &lt;code&gt;COUNT&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Índice Obrigatório para Keyset
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- SQL Server — índice covering para a query de keyset&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX_Pedidos_Keyset&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;INCLUDE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Oracle&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX_Pedidos_Keyset&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- PostgreSQL&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX_Pedidos_Keyset&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;INCLUDE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sem esse índice, o banco recai em um full scan mesmo com a cláusula &lt;code&gt;WHERE&lt;/code&gt; do keyset.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementação com EF Core 8
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Token de cursor — serializado em Base64 para o cliente (opaco)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;KeysetCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;UltimoId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt; &lt;span class="n"&gt;UltimaData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;Convert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToBase64String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SerializeToUtf8Bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;KeysetCursor&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;KeysetCursor&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
                &lt;span class="n"&gt;Convert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromBase64String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;KeysetResultado&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dados&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt;             &lt;span class="n"&gt;TemProximaPagina&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;          &lt;span class="n"&gt;ProximoToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// Token opaco para o cliente&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PedidoKeysetService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;KeysetResultado&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ListarAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;           &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt;               &lt;span class="n"&gt;limite&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;limite&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;KeysetCursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Expressão de keyset — funciona sem cursor (primeira página)&lt;/span&gt;
        &lt;span class="c1"&gt;// e com cursor (páginas seguintes)&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pedidos&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsNoTracking&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
                &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt;
                &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UltimaData&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UltimaData&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
                 &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CompareTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UltimoId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThenBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PedidoDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// Busca limite+1 para detectar se há próxima página&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limite&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;temProxima&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;limite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temProxima&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RemoveAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dados&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;proximoToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temProxima&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ultimo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt;&lt;span class="p"&gt;[^&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="n"&gt;proximoToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;KeysetCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ultimo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ultimo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;KeysetResultado&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;dados&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temProxima&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;proximoToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O token gerado é opaco para o cliente — ele não sabe o que está dentro, apenas passou para a próxima chamada. Isso permite mudar a implementação interna sem quebrar os clientes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estratégia 3: Cursor de Banco de Dados (Server-Side Cursor)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  O Que é um Cursor de Banco e Quando Usar
&lt;/h3&gt;

&lt;p&gt;Um &lt;strong&gt;cursor de banco de dados&lt;/strong&gt; é diferente do keyset cursor. Aqui, o &lt;strong&gt;próprio banco mantém uma posição de leitura aberta&lt;/strong&gt; entre as chamadas. O servidor reserva recursos (memória, locks ou um snapshot) para aquele conjunto de resultados enquanto o cliente vai buscando blocos.&lt;/p&gt;

&lt;p&gt;É a estratégia ideal para:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exportações longas&lt;/strong&gt; onde o cliente consome os dados em blocos ao longo de segundos ou minutos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Processamento ETL&lt;/strong&gt; linha por linha sem carregar tudo na memória&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming de dados&lt;/strong&gt; via WebSocket ou Server-Sent Events&lt;/li&gt;
&lt;li&gt;Situações onde o conjunto de resultados &lt;strong&gt;não pode mudar&lt;/strong&gt; durante a leitura (snapshot consistency)
&amp;gt; ⚠️ &lt;strong&gt;Atenção:&lt;/strong&gt; Cursores de banco consomem recursos do servidor enquanto estão abertos. Em SQL Server e Oracle, cursores mal gerenciados (esquecidos abertos) causam &lt;strong&gt;memory pressure&lt;/strong&gt; e até bloqueios. PostgreSQL lida melhor com cursores em transações, mas ainda exige cuidado.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  PostgreSQL: DECLARE CURSOR (o mais completo)
&lt;/h3&gt;

&lt;p&gt;O PostgreSQL tem o suporte mais rico a cursores server-side. Para usá-los com EF Core, é necessário executar SQL raw dentro de uma transação, pois os cursores do PostgreSQL existem apenas no escopo de uma transação:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PedidoCursorService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;IAsyncEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;StreamAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;blocoPorFetch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;EnumeratorCancellation&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Cursor PG exige transação ativa durante toda a leitura&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BeginTransactionAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// DECLARE abre o cursor no servidor&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteSqlRawAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"DECLARE pedidos_cursor NO SCROLL CURSOR FOR "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
            &lt;span class="s"&gt;"SELECT p.\"Id\", p.\"ClienteId\", p.\"Valor\", p.\"DataCriacao\", p.\"Status\" "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
            &lt;span class="s"&gt;"FROM \"Pedidos\" p ORDER BY p.\"DataCriacao\", p.\"Id\""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// FETCH busca um bloco por vez — nunca tudo na memória&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bloco&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SqlQueryRaw&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
                        &lt;span class="s"&gt;$"FETCH &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;blocoPorFetch&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; FROM pedidos_cursor"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bloco&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// Fim do cursor&lt;/span&gt;

                &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;bloco&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bloco&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;blocoPorFetch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Último bloco parcial&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;finally&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// CLOSE libera recursos no servidor — SEMPRE executar&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteSqlRawAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"CLOSE pedidos_cursor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;None&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CommitAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;None&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SQL Server: FAST_FORWARD Cursor via Raw SQL
&lt;/h3&gt;

&lt;p&gt;O SQL Server suporta cursores T-SQL, mas para APIs REST o padrão mais eficiente é usar &lt;code&gt;IAsyncEnumerable&lt;/code&gt; com &lt;code&gt;AsAsyncEnumerable()&lt;/code&gt; do EF Core ou um cursor via ADO.NET direto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SQL Server: streaming com IAsyncEnumerable do EF Core 8&lt;/span&gt;
&lt;span class="c1"&gt;// Internamente usa DataReader que consome linha por linha&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PedidoStreamService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;IAsyncEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;StreamComEfCoreAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;dataInicio&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;EnumeratorCancellation&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// AsAsyncEnumerable() não materializa a coleção —&lt;/span&gt;
        &lt;span class="c1"&gt;// mantém o DataReader aberto e lê sob demanda&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pedidos&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsNoTracking&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dataInicio&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;dataInicio&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThenBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PedidoDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsAsyncEnumerable&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;WithCancellation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 &lt;strong&gt;Dica:&lt;/strong&gt; &lt;code&gt;AsAsyncEnumerable()&lt;/code&gt; no EF Core é implementado como um &lt;strong&gt;DataReader&lt;/strong&gt; que permanece aberto enquanto o &lt;code&gt;IAsyncEnumerable&lt;/code&gt; está sendo consumido. Internamente, ele funciona como um cursor de banco implicit. A diferença para um cursor SQL explícito é que o DataReader não permite pausar a leitura e retomar mais tarde em uma nova conexão.&lt;/p&gt;

&lt;h3&gt;
  
  
  Oracle: REF CURSOR e SYS_REFCURSOR
&lt;/h3&gt;

&lt;p&gt;No Oracle, o equivalente é o &lt;code&gt;REF CURSOR&lt;/code&gt;, geralmente exposto via stored procedure. Com o provider &lt;code&gt;Oracle.EntityFrameworkCore&lt;/code&gt;, você pode executá-lo assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Oracle: REF CURSOR via stored procedure&lt;/span&gt;
&lt;span class="c1"&gt;// A procedure retorna um SYS_REFCURSOR como parâmetro OUT&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PedidoOracleService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;BuscarViaCursorAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;DateTime&lt;/span&gt; &lt;span class="n"&gt;dataInicio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;quantidade&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;param_dataInicio&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OracleParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"p_data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="n"&gt;OracleDbType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dataInicio&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;param_qtd&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OracleParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"p_qtd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="n"&gt;OracleDbType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Int32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;quantidade&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;param_cursor&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OracleParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"p_cursor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OracleDbType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RefCursor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ParameterDirection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="c1"&gt;// Chama a stored procedure que faz OPEN cursor FOR SELECT ...&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteSqlRawAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"BEGIN PKG_PEDIDOS.BUSCAR_PAGINADO(:p_data, :p_qtd, :p_cursor); END;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;param_dataInicio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param_qtd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param_cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Lê o REF CURSOR retornado pelo Oracle&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;refCursor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OracleRefCursor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;param_cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;refCursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDataReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PedidoDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetGuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDateTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Estratégia 4: Time-based Pagination
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Quando Usar
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Dados que têm &lt;strong&gt;dimensão temporal natural&lt;/strong&gt; (logs, eventos, transações)&lt;/li&gt;
&lt;li&gt;APIs de &lt;strong&gt;auditoria&lt;/strong&gt; ou &lt;strong&gt;histórico&lt;/strong&gt; com filtros por janela de tempo&lt;/li&gt;
&lt;li&gt;Quando o cliente quer dados de uma hora específica (e.g. “pedidos de ontem”)&lt;/li&gt;
&lt;li&gt;Integração com sistemas de streaming (Kafka, Event Sourcing)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Implementação
&lt;/h3&gt;

&lt;p&gt;A paginação time-based é uma forma especializada de keyset, com a coluna de data como cursor primário. A diferença é que o cliente escolhe a janela de tempo explicitamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;TimePaginacaoRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;DateTime&lt;/span&gt;  &lt;span class="n"&gt;DataInicio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DateTime&lt;/span&gt;  &lt;span class="n"&gt;DataFim&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;UltimaDataVista&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Cursor dentro da janela&lt;/span&gt;
    &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;     &lt;span class="n"&gt;UltimoIdVisto&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt;       &lt;span class="n"&gt;Limite&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PedidoTimeService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;KeysetResultado&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ListarPorJanelaAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;TimePaginacaoRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt;    &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;limite&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Limite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pedidos&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsNoTracking&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="c1"&gt;// Janela de tempo fixa — mantém o conjunto estável&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataInicio&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
                        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;  &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFim&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;// Cursor dentro da janela (para paginação sequencial)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
                &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UltimaDataVista&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt;
                &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UltimaDataVista&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UltimaDataVista&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
                 &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CompareTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UltimoIdVisto&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThenBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PedidoDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limite&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;temProxima&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;limite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temProxima&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RemoveAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dados&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;proximoToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temProxima&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ultimo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt;&lt;span class="p"&gt;[^&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="n"&gt;proximoToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;KeysetCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ultimo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ultimo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;KeysetResultado&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;dados&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temProxima&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;proximoToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 &lt;strong&gt;Dica:&lt;/strong&gt; A grande vantagem da paginação time-based sobre keyset puro é que &lt;strong&gt;o cliente pode escolher janelas imutáveis&lt;/strong&gt; (e.g. “todo o dia 2025-01-01”), o que permite &lt;strong&gt;cache agressivo&lt;/strong&gt; dessas janelas no servidor, já que o conteúdo não muda depois que a janela fecha.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estratégia 5: Token Opaco e Link HATEOAS
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Quando Usar
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;APIs públicas onde você não quer expor detalhes de implementação ao cliente&lt;/li&gt;
&lt;li&gt;Quando a estratégia de paginação pode mudar sem quebrar os clientes&lt;/li&gt;
&lt;li&gt;APIs que precisam de &lt;strong&gt;HATEOAS&lt;/strong&gt; (links de próxima/anterior página na resposta)
O token opaco encapsula qualquer estratégia de cursor internamente:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Resposta no padrão HATEOAS com links de navegação&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;PaginacaoHateoasResultado&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dados&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;PaginacaoLinks&lt;/span&gt;   &lt;span class="n"&gt;Links&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;PaginacaoMeta&lt;/span&gt;    &lt;span class="n"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;PaginacaoLinks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;  &lt;span class="n"&gt;Primeiro&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;  &lt;span class="n"&gt;Anterior&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;  &lt;span class="n"&gt;Proximo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;  &lt;span class="n"&gt;Ultimo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;PaginacaoMeta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt;  &lt;span class="n"&gt;Limite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;TotalRegistros&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;TemProxima&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Endpoint que monta a resposta HATEOAS&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/v1/pedidos"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;HttpContext&lt;/span&gt;          &lt;span class="n"&gt;httpCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FromQuery&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;  &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FromQuery&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;      &lt;span class="n"&gt;limite&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;PedidoKeysetService&lt;/span&gt;  &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CancellationToken&lt;/span&gt;    &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ListarAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;baseUrl&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;httpCtx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scheme&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;://&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;httpCtx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/v1/pedidos"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PaginacaoHateoasResultado&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PedidoDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Dados&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dados&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Links&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PaginacaoLinks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;Primeiro&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;?limite=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;limite&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Anterior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Keyset não suporta voltar sem histórico&lt;/span&gt;
            &lt;span class="n"&gt;Proximo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TemProximaPagina&lt;/span&gt;
                           &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;?cursor=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProximoToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;limite=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;limite&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
                           &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Ultimo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PaginacaoMeta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;Limite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="n"&gt;limite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;TotalRegistros&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Keyset não calcula total&lt;/span&gt;
            &lt;span class="n"&gt;TemProxima&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TemProximaPagina&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Comparativo: Qual Estratégia Usar em Cada Situação
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Critério&lt;/th&gt;
&lt;th&gt;Offset&lt;/th&gt;
&lt;th&gt;Keyset / Seek&lt;/th&gt;
&lt;th&gt;Cursor Server-Side&lt;/th&gt;
&lt;th&gt;Time-based&lt;/th&gt;
&lt;th&gt;Token Opaco&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total de registros&lt;/td&gt;
&lt;td&gt;✅ Sim&lt;/td&gt;
&lt;td&gt;❌ Não&lt;/td&gt;
&lt;td&gt;❌ Não&lt;/td&gt;
&lt;td&gt;⚠️ Por janela&lt;/td&gt;
&lt;td&gt;❌ Não&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Salto de página&lt;/td&gt;
&lt;td&gt;✅ Direto&lt;/td&gt;
&lt;td&gt;❌ Apenas sequencial&lt;/td&gt;
&lt;td&gt;❌ Apenas sequencial&lt;/td&gt;
&lt;td&gt;✅ Por janela&lt;/td&gt;
&lt;td&gt;❌ Apenas sequencial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance em pg. tardias&lt;/td&gt;
&lt;td&gt;❌ Degrada&lt;/td&gt;
&lt;td&gt;✅ Constante&lt;/td&gt;
&lt;td&gt;✅ Constante&lt;/td&gt;
&lt;td&gt;✅ Constante&lt;/td&gt;
&lt;td&gt;✅ Constante&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Registros novos entre páginas&lt;/td&gt;
&lt;td&gt;❌ Drift&lt;/td&gt;
&lt;td&gt;✅ Consistente&lt;/td&gt;
&lt;td&gt;✅ Snapshot&lt;/td&gt;
&lt;td&gt;✅ Janela fixa&lt;/td&gt;
&lt;td&gt;✅ Consistente&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complexidade de impl.&lt;/td&gt;
&lt;td&gt;⭐ Simples&lt;/td&gt;
&lt;td&gt;⭐⭐ Média&lt;/td&gt;
&lt;td&gt;⭐⭐⭐ Alta&lt;/td&gt;
&lt;td&gt;⭐⭐ Média&lt;/td&gt;
&lt;td&gt;⭐⭐ Média&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recursos no servidor&lt;/td&gt;
&lt;td&gt;Baixo&lt;/td&gt;
&lt;td&gt;Baixo&lt;/td&gt;
&lt;td&gt;🔴 Alto (cursor aberto)&lt;/td&gt;
&lt;td&gt;Baixo&lt;/td&gt;
&lt;td&gt;Baixo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Suporte EF Core nativo&lt;/td&gt;
&lt;td&gt;✅ Completo&lt;/td&gt;
&lt;td&gt;✅ Com Where custom&lt;/td&gt;
&lt;td&gt;⚠️ Raw SQL / ADO&lt;/td&gt;
&lt;td&gt;✅ Com Where custom&lt;/td&gt;
&lt;td&gt;✅ Sobre keyset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQL Server&lt;/td&gt;
&lt;td&gt;✅ OFFSET FETCH&lt;/td&gt;
&lt;td&gt;✅ WHERE seek&lt;/td&gt;
&lt;td&gt;✅ FAST_FORWARD cursor&lt;/td&gt;
&lt;td&gt;✅ WHERE range&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oracle&lt;/td&gt;
&lt;td&gt;✅ 12c+ / ROWNUM 11g&lt;/td&gt;
&lt;td&gt;✅ WHERE seek&lt;/td&gt;
&lt;td&gt;✅ REF CURSOR&lt;/td&gt;
&lt;td&gt;✅ WHERE range&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;✅ LIMIT OFFSET&lt;/td&gt;
&lt;td&gt;✅ WHERE seek&lt;/td&gt;
&lt;td&gt;✅ DECLARE CURSOR&lt;/td&gt;
&lt;td&gt;✅ WHERE range&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Melhor para&lt;/td&gt;
&lt;td&gt;UI clássica&lt;/td&gt;
&lt;td&gt;APIs / mobile&lt;/td&gt;
&lt;td&gt;ETL / streaming&lt;/td&gt;
&lt;td&gt;Auditoria / logs&lt;/td&gt;
&lt;td&gt;APIs públicas&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Índices: A Fundação de Toda Paginação
&lt;/h2&gt;

&lt;p&gt;Nenhuma estratégia de paginação funciona bem sem os índices certos. Para cada banco:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ============================================&lt;/span&gt;
&lt;span class="c1"&gt;-- SQL Server — Índices para paginação&lt;/span&gt;
&lt;span class="c1"&gt;-- ============================================&lt;/span&gt;

&lt;span class="c1"&gt;-- Para Offset por DataCriacao&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX_Pedidos_DataCriacao&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;INCLUDE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Para filtros frequentes + paginação&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX_Pedidos_ClienteId_Data&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;INCLUDE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- ============================================&lt;/span&gt;
&lt;span class="c1"&gt;-- Oracle — Equivalentes&lt;/span&gt;
&lt;span class="c1"&gt;-- ============================================&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX_Pedidos_DataCriacao&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Oracle: statistics importantes para o otimizador&lt;/span&gt;
&lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="n"&gt;DBMS_STATS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GATHER_TABLE_STATS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SCHEMA'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'PEDIDOS'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- ============================================&lt;/span&gt;
&lt;span class="c1"&gt;-- PostgreSQL — Com partial index opcional&lt;/span&gt;
&lt;span class="c1"&gt;-- ============================================&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX_Pedidos_DataCriacao&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;INCLUDE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Partial index para status frequente (ex.: apenas pedidos ativos)&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX_Pedidos_Ativos&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DataCriacao&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Ativo'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ &lt;strong&gt;Atenção Oracle:&lt;/strong&gt; O Oracle não suporta &lt;code&gt;INCLUDE&lt;/code&gt; columns em índices regulares (diferente de SQL Server e PostgreSQL). Para covering indexes no Oracle, use &lt;strong&gt;Composite Indexes&lt;/strong&gt; ou &lt;strong&gt;Index-Organized Tables (IOT)&lt;/strong&gt; para tabelas de alto volume de leitura.&lt;/p&gt;

&lt;h2&gt;
  
  
  Middleware de Paginação Reutilizável para ASP.NET Core
&lt;/h2&gt;

&lt;p&gt;Para APIs REST com múltiplos endpoints, criar um middleware de paginação evita duplicação:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Extensions para registrar os serviços e configurar resposta padrão&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaginacaoExtensions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Header padrão de paginação (common em APIs REST)&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IEndpointConventionBuilder&lt;/span&gt; &lt;span class="nf"&gt;ComPaginacao&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IEndpointConventionBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Adiciona headers de documentação no OpenAPI&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithOpenApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenApiParameter&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"cursor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;In&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ParameterLocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Schema&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenApiSchema&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"string"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;Description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Token de cursor para próxima página (keyset pagination)"&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenApiParameter&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"limite"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;In&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ParameterLocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Schema&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenApiSchema&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"integer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OpenApiInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Adiciona Link header HTTP padrão RFC 5988&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;AdicionarLinkHeader&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;HttpContext&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;KeysetResultado&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProximoToken&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Link"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;$"&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;?cursor=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProximoToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;; rel=\"next\""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dicas e Boas Práticas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nunca retorne sem Take/Limit:&lt;/strong&gt; Sempre aplique um limite máximo na camada de serviço, independente do que o cliente enviou. Exponha isso via validação de request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keyset exige chave composta estável:&lt;/strong&gt; Use sempre &lt;code&gt;(coluna_ordenacao, id_unico)&lt;/code&gt; como cursor composto. Apenas a coluna de ordenação pode ter duplicatas, o &lt;code&gt;Id&lt;/code&gt; garante unicidade e estabilidade.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cursor de banco: sempre feche:&lt;/strong&gt; Para cursores server-side (PostgreSQL &lt;code&gt;DECLARE&lt;/code&gt;, Oracle &lt;code&gt;REF CURSOR&lt;/code&gt;), use &lt;code&gt;try/finally&lt;/code&gt; para garantir o &lt;code&gt;CLOSE&lt;/code&gt;. Um cursor esquecido aberto no Oracle ou SQL Server consome recursos do servidor indefinidamente.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documente o tipo de paginação na OpenAPI:&lt;/strong&gt; Indique no Swagger qual estratégia cada endpoint usa — clientes precisam saber se podem usar salto de página ou apenas avançar sequencialmente.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versione a estratégia de paginação:&lt;/strong&gt; Se você mudar de Offset para Keyset em um endpoint existente, versione a API (&lt;code&gt;/v2/pedidos&lt;/code&gt;) para não quebrar clientes que dependem do campo &lt;code&gt;totalPaginas&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor de queries lentas:&lt;/strong&gt; Ative o Query Store (SQL Server), AWR (Oracle) ou &lt;code&gt;pg_stat_statements&lt;/code&gt; (PostgreSQL) para capturar queries de paginação que ultrapassam SLAs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefira projeções com Select():&lt;/strong&gt; Nunca retorne a entidade completa em endpoints de listagem. Selecione apenas os campos necessários para reduzir I/O e alocação de memória.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Paginar dados em APIs REST com C# e EF Core 8 vai muito além de &lt;code&gt;.Skip().Take()&lt;/code&gt;. Cada estratégia — &lt;strong&gt;Offset&lt;/strong&gt;, &lt;strong&gt;Keyset&lt;/strong&gt;, &lt;strong&gt;Cursor server-side&lt;/strong&gt;, &lt;strong&gt;Time-based&lt;/strong&gt; e &lt;strong&gt;Token opaco&lt;/strong&gt; — existe por um motivo e serve a cenários distintos. A escolha errada resulta em queries lentas, inconsistências de dados ou consumo desnecessário de recursos no banco.&lt;/p&gt;

&lt;p&gt;O caminho mais seguro é: &lt;strong&gt;comece com Offset&lt;/strong&gt; para UIs simples com volumes controlados, &lt;strong&gt;migre para Keyset&lt;/strong&gt; conforme a tabela cresce e o offset começa a degradar, use &lt;strong&gt;Cursor server-side&lt;/strong&gt; apenas para streaming e ETL com cuidado no gerenciamento do ciclo de vida, e &lt;strong&gt;Time-based&lt;/strong&gt; para dados com dimensão temporal natural.&lt;/p&gt;

&lt;p&gt;SQL Server, Oracle e PostgreSQL suportam todas essas estratégias — as diferenças estão na sintaxe e nos detalhes de implementação, mas o EF Core 8 abstrai a maior parte delas. O que o EF Core não faz por você é criar os índices certos: essa é sempre sua responsabilidade.&lt;/p&gt;

&lt;p&gt;Se você chegou aqui buscando entender como gargalos de banco afetam não só a leitura, mas também a escrita em massa, continue com o artigo Gargalo em Banco de Dados com C# e EF Core: Mensageria e Paginação, que aborda a solução via mensageria (RabbitMQ e Azure Service Bus) para o lado da escrita.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leia Também
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;EF Core 8 com Fluent API: Mapeamento, ORM e Desacoplamento Total&lt;/li&gt;
&lt;li&gt;EF Core Migrations em Multi-Projeto: Secrets, Scaffolding e Gestão em Times&lt;/li&gt;
&lt;li&gt;Full-Text Search em APIs REST com C#: SQL Server, PostgreSQL e Oracle&lt;/li&gt;
&lt;li&gt;Arquitetura de Software e os Padrões GoF: do Código à Nuvem, do Monólito ao Microserviço&lt;/li&gt;
&lt;li&gt;Design de APIs REST: Sem Verbos na URL, Métodos HTTP e Binding de Parâmetros no ASP.NET Core&lt;/li&gt;
&lt;li&gt;Gargalo em Banco de Dados com C# e EF Core: Mensageria e Paginação&lt;/li&gt;
&lt;li&gt;Programação Assíncrona em C#: async/await do Fundamento à Produção&lt;/li&gt;
&lt;li&gt;Paralelismo em C#: Parallel, PLINQ e Tasks do Fundamento à Produção&lt;/li&gt;
&lt;li&gt;.NET Worker e Background Service para Alto Volume&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Referências
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;EF Core — Querying Data (Microsoft Docs) — Documentação oficial de consultas com EF Core&lt;/li&gt;
&lt;li&gt;EF Core — Pagination — Guia oficial de paginação com EF Core (Offset e Keyset)&lt;/li&gt;
&lt;li&gt;PostgreSQL — Cursors — Documentação oficial de cursores no PostgreSQL&lt;/li&gt;
&lt;li&gt;SQL Server — Cursor T-SQL — Referência de cursores T-SQL no SQL Server&lt;/li&gt;
&lt;li&gt;Oracle — REF CURSOR — Documentação oficial de REF CURSOR no Oracle PL/SQL&lt;/li&gt;
&lt;li&gt;Use the PostgreSQL pg_stat_statements — Monitoramento de queries lentas no PostgreSQL&lt;/li&gt;
&lt;li&gt;Repositório blog-zocateli-sample — Pagination — Código-fonte completo dos exemplos deste artigo
📬&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;👉 &lt;strong&gt;Artigo completo com todos os exemplos de código:&lt;/strong&gt; &lt;a href="https://zocate.li/posts/2026/paginacao-api-rest-csharp-efcore-sqlserver-oracle-postgres/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog" rel="noopener noreferrer"&gt;Paginação em APIs REST com C# e EF Core: Guia Prático&lt;/a&gt;&lt;/p&gt;

</description>
      <category>portuguese</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>efcore</category>
    </item>
    <item>
      <title>O Projeto Fênix e a TI que eu vivo todos os dias</title>
      <dc:creator>Lincoln Zocateli</dc:creator>
      <pubDate>Tue, 14 Apr 2026 01:42:47 +0000</pubDate>
      <link>https://dev.to/lzocate-li/o-projeto-fenix-e-a-ti-que-eu-vivo-todos-os-dias-4a12</link>
      <guid>https://dev.to/lzocate-li/o-projeto-fenix-e-a-ti-que-eu-vivo-todos-os-dias-4a12</guid>
      <description>&lt;h2&gt;
  
  
  INTRODUÇÃO
&lt;/h2&gt;

&lt;p&gt;Como Solutions Architect e dev .NET há mais de 25 anos, eu já vi de tudo um pouco: times brilhantes travados por processos ruins, deploy manual virando ritual de sofrimento e decisões técnicas sendo engolidas por urgência. Foi por isso que "O Projeto Fênix", do Gene Kim, me pegou tanto — não como ficção corporativa, mas como espelho.&lt;/p&gt;

&lt;p&gt;O livro fala sobre DevOps, fluxo e melhoria contínua, mas o que realmente me marcou foi a sensação de frustração de querer fazer certo em um ambiente que ainda não entende o básico de entrega de software.&lt;/p&gt;

&lt;h2&gt;
  
  
  DESENVOLVIMENTO
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Quando a operação vira o gargalo&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O livro deixa claro que a TI não quebra só por falta de ferramenta; ela quebra quando cada área otimiza o próprio pedaço e ninguém enxerga o fluxo inteiro.&lt;/li&gt;
&lt;li&gt;Isso conversa diretamente com ambientes onde o dev termina o trabalho e "joga para a operação", sem ownership real.&lt;/li&gt;
&lt;li&gt;Na prática, o custo aparece em filas, retrabalho, incidentes recorrentes e deploys que dependem de heroísmo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;DevOps não é cargo, é desenho de sistema&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O ponto mais forte do livro é mostrar que DevOps não se resume a Jenkins, Kubernetes ou pipelines bonitos.&lt;/li&gt;
&lt;li&gt;CI/CD só funciona quando existe cultura de automação, feedback rápido e responsabilidade compartilhada.&lt;/li&gt;
&lt;li&gt;Sem isso, a esteira vira teatro: automatiza um caos que continua existindo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Arquitetura também é gestão de fluxo&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Como arquiteto, eu me identifiquei com a tensão entre padrão ideal e realidade operacional.&lt;/li&gt;
&lt;li&gt;Não adianta desenhar uma arquitetura elegante se o time não consegue entregar com segurança, observabilidade e rollback.&lt;/li&gt;
&lt;li&gt;Decisões técnicas boas precisam considerar lead time, risco e capacidade do time de sustentar o sistema.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;O que fica depois da leitura&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Melhoria contínua não é discurso: é disciplina.&lt;/li&gt;
&lt;li&gt;Qualidade não pode ser "fase final"; precisa nascer junto com o código.&lt;/li&gt;
&lt;li&gt;E maturidade técnica não é fazer mais coisas, mas reduzir atrito entre ideia, implementação e produção.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  CONCLUSÃO
&lt;/h2&gt;

&lt;p&gt;"O Projeto Fênix" funciona porque não romantiza a TI: ele mostra o preço real de ignorar fluxo, feedback e colaboração. Para mim, foi impossível não enxergar ali a rotina de muitos times que ainda vivem entre urgência, dívida técnica e processos quebrados.&lt;/p&gt;

&lt;p&gt;No fim, a grande lição é simples: entregar software bem não é um detalhe operacional, é parte central da estratégia.&lt;/p&gt;




&lt;p&gt;📖 Artigo completo: &lt;a href="https://zocate.li/posts/2026/projeto-fenix-romance-ti-devops/" rel="noopener noreferrer"&gt;https://zocate.li/posts/2026/projeto-fenix-romance-ti-devops/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>portuguese</category>
      <category>projetofenix</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
  </channel>
</rss>
