<?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>Forem: Rafael</title>
    <description>The latest articles on Forem by Rafael (@rafaelpadovezi).</description>
    <link>https://forem.com/rafaelpadovezi</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%2F38235%2F959d0b3f-a599-4daa-8153-912e98666075.png</url>
      <title>Forem: Rafael</title>
      <link>https://forem.com/rafaelpadovezi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/rafaelpadovezi"/>
    <language>en</language>
    <item>
      <title>Minha jornada de otimização de uma aplicação django</title>
      <dc:creator>Rafael</dc:creator>
      <pubDate>Wed, 13 Mar 2024 20:19:00 +0000</pubDate>
      <link>https://forem.com/rafaelpadovezi/minha-jornada-de-otimizacao-de-uma-aplicacao-django-2mdi</link>
      <guid>https://forem.com/rafaelpadovezi/minha-jornada-de-otimizacao-de-uma-aplicacao-django-2mdi</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/zanfranceschi/rinha-de-backend-2024-q1"&gt;Rinha de backend&lt;/a&gt; é um desafio de programação com o objetivo de gerar e compartilhar conhecimento. O texto descreve minha experiência ao participar do desafio mas acredito que é útil para todos que querem entender um pouco mais de django, gunicorn e gevent.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Para a rinha de backend segunda edição eu fiz uma submissão usando django com o objetivo de aprimorar meus conhecimentos em python e no framework. O desafio consiste em resolver o problema clássico de concorrência de transações bancárias ao mesmo tempo que deve suportar um teste de carga. O código resultante da minha submissão pode ser visto &lt;a href="https://github.com/rafaelpadovezi/rinha-2-django-orm"&gt;aqui&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Além do django, eu escolhi usar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Django rest framework&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gunicorn.org"&gt;Gunicorn&lt;/a&gt; como web server&lt;/li&gt;
&lt;li&gt;PostrgreSQL como banco de dados&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A minha primeira meta era conseguir executar os testes de carga na API sem restrições de recursos, podendo usar todos os recursos disponíveis do meu computador. Após isso, para poder submeter minha solução do desafio era necessário aplicar limites de CPU (1.5) e memória (550MB). Ou seja, a soma dos recursos do load balancer, banco de dados e duas instâncias da API devem estar dentro do limite especificado. E, se possível, manter os tempos de requisições baixos durante todo o teste.&lt;/p&gt;

&lt;p&gt;Importante falar que a maioria das otimizações foram feitas especialmente para a rinha e não necessariamente sejam necessárias ou até mesmo recomendadas em aplicação reais. Mas ao mesmo tempo me sinto muito mais confortável em colocar aplicações django em produção com o conhecimento que adquiri durante essa jornada.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gevent
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.gevent.org/intro.html"&gt;Gevent&lt;/a&gt; é uma biblioteca de corotinas do python que facilita a execução de código bloqueante de maneira assíncrona. Um exemplo do impacto do uso gevent pode ser visto &lt;a href="https://www.willianantunes.com/blog/2023/07/understand-async-workers-by-practice-with-django-gunicorn-and-gevent/"&gt;nesse artigo&lt;/a&gt;. Como tenho mais experiência com C# faço o paralelo com o async/await apesar de terem implementações distintas.&lt;/p&gt;

&lt;p&gt;O gunicorn apresenta tipos distintos de workers e a &lt;a href="https://docs.gunicorn.org/en/latest/design.html#choosing-a-worker-type"&gt;documentação&lt;/a&gt; recomenda:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Some examples of behavior requiring asynchronous workers:&lt;br&gt;
Applications making long blocking calls (Ie, external web services)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Como a API é basicamente IO bound foi escolhido o gevent como worker type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Micro-otimizacoes
&lt;/h2&gt;

&lt;p&gt;Uma vez que um dos objetivos era ter uma API otimizada busquei e encontrei os seguintes tópicos para melhorar a performance do meu código:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.djangoproject.com/en/5.0/ref/models/querysets/#values"&gt;values()&lt;/a&gt;: retorna um Queryset que retorna um dicionário ao invés de uma instância do modelo&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.djangoproject.com/en/5.0/ref/models/instances/#specifying-which-fields-to-save"&gt;update_fields&lt;/a&gt;: argumento do método save que especifica qual campo deve ser atualizado simplificando o comando SQL gerado&lt;/li&gt;
&lt;li&gt;Remoção dos middlewares desnecessários (para o desafio)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eu não medi o impacto mas mantive as mudanças. Considerando as documentações e blogs é esperado algum ganho de performance.&lt;/p&gt;

&lt;p&gt;Algumas coisas que tentei mas abandonei:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.pgbouncer.org/"&gt;Pgbouncer&lt;/a&gt; - resolvia o problema do limite de conexões no postgres. Mas a API “saudável” manteve o número de conexões baixo o suficiente.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.djangoproject.com/en/5.0/topics/performance/#id1"&gt;Pypy&lt;/a&gt; ao invés de cpython - os resultados não foram bons, aumentou o consumo de recursos entregando uma performance pior. No entanto, é importante dizer que não me aprofundei no assunto e não dediquei nenhum tempo para fazer qualquer tunning.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rafaelpadovezi/rinha-2-django"&gt;Usar diretamente o psycopg3 e o seu connection pool&lt;/a&gt;. Obtive ótimos resultados dessa maneira e serviu para mostrar que o setup django + gunicorn + gevent funciona bem e é capaz de processar as requisições com a limitação de recursos da rinha. Mas ainda queria ter uma submissão com o ORM.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conexões persistentes (CONN_MAX_AGE)
&lt;/h2&gt;

&lt;p&gt;Django não possui uma connection pool própria para conexão com banco de dados. Uma possibilidade é o uso da propriedade &lt;a href="https://docs.djangoproject.com/en/5.0/ref/databases/#persistent-connections"&gt;CONN_MAX_AGE&lt;/a&gt; que determina o tempo de vida que uma conexão pode ter. Quando 0 (valor padrão) cada conexão será finalizada no fim da execução da requisição. Valores maiores que 0 indicam o tempo que uma conexão pode existir até ser finalizada, permitindo que outras requisições reusem a conexão. Percebi na prática que usar qualquer valor diferente de 0 não funciona bem com o worker do tipo gevent do gunicorn. Como descrito &lt;a href="https://github.com/benoitc/gunicorn/issues/996#issuecomment-93301359"&gt;aqui&lt;/a&gt;, as conexões não são reutilizadas nesse cenário.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffzlas3tdtx54n0h8en7d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffzlas3tdtx54n0h8en7d.png" alt="Com  raw `CONN_MAX_AGE!=0` endraw  e  raw `worker_type=gevent` endraw  não há reutilizações das conexões persistentes. O número de conexões total cresce mesmo com quase todas conexões ociosas. Na medida que se aumenta a quantidade de requisições por segundo o número de conexões também cresce" width="800" height="131"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Com &lt;code&gt;CONN_MAX_AGE!=0&lt;/code&gt; e &lt;code&gt;worker_type=gevent&lt;/code&gt; não há reutilizações das conexões persistentes. O número de conexões total cresce mesmo com quase todas conexões ociosas. Na medida que se aumenta a quantidade de requisições por segundo o número de conexões também cresce&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Usar esse valor com 0 mantém o número de sessões no postgres baixo, sem necessidade de se reutilizar as conexões para esse fim.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F10b0tsxt5yt9ny0qycxg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F10b0tsxt5yt9ny0qycxg.png" alt="Com  raw `CONN_MAX_AGE=0` endraw  e  raw `worker_type=gevent` endraw  o número de conexões se manteve estável. Cada requisição abre e fecha uma nova conexão com o banco" width="727" height="248"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Com &lt;code&gt;CONN_MAX_AGE=0&lt;/code&gt; e &lt;code&gt;worker_type=gevent&lt;/code&gt; o número de conexões se manteve estável. Cada requisição abre e fecha uma nova conexão com o banco&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Worker connections
&lt;/h2&gt;

&lt;p&gt;O valor padrão de &lt;a href="https://docs.gunicorn.org/en/stable/settings.html#worker-connections"&gt;worker connections&lt;/a&gt; é 1000 então começar os testes com o valor 50 e ir aumentando me pareceu razoável. No entanto, enquanto usava esse valor “alto”, durante o teste o número de sessões no postgres começava estável até chegar um momento de pico e a partir daí aplicação já não era capaz de responder as requisições do teste de carga.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flaag61429wnaya3ymoq2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flaag61429wnaya3ymoq2.png" alt="Com um valor maior de  raw `GUNICORN_WORKER_CONNECTIONS` endraw  e  raw `GUNICORN_WORKER_TYPE=gevent` endraw , em um determinado ponto da execução dos testes de carga o número de conexões com banco de dados explodia, causando erros na API" width="800" height="147"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Com um valor maior de &lt;code&gt;GUNICORN_WORKER_CONNECTIONS&lt;/code&gt; e &lt;code&gt;GUNICORN_WORKER_TYPE=gevent&lt;/code&gt;, em um determinado ponto da execução dos testes de carga o número de conexões com banco de dados explodia, causando erros na API&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Acidentalmente fiz um teste sem gevent e para minha surpresa os testes foram concluídos sem erros. Lendo &lt;a href="https://eng.lyft.com/gevent-part-3-performance-e64303fa102b"&gt;esse artigo&lt;/a&gt; entendi o motivo. O número alto de worker connections e gevent significa que mais greenlets (pseudo threads) eram agendados além da capacidade de processamento. Uma requisição era iniciada e, por conta de uma operação bloqueante como um query no banco, a execução era pausada e ia para fila de itens a serem processados. Como já existiam várias requisições na frente, essa greenlet sofria starvation e esse processo causa um pico de número de sessões abertas ao aplicar uma carga suficiente. Ajustando esse valor para um número menor (no meu caso 5) já é possível perceber que há uma melhora considerável nos resultados dos testes. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbhqrmju2izbbxjq3o4vc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbhqrmju2izbbxjq3o4vc.png" alt="Com  raw `worker_connections=5` endraw  as conexões do banco se mativeram baixas durante todo o teste" width="800" height="125"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Com &lt;code&gt;worker_connections=5&lt;/code&gt; as conexões do banco se mativeram baixas durante todo o teste.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp30t6p0iqtlgna3r5tor.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp30t6p0iqtlgna3r5tor.png" alt="Parte do relatório de execução dos testes de carga do Gatling. O resultado apresenta boa performance mas ainda não foi aplicado os limites de recursos" width="800" height="200"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Parte do relatório de execução dos testes de carga do Gatling. O resultado apresenta boa performance mas ainda não foi aplicado os limites de recursos&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Profiling
&lt;/h2&gt;

&lt;p&gt;Apesar de muito contente com a execução da API ainda estava cético pois as métricas coletadas durante os testes mostravam que a API não iria se comportar bem com os limites impostos do desafio, principalmente para CPU.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6kcyujxzrt7mngoz8di9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6kcyujxzrt7mngoz8di9.png" alt="Resultado do comando  raw `docker stats` endraw . Valores de CPU das APIs superam (e muito) os limites do desafio" width="800" height="81"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Resultado do comando &lt;code&gt;docker stats&lt;/code&gt;. Valores de CPU das APIs superam (e muito) os limites do desafio&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Quando limitado, os resultado dos testes de carga apresentavam alta taxa de erro pois a API não processava as requisições em tempo hábil para responder a carga enviada. Para tentar entender o motivo usei uma ferramenta muito interessante para profiling que me ganhou pela facilidade de uso. Com um simples comando &lt;a href="https://github.com/benfred/py-spy"&gt;py-spy&lt;/a&gt; consegui gerar o flamegraph da minha API durante a carga e visualizar os métodos que mais consomem tempo de CPU.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;py-spy record --gil --subprocesses -o profile.svg -- gunicorn -w 2 rinha.wsgi -b 0:9999
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbexranqze0yj99gaadcb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbexranqze0yj99gaadcb.png" alt="Flamegraph de um worker gerado pelo py-spy durante a execução dos testes de carga" width="800" height="293"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Flamegraph de um worker gerado pelo py-spy durante a execução dos testes de carga&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbt47dwpvy716vu4uctao.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbt47dwpvy716vu4uctao.png" alt="Flamegraph com foco na abertura da transação com o banco de dados" width="800" height="294"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Flamegraph com foco na abertura da transação com o banco de dados&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;O arquivo gerado é um SVG navegável e pode ser baixado &lt;a href="https://github.com/rafaelpadovezi/rinha-2-django-orm/blob/d96aca58e2743794349f00881b3d41d8d885f333/profile.svg"&gt;aqui&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  De volta com CONN_MAX_AGE
&lt;/h2&gt;

&lt;p&gt;Já no último dia da rinha, a poucas horas do fim, enquanto observava o gráfico percebi que muito tempo de CPU era gasto na criação da conexão com o banco de dados. Assim eu especulei que se houvesse um pool de conexões ou, pelo menos evitar que elas fossem abertas a cada requisição, haveria redução do consumo de CPU. Isso trouxe a ideia de setar novamente um valor de CONN_MAX_AGE para manter as conexões abertas e que fossem reutilizadas. Mas para isso seria necessário alterar o worker type para &lt;a href="https://docs.gunicorn.org/en/latest/design.html#sync-workers"&gt;sync&lt;/a&gt;. A expectativa é que o menor consumo de recursos possibilitaria a execução da API sob carga com as limitações impostas. E felizmente foi isso que aconteceu.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fllk6z6q5nulm9mgmmx9r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fllk6z6q5nulm9mgmmx9r.png" alt="Parte do relatório de execução dos testes de carga do Gatling. O resultado apresenta boa performance mesmo com os limites de recursos aplicados" width="800" height="202"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Parte do relatório de execução dos testes de carga do Gatling. O resultado apresenta boa performance mesmo com os limites de recursos aplicados&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;E isso é o fim da minha versão de django + orm para a rinha!&lt;/p&gt;

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

&lt;p&gt;Então o gevent é ruim e é melhor usar sync mesmo? Não é bem assim. O que eu posso afirmar é que em um cenário de &lt;strong&gt;baixíssima latência&lt;/strong&gt;, &lt;strong&gt;requisições externas rápidas&lt;/strong&gt;, &lt;strong&gt;carga alta&lt;/strong&gt; e &lt;strong&gt;recursos limitados&lt;/strong&gt; é melhor abrir mão do “async” para ter a reutilização de conexões. O overhead de abrir uma conexão por requisição foi o fator determinante para a API não passar nos testes de carga do desafio. Acredito que usando alguma biblioteca que implemente um pool de conexões no django seria possível usar o gevent para esse caso.&lt;/p&gt;

&lt;p&gt;Além disso, os maiores ensinamentos foram:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Conexões persistentes e gevent worker não funcionam juntos. É necessário escolher um ou outro;&lt;/li&gt;
&lt;li&gt;Usando gunicorn, se for alterar o worker type de sync para gevent faça testes para escolher o valor de worker connections. Um valor muito acima do ideal prejudica bastante a performance;&lt;/li&gt;
&lt;li&gt;Não dá para resolver o problema sem saber qual é o problema. Em outras palavras, use ferramentas para medir e permitir fazer uma análise efetiva. Eu não teria feito muito sem docker stats, pgadmin e py-spy. E se eu tivesse gasto mais tempo em instrumentação poderia ter ido mais longe e gasto menos tempo em tentativa e erro.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Understand Async Workers by Practice with Django, Gunicorn, and Gevent - &lt;a href="https://www.willianantunes.com/blog/2023/07/understand-async-workers-by-practice-with-django-gunicorn-and-gevent/"&gt;https://www.willianantunes.com/blog/2023/07/understand-async-workers-by-practice-with-django-gunicorn-and-gevent/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Web API performance: profiling Django REST framework - &lt;a href="https://medium.com/@yashnarsamiyev2/web-api-performance-profiling-django-rest-framework-72886c5cd614"&gt;https://medium.com/@yashnarsamiyev2/web-api-performance-profiling-django-rest-framework-72886c5cd614&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;What the heck is gevent? (Part 1 of 4) - &lt;a href="https://eng.lyft.com/what-the-heck-is-gevent-4e87db98a8"&gt;https://eng.lyft.com/what-the-heck-is-gevent-4e87db98a8&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Django ORM Under the Hood - Iterables - &lt;a href="https://www.hacksoft.io/blog/django-orm-under-the-hood-iterables"&gt;https://www.hacksoft.io/blog/django-orm-under-the-hood-iterables&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>django</category>
      <category>gunicorn</category>
      <category>rinhadebackend</category>
    </item>
    <item>
      <title>Diagnósticos usando dotnet-monitor + prometheus + grafana</title>
      <dc:creator>Rafael</dc:creator>
      <pubDate>Wed, 03 Jan 2024 21:30:00 +0000</pubDate>
      <link>https://forem.com/rafaelpadovezi/diagnosticos-usando-dotnet-monitor-prometheus-grafana-3n7o</link>
      <guid>https://forem.com/rafaelpadovezi/diagnosticos-usando-dotnet-monitor-prometheus-grafana-3n7o</guid>
      <description>&lt;p&gt;No meu &lt;a href="https://dev.to/rafaelpadovezi/threadpool-no-aspnet-e-problemas-de-performance-3mao"&gt;último post&lt;/a&gt; escrevi sobre o problema de &lt;strong&gt;Threadpool Starvation&lt;/strong&gt; e o uso de &lt;code&gt;dotnet-counters&lt;/code&gt; para obter as métricas de performance de uma aplicação dotnet. Neste artigo, será usado o &lt;code&gt;dotnet-monitor&lt;/code&gt; como ferramenta para obter diversas informações de diagnóstico de aplicações dotnet. O foco será em métricas de performance da aplicação e para facilitar a visualização será utilizado o conjunto &lt;a href="https://prometheus.io/"&gt;prometheus&lt;/a&gt; e &lt;a href="https://grafana.com/oss/grafana/"&gt;grafana&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Para o artigo foi criado um laboratório usando docker compose e o código pode ser encontrado no &lt;a href="https://github.com/rafaelpadovezi/thread-pool-starvation"&gt;github&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  dotnet-monitor
&lt;/h2&gt;

&lt;p&gt;dotnet-monitor é uma ferramenta de monitoramento em ambiente produtivo capaz de coletar artefatos como dumps, traces, logs e métricas. A ferramenta expõe &lt;a href="https://github.com/dotnet/dotnet-monitor/blob/main/documentation/api/README.md"&gt;uma API&lt;/a&gt; que disponibiliza extração de informações de diagnóstico sob demanda.&lt;/p&gt;

&lt;p&gt;O runtime do dotnet expõe um endpoint de serviço para comunicação entre processos chamado de &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/diagnostics/diagnostic-port"&gt;porta de diagnóstico&lt;/a&gt;. A tecnologia usada depende da plataforma. No Linux são utilizados &lt;a href="https://twitter.com/b0rk/status/980955984402272257"&gt;sockets de domínio Unix&lt;/a&gt; como transporte. É dessa maneira que o &lt;code&gt;dotnet-monitor&lt;/code&gt; se comunica com as aplicações dotnet. No laboratório, a aplicação e o monitor são executados em diferentes containers e dessa forma é preciso que o monitor compartilhe um volume com a aplicação.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
    &lt;span class="s"&gt;environment&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DOTNET_DiagnosticPorts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/app/diag/dotnet-monitor.sock&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./:/app/"&lt;/span&gt;
  &lt;span class="na"&gt;monitor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mcr.microsoft.com/dotnet/monitor:6&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DOTNETMONITOR_Storage__DefaultSharedPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/diag&lt;/span&gt;
      &lt;span class="na"&gt;DOTNETMONITOR_DiagnosticPort__ConnectionMode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;listen&lt;/span&gt;
      &lt;span class="na"&gt;DOTNETMONITOR_DiagnosticPort__EndpointName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/diag/dotnet-monitor.sock&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./diag:/diag"&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;É possível executar a aplicação e o monitor com o comando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O endpoint de metrics exporta os valores no formato compatível do Prometheus e pode ser acessado no endereço &lt;a href="http://localhost:52325/metrics"&gt;http://localhost:52325/metrics&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prometheus
&lt;/h2&gt;

&lt;p&gt;Prometheus é uma ferramenta de monitoramento open source capaz de coletar e armazenar métricas como series de dados temporais.&lt;/p&gt;

&lt;p&gt;Para o laboratório é usada a seguinte configuração:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# prometheus/prometheus.yml&lt;/span&gt;
&lt;span class="na"&gt;scrape_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
  &lt;span class="s"&gt;- job_name&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;monitor"&lt;/span&gt;
    &lt;span class="na"&gt;scrape_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
    &lt;span class="na"&gt;metrics_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/metrics"&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;monitor:52323"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dessa forma, ao inicializar o container usando o docker compose o prometheus irá se conectar ao dotnet-monitor para coleta das métricas.&lt;/p&gt;

&lt;p&gt;É possível navegar e visualizar as métricas na interface do Prometheus mas vamos usá-lo como fonte de dados para uma outra ferramenta focada em visualização.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grafana
&lt;/h2&gt;

&lt;p&gt;Grafana é uma ferramenta open source de visualização e monitoramento que permite a criação de dashboards. É possível usar várias tecnologias como fonte de dados, incluindo Prometheus.&lt;/p&gt;

&lt;p&gt;Outro ponto interessante do grafana é a disponibilização de inúmeros &lt;a href="https://grafana.com/grafana/dashboards/"&gt;dashboards&lt;/a&gt; pela comunidade que podem ser usados como base para monitoramento de sistemas e aplicações. Para o laboratório vamos usar o &lt;a href="https://grafana.com/grafana/dashboards/19297-dotnet-monitor-dashboard/"&gt;dotnet-monitor dashboard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A configuração do grafana para o laboratório poderia ser feita manualmente via GUI mas para facilitar a reprodução usaremos arquivos de &lt;a href="https://grafana.com/docs/grafana/latest/administration/provisioning/"&gt;provisionamento&lt;/a&gt; para automatizar o setup.&lt;/p&gt;

&lt;p&gt;A configuração do Prometheus como fonte de dados é feita pelo arquivo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# grafana/provisioning/datasources/default.yaml&lt;/span&gt;
&lt;span class="na"&gt;datasources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prometheus&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prometheus&lt;/span&gt;
    &lt;span class="na"&gt;access&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;proxy&lt;/span&gt;
    &lt;span class="c1"&gt;# Access mode - proxy (server in the UI) or direct (browser in the UI).&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://prometheus:9090&lt;/span&gt;
    &lt;span class="na"&gt;jsonData&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;httpMethod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
      &lt;span class="na"&gt;manageAlerts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;prometheusType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prometheus&lt;/span&gt;
      &lt;span class="na"&gt;prometheusVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2.44.0&lt;/span&gt;
      &lt;span class="na"&gt;cacheLevel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;High"&lt;/span&gt;
      &lt;span class="na"&gt;disableRecordingRules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;incrementalQueryOverlapWindow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E a configuração para permitir o provisionamento de dashboards é feita no arquivo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# grafana/provisioning/dashboards/default.yaml&lt;/span&gt;
&lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default"&lt;/span&gt;
    &lt;span class="na"&gt;folder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;file&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/lib/grafana/dashboards&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada dashboard deve ter sua definição em JSON disponibilizado no caminho definido no arquivo anterior.&lt;/p&gt;

&lt;p&gt;O arquivo docker-compose mapeia os volumes para os diretórios padrão de configurações esperada pelo grafana.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml&lt;/span&gt;
  &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/grafana-oss&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# https://grafana.com/docs/grafana/latest/administration/provisioning/&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./grafana/provisioning/:/etc/grafana/provisioning/"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./grafana/dashboards/:/var/lib/grafana/dashboards/"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O próximo comando executa o grafana e o prometheus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up grafana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O grafana pode ser acessado no endereço &lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt; (use admin/admin para login).&lt;/p&gt;

&lt;h2&gt;
  
  
  Testes de carga
&lt;/h2&gt;

&lt;p&gt;Por último, podemos executar os testes de carga usando &lt;a href="https://github.com/rakyll/hey"&gt;hey&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;O comando &lt;code&gt;docker compose up send-load-async&lt;/code&gt; dispara uma série de requisições em um endpoint que utiliza o async/await do dotnet para reaproveitamento das threads e otimização de aplicações IO bound. Os gráficos abaixo mostram a quantidade se requisições processadas durante a execução do teste bem como as métricas relacionadas ao uso de threads pela aplicação.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rx6ymGAu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p4bb2y5u0yl9gsv6vuhn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rx6ymGAu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p4bb2y5u0yl9gsv6vuhn.png" alt="Parte do dashboard: Requisições por segundo e estatísticas de threadpool da carga de testes async. Requisições ficam em 1000 constante e a fila de threadpool se mantém em zero." width="800" height="646"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Por sua vez, o comando &lt;code&gt;docker compose up send-load-sync&lt;/code&gt; realiza requisições em um endpoint que utiliza não utiliza o async/await e portanto realiza o bloqueio de threads em operações de IO. Os gráficos abaixo mostram o resultado do teste.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JF3kvzDm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2wq7ic905p59e8pvcfyl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JF3kvzDm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2wq7ic905p59e8pvcfyl.png" alt="Parte do dashboard: Requisições por segundo e estatísticas de threadpool da carga de testes async. Quantidade de requisições crescem a medida que novas threads são criadas, chegando em 600." width="800" height="645"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Os testes realizados mostram o potencial do dotnet-monitor para monitoramento de aplicações em ambientes produtivos. Os testes foram feitos usando o docker compose e a &lt;a href="https://github.com/dotnet/dotnet-monitor/blob/main/documentation%2Fkubernetes.md"&gt;configuração para execução em kubernetes&lt;/a&gt; é similar, sendo que o monitor seria publicado como um sidecar da aplicação.&lt;/p&gt;

&lt;p&gt;Tão importante quanto a extração das métricas é a possibilidade de visualizar e correlacionar esses dados para permitir o diagnóstico de problemas de performance. O par prometheus e grafana foram usados para esse meio por serem ferramentas conhecidas do mercado e facilitarem a reprodução em ambiente local para um laboratório.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>grafana</category>
      <category>prometheus</category>
      <category>monitor</category>
    </item>
    <item>
      <title>Threadpool no aspnet e problemas de performance</title>
      <dc:creator>Rafael</dc:creator>
      <pubDate>Mon, 04 Sep 2023 09:22:38 +0000</pubDate>
      <link>https://forem.com/rafaelpadovezi/threadpool-no-aspnet-e-problemas-de-performance-3mao</link>
      <guid>https://forem.com/rafaelpadovezi/threadpool-no-aspnet-e-problemas-de-performance-3mao</guid>
      <description>&lt;p&gt;Quando uma requisição passa maior parte do seu tempo aguardando o resultado de operações de entrada e saída, como banco de dados ou requisições para outras APIs, ela é considerada I/O bound.&lt;/p&gt;

&lt;p&gt;Considere o seguinte trecho de código C#, que executa uma consulta ao banco de dados.&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;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&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="n"&gt;x&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O que acontece nesse cenário é que a thread sendo executada é bloqueada ao executar a chamada remota e só será desbloqueada quando o resultado da requisição estiver disponível. Logo, outras requisições dessa API precisam ser executadas em outras threads. Se não houverem threads disponíveis no momento as outras requisições devem esperar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DgIEMVT2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s5e5tklqdam09wjb9yki.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DgIEMVT2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s5e5tklqdam09wjb9yki.png" alt="I/O bound" width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Para melhorar a performance de aplicações desse tipo o dotnet disponibiliza o tipo &lt;code&gt;Task&lt;/code&gt; que representa uma operação normalmente executada de forma assíncrona.&lt;/p&gt;

&lt;p&gt;O cenário anterior foi modificado para usar programação assíncrona.&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;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;Customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefaultAsync&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="n"&gt;x&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quando usamos o &lt;code&gt;async/await&lt;/code&gt; a thread corrente não é bloqueada como no cenário anterior. As threads são reaproveitadas ao invés de bloqueadas, o que permite que mais requisições possam ser processadas de forma concorrente. Quando usamos &lt;code&gt;Task&lt;/code&gt; e o &lt;code&gt;async/await&lt;/code&gt; por baixo dos panos uma callback é registrada e executada quando o resultado da operação de IO é retornada.&lt;/p&gt;

&lt;h3&gt;
  
  
  Threadpool starvation
&lt;/h3&gt;

&lt;p&gt;A criação e destruição de threads é um processo caro ao sistema operacional. Por esse motivo o dotnet disponibiliza o threadpool, que é um conjunto de threads que foram criadas e são disponibilizadas para uso. Quando necessário novas threads podem ser criadas pelo threadpool do dotnet mas numa taxa limitada (um ou duas por segundo).&lt;/p&gt;

&lt;p&gt;O cenário em que a quantidade de tarefas aguardando a liberação de threads aumenta em uma taxa maior que a de criação de novas threads é chamado de threadpool starvation. As requisições são enfileiradas aguardando a sua vez para serem processadas impactando a performance da aplicação.&lt;/p&gt;

&lt;p&gt;Os sintomas de aplicação nesse estado é o aumento do número de threads enquanto ainda há capacidade de CPU disponível.&lt;br&gt;
Uma maneira para diagnosticar APIs no estado de threadpool starvation é observar as seguintes métricas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;threadpool-queue-length&lt;/code&gt;: O número de itens de trabalho que estão enfileirados, no momento, para serem processados no &lt;code&gt;ThreadPool&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;threadpool-thread-count&lt;/code&gt;: O número de threads do pool de threads que existem no momento no ThreadPool, com base em &lt;code&gt;ThreadPool.ThreadCount&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;threadpool-completed-items-count&lt;/code&gt;: O número de itens de trabalho processados no &lt;code&gt;ThreadPool&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O saudável seria o número de threads constante, a fila se mantendo zerada e o número de itens processados alto.&lt;/p&gt;
&lt;h3&gt;
  
  
  Exemplo
&lt;/h3&gt;

&lt;p&gt;Como laboratório para analisar as métricas de performance em um cenário saudável e em threadpool starvation serão usadas duas ferramentas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters"&gt;&lt;code&gt;dotnet-counters&lt;/code&gt;&lt;/a&gt;: uma ferramenta para análise de performance e saúde de aplicações dotnet. Permite observar valores de contadores de performance.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rakyll/hey"&gt;&lt;code&gt;hey&lt;/code&gt;&lt;/a&gt;: gerador de carga para aplicações web.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;O código da aplicação de teste e todo o setup das ferramentas usando docker está disponível no &lt;a href="https://github.com/rafaelpadovezi/thread-pool-starvation"&gt;github&lt;/a&gt;. A aplicação possui dois endpoints que executam chamadas no banco de dados em uma operação de duração de 500 milissegundos, para simular um cenário com maior latência. O endpoint &lt;code&gt;sync&lt;/code&gt; utiliza a API síncrona e o &lt;code&gt;async&lt;/code&gt; a API assíncrona:&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="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sync"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;GetSync&lt;/span&gt;&lt;span class="p"&gt;()&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;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteSqlRaw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"WAITFOR DELAY '00:00:00.500'"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&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;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"async"&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;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetAsync&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;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;"WAITFOR DELAY '00:00:00.500'"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&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;Para iniciar aplicação e o monitoramento com o &lt;code&gt;dotnet-counters&lt;/code&gt; devem ser executados os seguintes comandos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up app
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; thread-pool-test-app dotnet-counters monitor &lt;span class="nt"&gt;-n&lt;/span&gt; dotnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O teste de carga usando o endpoint sync:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up send-load-sync
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O resultado simplificado do teste carga e um snapshot do &lt;code&gt;dotnet-counters&lt;/code&gt; é aprensentado abaixo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; Summary:
   Total:    27.2940 secs
   Slowest:  5.1772 secs
   Fastest:  0.5020 secs
   Average:  2.6085 secs
   Requests/sec:     36.6380
&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;[System.Runtime]
    % Time in GC since last GC (%)                              0
    Allocation Rate (B / 1 sec)                         2,987,784
    CPU Usage (%)                                               0
    Exception Count (Count / 1 sec)                             0
    GC Committed Bytes (MB)                                     0
    GC Fragmentation (%)                                        0
    GC Heap Size (MB)                                         109
    Gen 0 GC Count (Count / 1 sec)                              0
    Gen 0 Size (B)                                              0
    Gen 1 GC Count (Count / 1 sec)                              0
    Gen 1 Size (B)                                              0
    Gen 2 GC Count (Count / 1 sec)                              0
    Gen 2 Size (B)                                              0
    IL Bytes Jitted (B)                                   797,595
    LOH Size (B)                                                0
    Monitor Lock Contention Count (Count / 1 sec)              11
    Number of Active Timers                                     3
    Number of Assemblies Loaded                               152
    Number of Methods Jitted                               10,503
    POH (Pinned Object Heap) Size (B)                           0
    ThreadPool Completed Work Item Count (Count / 1 sec)       55
    ThreadPool Queue Length                                    74
    ThreadPool Thread Count                                    36
    Time spent in JIT (ms / 1 sec)                              0.656
    Working Set (MB)                                          232
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O teste de carga usando o endpoint async:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up send-load-async
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Os resultados:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; Summary:
   Total:   5.5532 secs
   Slowest: 1.0283 secs
   Fastest: 0.5011 secs
   Average: 0.5272 secs
   Requests/sec:    180.0777
&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;[System.Runtime]
    % Time in GC since last GC (%)                              0
    Allocation Rate (B / 1 sec)                         4,458,328
    CPU Usage (%)                                               0
    Exception Count (Count / 1 sec)                             0
    GC Committed Bytes (MB)                                     0
    GC Fragmentation (%)                                        0
    GC Heap Size (MB)                                         114
    Gen 0 GC Count (Count / 1 sec)                              0
    Gen 0 Size (B)                                              0
    Gen 1 GC Count (Count / 1 sec)                              0
    Gen 1 Size (B)                                              0
    Gen 2 GC Count (Count / 1 sec)                              0
    Gen 2 Size (B)                                              0
    IL Bytes Jitted (B)                                   825,928
    LOH Size (B)                                                0
    Monitor Lock Contention Count (Count / 1 sec)              10
    Number of Active Timers                                     3
    Number of Assemblies Loaded                               152
    Number of Methods Jitted                               10,947
    POH (Pinned Object Heap) Size (B)                           0
    ThreadPool Completed Work Item Count (Count / 1 sec)    1,384
    ThreadPool Queue Length                                     0
    ThreadPool Thread Count                                    30
    Time spent in JIT (ms / 1 sec)                              3.467
    Working Set (MB)                                          236
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Comparando as duas versões é possível ver que a versão sync tem chamadas lentas causadas pelo tempo de espera da requisição para ser processada. Durante todo o teste o &lt;code&gt;ThreadPool Queue Length&lt;/code&gt; se manteve alto. &lt;/p&gt;

&lt;p&gt;A versão async tem uma média de chamadas próximo ao tempo de 500 milissegundos que é o esperado para cada requisição e o &lt;code&gt;ThreadPool Queue Length&lt;/code&gt; se mantem próximo a 0 durante o teste. O &lt;code&gt;ThreadPool Completed Work Item Count&lt;/code&gt;é bem maior comparado ao cenário sync.&lt;/p&gt;

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

&lt;p&gt;A programação assíncrona, ao reutilizar threads ao invés de bloqueá-las, permite aumentar a quantidade de requisições capaz de serem processadas em aplicações com características IO bound. No dotnet isso é feito usando o &lt;code&gt;async/await&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Em uma aplicação real pode não ser simples identificar operações blocantes que podem levar a problemas de performance em um cenário de grande quantidade de requisições. Para isso, pode ser usada a ferramenta &lt;code&gt;dotnet-counters&lt;/code&gt; somado à um teste de carga para diagnosticar possíveis cenários de thread starvation.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>threadpool</category>
      <category>starvation</category>
      <category>async</category>
    </item>
    <item>
      <title>Consistência de dados e padrão Outbox</title>
      <dc:creator>Rafael</dc:creator>
      <pubDate>Sat, 13 May 2023 12:15:00 +0000</pubDate>
      <link>https://forem.com/rafaelpadovezi/consistencia-de-dados-e-padrao-outbox-188p</link>
      <guid>https://forem.com/rafaelpadovezi/consistencia-de-dados-e-padrao-outbox-188p</guid>
      <description>&lt;p&gt;Um dos pontos mais importantes em todas aplicações que trabalha com escrita de dados é garantir a consistência dos dados.&lt;/p&gt;

&lt;p&gt;Um exemplo básico é a criação de um pedido que possui itens. Considerando um banco relacional, os dados são armazenados em duas tabelas &lt;code&gt;Orders&lt;/code&gt; e &lt;code&gt;OrderItems&lt;/code&gt;. Imagine que a aplicação realize um &lt;code&gt;INSERT&lt;/code&gt; no banco na tabela &lt;code&gt;Orders&lt;/code&gt; e recebe um erro na tentativa de fazer o &lt;code&gt;INSERT&lt;/code&gt; na tabela &lt;code&gt;OrderItems&lt;/code&gt;. Dessa forma o sistema ficou inconsistente, o usuário tem um pedido sem itens e isso pode, inclusive, impedir que ele consiga repetir o pedido.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmupjvxz0vsfxeu8ilxqi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmupjvxz0vsfxeu8ilxqi.png" alt="Aplicação de Pedidos que armazena no banco de dados"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;O desejado é que o pedido seja salvo completamente ou que seja nada salvo. Nos bancos relacionais a solução é o uso de transações.&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="k"&gt;BEGIN&lt;/span&gt; &lt;span class="n"&gt;TRANSACTION&lt;/span&gt;
  &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;OrderItems&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;COMMIT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Caso o segundo &lt;code&gt;INSERT&lt;/code&gt; não seja concluído deve ser realizado o rollback da transação mantendo o comportamento de "tudo ou nada".&lt;/p&gt;

&lt;h2&gt;
  
  
  O problema de escrita dupla (dual write)
&lt;/h2&gt;

&lt;p&gt;Agora vamos ampliar nosso cenário, um microsserviço de Pedidos é responsável por manter os dados de pedidos e notificar os outros sistemas sobre pedidos feitos. Para se comunicar com os outros serviços será utilizado uma solução de &lt;a href="https://www.youtube.com/watch?v=R-zLsCDiTV8&amp;amp;list=PLqONbZa3fPe4Z6sg7RQGaHlYqUjsliKAH" rel="noopener noreferrer"&gt;mensageria&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwyz27ds1s9b2l8k5z9g2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwyz27ds1s9b2l8k5z9g2.png" alt="A aplicação salva pedidos no banco de dados e envia uma mensagem para um sistema de mensageria"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dessa forma, ao receber um novo pedido deve ser feita uma escrita na tabela &lt;code&gt;Orders&lt;/code&gt; no banco de dados e enviar uma mensagem &lt;code&gt;order.created&lt;/code&gt; para uma fila. Como manter a consistência entre o banco de dados e o sistema de mensageria? Se, após inserir um registro no banco de dados, o envio da mensagem para a fila não tiver sucesso as aplicações que esperam a mensagem ficam inconsistentes. Invertendo a lógica e enviando a mensagem antes de salvar no banco de dados produz um problema maior pois as aplicações seguintes podem receber mensagens de operações que nunca foram registradas na aplicação que é dona desse dado.&lt;/p&gt;

&lt;p&gt;Manter a consistência dos dados em sistemas distribuídos é um grande desafio e &lt;a href="https://developers.redhat.com/articles/2021/09/21/distributed-transaction-patterns-microservices-compared" rel="noopener noreferrer"&gt;diversas abordagens tem diferentes trade-offs&lt;/a&gt;. Comumente, é aceitável garantir que o sistema esteja consistente em algum ponto no futuro. Essa abordagem é chamada de &lt;strong&gt;Consistência Posterior&lt;/strong&gt; (eventual consistency).&lt;/p&gt;

&lt;h2&gt;
  
  
  Outbox Pattern
&lt;/h2&gt;

&lt;p&gt;A ideia do outbox pattern é usar o banco de dados para garantir que a mensagem será enviada. As mensagens que serão enviadas para o sistema de mensageria devem ser armazenadas em uma tabela junto do status de envio. Ao inserir um registro da tabela &lt;code&gt;Orders&lt;/code&gt; também deve-se inserir a mensagem, tudo dentro de uma transação.&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="k"&gt;BEGIN&lt;/span&gt; &lt;span class="n"&gt;TRANSACTION&lt;/span&gt;
  &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;OutboxMessages&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;status&lt;/span&gt;&lt;span class="p"&gt;)...&lt;/span&gt;
&lt;span class="k"&gt;COMMIT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa tabela &lt;code&gt;OutboxMessages&lt;/code&gt; deve ser verificada periodicamente em busca de mensagens com o status pendente para que sejam enviadas para a fila. Dessa forma tem-se a garantia que a mensagem será enviada &lt;strong&gt;ao menos uma vez&lt;/strong&gt;. Por que ao menos uma vez? Porque o processo de envio da mensagem e a atualização do status de envio do registro do banco é uma escrita dupla e está sujeita a falhas. A operação de atualização do status como enviado pode falhar e portanto durante a próxima busca de mensagens pendentes essa mensagem seria enviada novamente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idempotência
&lt;/h2&gt;

&lt;p&gt;Considerando o problema anterior é possível que uma mesma mensagem seja enviada repetidamente para a fila. Assim, é necessário que os sistemas que processam essas mensagens sejam idempotentes, ou seja, a execução de operações repetidas não podem afetar o resultado da primeira operação. Algumas operações são idempotentes por natureza, como atribuir um determinado valor para um campo de um registro de uma tabela. Se executada múltiplas vezes o resultado final é o mesmo de se executar apenas uma vez. Em outras situações não garantir a idempotência pode ser catastrófico. Expandindo nosso exemplo, considere uma aplicação que recebe mensagens de pedidos realizados e decrementa o estoque dos produtos vendidos. Se a mensagem do pedido for duplicada, o pedido abateria do estoque o dobro de produtos comprados.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmyk48c7tptbiq0l6l4py.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmyk48c7tptbiq0l6l4py.png" alt="O serviço de Produtos processa a mensagem de pedidos realizados e atualiza no seu banco de dados"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Uma maneira de implementar idempotência para esses casos é utilizar novamente o banco de dados. A aplicação que consome as mensagens deve possuir uma tabela para armazenar o id das mensagens processadas. Ao executar a operação no banco de dados, no nosso exemplo um &lt;code&gt;UPDATE&lt;/code&gt; na tabela &lt;code&gt;Products&lt;/code&gt;, deve-se inserir o id da mensagem na tabela de mensagens processadas. As duas operações devem ser realizadas dentro de uma transação pois se a restrição de chave primária da tabela &lt;code&gt;ProccessedMessages&lt;/code&gt; retornar erro (ou seja, a mensagem já foi processada) a atualização do produto deve ser desfeita.&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="k"&gt;BEGIN&lt;/span&gt; &lt;span class="n"&gt;TRANSACTION&lt;/span&gt;
  &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;Produtcs&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;ProccessedMessages&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="k"&gt;COMMIT&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Usando C# existem algumas opções de bibliotecas que implementam o padrão Outbox como &lt;a href="https://cap.dotnetcore.xyz/" rel="noopener noreferrer"&gt;CAP&lt;/a&gt; e &lt;a href="https://masstransit.io/" rel="noopener noreferrer"&gt;Mass Transit&lt;/a&gt;. Para idempotência eu criei o &lt;a href="https://github.com/rafaelpadovezi/Ziggurat" rel="noopener noreferrer"&gt;Ziggurat&lt;/a&gt; que implementa a solução descrita nesse texto. É possível ver um exemplo de implementação de CAP com Ziggurat &lt;a href="https://dev.to/luizhlelis/data-consistency-outbox-pattern-and-idempotency-in-a-microservice-architecture-32dl"&gt;aqui&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;No python existe a biblioteca &lt;a href="https://github.com/juntossomosmais/django-outbox-pattern" rel="noopener noreferrer"&gt;django-outbox-pattern&lt;/a&gt; que implementa o padrão outbox e também garante idempotência nos consumidores.&lt;/p&gt;

</description>
      <category>outboxpattern</category>
      <category>consistência</category>
      <category>transaçãodistribuida</category>
      <category>eventualconsistency</category>
    </item>
    <item>
      <title>Validação de entrada de dados e respostas de erro no ASP.NET</title>
      <dc:creator>Rafael</dc:creator>
      <pubDate>Wed, 18 Aug 2021 20:51:12 +0000</pubDate>
      <link>https://forem.com/rafaelpadovezi/validacao-de-entrada-de-dados-e-respostas-de-erro-no-asp-net-1pff</link>
      <guid>https://forem.com/rafaelpadovezi/validacao-de-entrada-de-dados-e-respostas-de-erro-no-asp-net-1pff</guid>
      <description>&lt;p&gt;Validação dos dados de entrada é parte essencial no desenvolvimento de software. O framework ASP.&lt;span&gt;NET&lt;/span&gt; provê um conjunto de funcionalidades que auxiliam os desenvedores garantir que as APIs só processem dados que possuem valores que atendam as regras da aplicação. Nesse texto serão discutidas as formas de validação do ASP.&lt;span&gt;NET&lt;/span&gt; e o formato das mensagens de erro retornadas. Serão tratados erros de tipos inválidos e a validação dos modelos usando a funcionalidade de &lt;code&gt;DataAnnotations&lt;/code&gt; do ASP.&lt;span&gt;NET&lt;/span&gt;. Além disso, será mostrado como customizar o formato de resposta. Os exemplos de código foram desenvolvidos usando a versão 5 do ASP.&lt;span&gt;NET&lt;/span&gt; e estão disponíveis no &lt;a href="https://github.com/rafaelpadovezi/aspnet-validation-error-response" rel="noopener noreferrer"&gt;github&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Erros de Model Binding
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.microsoft.com/pt-br/aspnet/core/mvc/models/model-binding?view=aspnetcore-5.0#what-is-model-binding" rel="noopener noreferrer"&gt;Model binding&lt;/a&gt; é a funcionalidade do ASP.&lt;span&gt;NET&lt;/span&gt; que atua nas requisições HTTP convertendo os dados de entrada nas rotas em tipos .NET.&lt;/p&gt;

&lt;p&gt;A etapa de Model Binding é executada durante o pipelines de filtros. Mais especificamente, essa etapa é executada antes dos &lt;a href="https://docs.microsoft.com/pt-br/aspnet/core/mvc/controllers/filters?view=aspnetcore-5.0#action-filters" rel="noopener noreferrer"&gt;filtros de ação&lt;/a&gt; que por sua vez são executados antes e depois da execução dos métodos dos controllers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faw4hvurk87ohaxo3pjn0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faw4hvurk87ohaxo3pjn0.png" title="ASP.NET filter pipeline" alt="Pipelines de filtro do ASP.NET. A etapa de Model Binding é executada antes dos filtros de ação"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Considere o &lt;code&gt;Controller&lt;/code&gt; de exemplo:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[controller]"&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;ExampleController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ControllerBase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ActionResult&lt;/span&gt; &lt;span class="nf"&gt;Get&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="k"&gt;if&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="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&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;ExampleRequest&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;"Example1"&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;NotFound&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;Ao fazer uma requisição para essa rota o ASP.&lt;span&gt;NET&lt;/span&gt; vai examinar a requisição para encontrar o campo &lt;code&gt;id&lt;/code&gt;, que nesse caso é enviada como parâmetro. Em seguida, o valor encontrado será convertido para inteiro e o método &lt;code&gt;Get&lt;/code&gt; será executado. Mas o que acontece se o valor passado for um texto? O dado não é convertido e é preenchido com o valor padrão, que para inteiro é 0. Caso fosse um objeto, o valor padrão seria &lt;code&gt;null&lt;/code&gt; o que poderia causar um &lt;code&gt;NullReferenceException&lt;/code&gt; se não fosse feita nenhuma verificação.&lt;/p&gt;

&lt;h3&gt;
  
  
  ModelState
&lt;/h3&gt;

&lt;p&gt;A propriedade &lt;a href="https://docs.microsoft.com/pt-br/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.modelstate?view=aspnetcore-5.0" rel="noopener noreferrer"&gt;ModelState&lt;/a&gt; da classe &lt;code&gt;ControllerBase&lt;/code&gt; contém o estado do modelo e a validação de associação de modelo. Quando não é possível a conversão da entrada de dados o &lt;code&gt;ModelState&lt;/code&gt; é inválido. Logo, é possível verificar se os dados de entrada estão corretos em relação aos tipos esperados.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[controller]"&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;ExampleController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ControllerBase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ActionResult&lt;/span&gt; &lt;span class="nf"&gt;Get&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;ModelState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsValid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelState&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;id&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="k"&gt;return&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;ExampleRequest&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;"Example1"&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;NotFound&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;Dessa forma, se essa rota for chamada enviando o valor "texto" no campo &lt;code&gt;id&lt;/code&gt; recebemos o seguinte erro:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"The value 'texto' is not valid."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Isso previne que a aplicação processe requisições com dados inválidos. Mas se minha aplicação possui vários controllers eu preciso repetir essa verificação em todos?&lt;/p&gt;

&lt;h3&gt;
  
  
  [ApiController]
&lt;/h3&gt;

&lt;p&gt;O atributo do ASP.&lt;span&gt;NET&lt;/span&gt; &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-5.0#apicontroller-attribute" rel="noopener noreferrer"&gt;ApiControllerAttribute&lt;/a&gt; pode ser aplicado à Controllers e traz algumas funcionalidades. Entre elas, ele faz a validação automática dos dados de entrada e retorna um erro 400 de maneira similar à verificação do &lt;code&gt;ModelState&lt;/code&gt;. Alterando o Controller de exemplo:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[controller]"&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;ExampleController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ControllerBase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ActionResult&lt;/span&gt; &lt;span class="nf"&gt;Get&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="k"&gt;if&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="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&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;ExampleRequest&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;"Example1"&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;NotFound&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;E ao fazer a requisição com o valor inválido obtemos o mesmo erro quando usamos a verificação do &lt;code&gt;ModelState&lt;/code&gt;. Isso ocorre porque o filtro &lt;code&gt;ModelStateInvalidFilter&lt;/code&gt; é adicionado a todos os Controllers que são anotados com o &lt;code&gt;ApiControllerAttribute&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Além da validação do &lt;code&gt;ModelState&lt;/code&gt;, o &lt;code&gt;ApiControllerAttribute&lt;/code&gt; traz outras informações de erro no seu resultado:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://tools.ietf.org/html/rfc7231#section-6.5.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"One or more validation errors occurred."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"traceId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00-a89db4d9a02dfc479c4d50b401f60fb5-28ba31ad1392154c-00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"The value 'texto' is not valid."&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Por padrão o atributo tem como resposta o formato acima contendo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;type&lt;/strong&gt;: o link da RFC em que determina os tipos de resposta HTTP, especificamente para a sessão do erro 400;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;status&lt;/strong&gt;: o código do status de erro;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;traceId&lt;/strong&gt;: o traceId da requisição. Por padrão, o ASP.NET 5 utiliza o formato definido &lt;a href="https://www.w3.org/TR/trace-context/" rel="noopener noreferrer"&gt;pela recomendação da W3C&lt;/a&gt;. Você pode encontrar mais informações sobre o traceId e o trace context &lt;a href="https://dev.to/luizhlelis/using-w3c-trace-context-standard-in-distributed-tracing-3743"&gt;aqui&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;errors&lt;/strong&gt;: uma lista de erros contendo o erro de validação do modelo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;É possível também decorar o assembly com o &lt;code&gt;ApiControllerAttribute&lt;/code&gt;. Isso pode ser feito decorando a declaração do namespace que contém a classe &lt;code&gt;Startup&lt;/code&gt;. Dessa forma, o comportamento do &lt;code&gt;ApiControllerAttribute&lt;/code&gt; será aplicado a todos os controllers do assembly &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ApiController&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;WebApiSample&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;Startup&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;h2&gt;
  
  
  Validação do modelo
&lt;/h2&gt;

&lt;p&gt;A validação dos tipos de dados é importante mas normalmente queremos aplicar outras validações ao nossos dados de entrada. Por exemplo, podemos marcar campos como obrigatórios, tamanho mínimo ou máximo e regras mais complexas. É importante garantir que a nossa aplicação só vai processar dados válidos. Isso também evita que o código da aplicação tenha uma quantidade de &lt;code&gt;if&lt;/code&gt;s e &lt;code&gt;else&lt;/code&gt;s que acabam poluindo o código. Vejam o exemplo abaixo:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ActionResult&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;ExampleRequest&lt;/span&gt; &lt;span class="n"&gt;example&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="nf"&gt;Ok&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;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleRequest&lt;/span&gt;
&lt;span class="p"&gt;{&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;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;O exemplo utiliza o atributo &lt;code&gt;Required&lt;/code&gt; pressente no namespace &lt;a href="https://docs.microsoft.com/pt-br/aspnet/core/mvc/models/validation?view=aspnetcore-5.0#validation-attributes" rel="noopener noreferrer"&gt;System.ComponentModel.DataAnnotations&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Realizando uma requisição para a nova rota de POST com o body vazio obtemos o erro:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://tools.ietf.org/html/rfc7231#section-6.5.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"One or more validation errors occurred."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"traceId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00-421e7740cdb1394aba958b549d319bc2-ffc80b3fe2883349-00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"The Name field is required."&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Existem vários outros atributos (a lista completa pode ser vista &lt;a href="https://docs.microsoft.com/pt-br/dotnet/api/system.componentmodel.dataannotations?view=net-5.0" rel="noopener noreferrer"&gt;aqui&lt;/a&gt;) e também é possível estender essa funcionalidade criando atributos customizados herdando a classe &lt;code&gt;ValidationAttribute&lt;/code&gt; como apresentado no exemplo:&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;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleRequest&lt;/span&gt;
&lt;span class="p"&gt;{&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;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&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="nf"&gt;StringLength&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="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Description&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="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="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;SomeValue&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;EmailAddress&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;Email&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;IsEven&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;EvenNumber&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;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IsEvenAttribute&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ValidationAttribute&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;IsEvenAttribute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Value is not an even number"&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;override&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;IsValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="k"&gt;value&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;intValue&lt;/span&gt; &lt;span class="p"&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;ToInt32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&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;intValue&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;O mesmo efeito pode ser obtido utilizando o &lt;a href="https://docs.fluentvalidation.net/en/latest/index.html" rel="noopener noreferrer"&gt;FluentValidation&lt;/a&gt; configurando a sua &lt;a href="https://docs.fluentvalidation.net/en/latest/aspnet.html" rel="noopener noreferrer"&gt;integração com o ASP.NET&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customização da resposta de erro
&lt;/h2&gt;

&lt;p&gt;Para alguns casos a resposta de erro padrão que o &lt;code&gt;ApiControllerAttribute&lt;/code&gt; envia pode ser indequada para a aplicação. Por exemplo, os campos &lt;code&gt;status&lt;/code&gt; e &lt;code&gt;type&lt;/code&gt; são redundantes considerando que o código da resposta HTTP já é retornado na requisição. Além disso, caso a aplicação retorne outros tipos de erros 400 pode ser necessário incluir novos campos na resposta.&lt;/p&gt;

&lt;p&gt;Para isso o ASP.NET possui uma &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/web-api/handle-errors?view=aspnetcore-5.0#validation-failure-error-response" rel="noopener noreferrer"&gt;funcionalidade&lt;/a&gt; que permite alterar o formato da resposta. Deve-se utilizar a classe &lt;code&gt;ApiBehaviorOptions&lt;/code&gt; para alterar o comportamento de todos os Controllers anotados com o &lt;code&gt;ApiControllerAttribute&lt;/code&gt;. Essa configuração deve ser feita no método &lt;code&gt;ConfigureServices&lt;/code&gt; da classe &lt;code&gt;Startup&lt;/code&gt; da aplicação. Deve ser chamado o método &lt;code&gt;ConfigureApiBehaviorOptions&lt;/code&gt; preenchendo a propriedade &lt;code&gt;InvalidModelStateResponseFactory&lt;/code&gt; com a customização da resposta. &lt;/p&gt;

&lt;p&gt;Segue o exemplo abaixo:&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;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddControllers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureApiBehaviorOptions&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="n"&gt;InvalidModelStateResponseFactory&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;=&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;response&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;Error&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;string&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&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;"VALIDATION_ERRORS"&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&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;ModelState&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;Error&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;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errors&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;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;ErrorMessage&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToArray&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="nf"&gt;BadRequestObjectResult&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="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 código acima simplifica o retorno da API, trazendo apenas a lista de erros e um novo campo para indicar que o motivo do erro é de validação dos dados. Fazendo novamente a requisição problemática recebemos o erro:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"The Name field is required."&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VALIDATION_ERRORS"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;É importante notar que apenas erros de validação, ou seja, que o &lt;code&gt;ModelState&lt;/code&gt; é ínválido, são afetados por essa customização.&lt;/p&gt;

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

&lt;p&gt;O ASP.NET possui funcionalidades para ajudar os desenvolvedores criarem APIs mais robustas aplicando validação de dados de entrada. Além disso, existem ótimas bibliotecas como o Fluent Validation que permite mais liberdade para criação de validadores de modelos mais inteligentes. O uso do atributo &lt;code&gt;ApiController&lt;/code&gt; do ASP.&lt;span&gt;NET&lt;/span&gt; incrementa as APIs adicionando funcionalidades como a resposta 400 automática para erros de validação padrão. No entanto, se desejado, é possível customizar o resultado de forma simples usando o &lt;code&gt;InvalidModelStateResponseFactory&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Obrigado por ter chegado até aqui e espero que tenha gostado. Dúvidas, sugestões ou encontrou algum erro? Por favor, deixe seu comentário.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/pt-br/aspnet/core/mvc/controllers/filters?view=aspnetcore-5.0" rel="noopener noreferrer"&gt;Filtros no ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/pt-br/aspnet/core/mvc/models/model-binding?view=aspnetcore-5.0" rel="noopener noreferrer"&gt;Model binding no ASPNET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-5.0" rel="noopener noreferrer"&gt;Criar APIs Web com o ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/pt-br/aspnet/core/mvc/models/validation?view=aspnetcore-5.0" rel="noopener noreferrer"&gt;Validação de modelo no ASP.NET Core MVC e Razor páginas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-5.0&amp;amp;tabs=visual-studio" rel="noopener noreferrer"&gt;Tutorial: criar uma API Web com o ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/pt-br/aspnet/core/web-api/handle-errors?view=aspnetcore-5.0" rel="noopener noreferrer"&gt;Handle errors in ASP.NET Core web APIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dotnet/aspnetcore/blob/52eff90fbcfca39b7eb58baad597df6a99a542b0/src/Mvc/Mvc.Core/src/Infrastructure/ModelStateInvalidFilter.cs" rel="noopener noreferrer"&gt;ModelStateInvalidFilter.cs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-5.0#attribute-on-an-assembly" rel="noopener noreferrer"&gt;Attribute on an assembly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>csharp</category>
      <category>validation</category>
      <category>dataannotations</category>
      <category>aspnet</category>
    </item>
    <item>
      <title>SaveChanges e controle transacional no EF Core</title>
      <dc:creator>Rafael</dc:creator>
      <pubDate>Mon, 01 Mar 2021 23:29:02 +0000</pubDate>
      <link>https://forem.com/rafaelpadovezi/savechanges-e-controle-transacional-no-ef-core-3e2h</link>
      <guid>https://forem.com/rafaelpadovezi/savechanges-e-controle-transacional-no-ef-core-3e2h</guid>
      <description>&lt;p&gt;O método &lt;code&gt;SaveChanges&lt;/code&gt; é responsável por salvar todas as alterações no banco de dados quando se está trabalhando com o Entity Framework Core. Ao executar esse método, por padrão, estamos executando as operações dentro de uma transação. A &lt;a href="https://docs.microsoft.com/pt-br/ef/core/saving/transactions" rel="noopener noreferrer"&gt;documentação&lt;/a&gt; diz:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Por padrão, se o provedor de banco de dados oferecer suporte a transações, todas as alterações em uma única chamada para SaveChanges serão aplicadas em uma transação. Se qualquer uma das alterações falhar, a transação é revertida e nenhuma das alterações será aplicada ao banco de dados.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Será tratado aqui o funcionamento do método &lt;code&gt;SaveChanges&lt;/code&gt; analisando os logs do EF Core e as consultas geradas. O foco da análise será o controle da transação. A versão da biblioteca nos testes é a 5 e o banco de dados utilizado na análise é o SQL Server da Microsoft. Os resultados encontrados podem ser diferentes em outras versões e/ou utilizando outro bancos de dados. O código criado para os testes pode ser visto &lt;a href="https://github.com/rafaelpadovezi/efcore-tests" rel="noopener noreferrer"&gt;aqui&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configurando o &lt;code&gt;DbContext&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;O contexto do banco de dados deve ser configurado para obter as informações necessárias para o entendimento do EF Core.&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;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;OnConfiguring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DbContextOptionsBuilder&lt;/span&gt; &lt;span class="n"&gt;optionsBuilder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;optionsBuilder&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSqlServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Data Source=localhost,1433;Initial Catalog=Timesheet;User Id=sa;Password=Password1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogTo&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;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O método &lt;code&gt;LogTo&lt;/code&gt;, usado na configuração, é a nova maneira de acessar os logs do EF Core. A funcionalidade &lt;a href="https://docs.microsoft.com/en-us/ef/core/logging-events-diagnostics/simple-logging" rel="noopener noreferrer"&gt;Simple Logging&lt;/a&gt; foi apresentada na versão 5 do ORM e permite direcionar as entradas de log para o tipo de saída desejada. No exemplo, os dados são direcionados para janela de Debug do Visual Studio usando o método &lt;code&gt;Debug.Writeline&lt;/code&gt;. Nas versões antigas é possível se obter os logs usando o método &lt;code&gt;UseLoggerFactory&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adicionando dados com o &lt;code&gt;SaveChanges&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Para verificar o funcionamento do &lt;code&gt;SaveChanges&lt;/code&gt; foi escrito um teste simples adicionando dois objetos no banco de dados. No banco serão armazenadas duas linhas, uma em cada tabela.&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;employee&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;Employee&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;"John Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Entries&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;TimeEntry&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;new&lt;/span&gt; &lt;span class="n"&gt;TimeEntry&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Start&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;FromHours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;End&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;FromHours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;12&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="n"&gt;context&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;employee&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="nf"&gt;SaveChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;É possível ver no log gerado que os primeiros passos do EF Core na execução do método &lt;code&gt;SaveChanges&lt;/code&gt; é a verificação das mudanças das entidades do contexto. Logo em seguida, a conexão com o banco é aberta.&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="n"&gt;dbug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CoreEventId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SaveChangesStarting&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10004&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="n"&gt;SaveChanges&lt;/span&gt; &lt;span class="n"&gt;starting&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="s1"&gt;'TestContext'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;dbug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CoreEventId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DetectChangesStarting&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10800&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChangeTracking&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="n"&gt;DetectChanges&lt;/span&gt; &lt;span class="n"&gt;starting&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="s1"&gt;'TestContext'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;dbug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CoreEventId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DetectChangesCompleted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10801&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChangeTracking&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="n"&gt;DetectChanges&lt;/span&gt; &lt;span class="n"&gt;completed&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="s1"&gt;'TestContext'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;dbug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RelationalEventId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectionOpening&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="n"&gt;Opening&lt;/span&gt; &lt;span class="k"&gt;connection&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="k"&gt;database&lt;/span&gt; &lt;span class="s1"&gt;'Timesheet'&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="s1"&gt;'localhost,1433'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;dbug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RelationalEventId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectionOpened&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20001&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="n"&gt;Opened&lt;/span&gt; &lt;span class="k"&gt;connection&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="k"&gt;database&lt;/span&gt; &lt;span class="s1"&gt;'Timesheet'&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="s1"&gt;'localhost,1433'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As entradas de log seguintes são relacionadas à transação. No primeiro momento, a transação é iniciada com o nível de isolamento não especificado. A próxima mensagem mostra que a transação foi inicializada com o nível &lt;a href="https://docs.microsoft.com/pt-br/sql/relational-databases/sql-server-transaction-locking-and-row-versioning-guide?view=sql-server-ver15#database-engine-isolation-levels" rel="noopener noreferrer"&gt;&lt;code&gt;ReadCommitted&lt;/code&gt;&lt;/a&gt;, que é o padrão do SQL Server.&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="n"&gt;dbug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RelationalEventId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransactionStarting&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20209&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="n"&gt;Beginning&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="k"&gt;isolation&lt;/span&gt; &lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="s1"&gt;'Unspecified'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;dbug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RelationalEventId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransactionStarted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20200&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="n"&gt;Began&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="k"&gt;isolation&lt;/span&gt; &lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="s1"&gt;'ReadCommitted'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Em seguida são executados dois comandos &lt;code&gt;INSERT&lt;/code&gt;, um para cada entidade. Como o &lt;code&gt;Id&lt;/code&gt; das entidades são gerados pelo banco de dados, pode-se ver que, para cada &lt;code&gt;INSERT&lt;/code&gt;, também foi realizado um &lt;code&gt;SELECT&lt;/code&gt; para que o EF Core obtenha o Id da entidade inserida. Alguns logs de criação dos comandos e detecção de alterações de chave estrangeira foram omitidos.&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="n"&gt;dbug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RelationalEventId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandExecuting&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20100&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="n"&gt;Executing&lt;/span&gt; &lt;span class="n"&gt;DbCommand&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;Parameters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;p0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'John Doe'&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;Size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="n"&gt;CommandType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Text'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CommandTimeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'30'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;NOCOUNT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Employees&lt;/span&gt;&lt;span class="p"&gt;]&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="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;p0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;SELECT&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="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Employees&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="o"&gt;@@&lt;/span&gt;&lt;span class="n"&gt;ROWCOUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope_identity&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;dbug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RelationalEventId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandExecuting&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20100&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="n"&gt;Executing&lt;/span&gt; &lt;span class="n"&gt;DbCommand&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;Parameters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'3'&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;Nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'12:00:00'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;p3&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'08:00:00'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;CommandType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Text'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CommandTimeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'30'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;NOCOUNT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TimeEntries&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;EmployeeId&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;End&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;p3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;SELECT&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="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TimeEntries&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="o"&gt;@@&lt;/span&gt;&lt;span class="n"&gt;ROWCOUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope_identity&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por último, é feito o &lt;code&gt;commit&lt;/code&gt; da transação.&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="n"&gt;dbug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RelationalEventId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransactionCommitting&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20210&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="n"&gt;Committing&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;dbug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RelationalEventId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransactionCommitted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20202&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&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;Committed&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Controlando a transação
&lt;/h2&gt;

&lt;p&gt;Para a maioria dos casos, o uso do &lt;code&gt;SaveChanges&lt;/code&gt; é o suficiente para garantir a consistência dos dados. Porém, quando necessário, é possível controlar as transações manualmente.&lt;/p&gt;

&lt;p&gt;O segundo teste utiliza o mesmo exemplo do primeiro. Dessa vez o método &lt;code&gt;SaveChanges&lt;/code&gt; está sendo executado dentro de uma transação iniciada com  método &lt;code&gt;BeginTransaction&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;employee&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;Employee&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;"John Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;Entries&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;TimeEntry&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;new&lt;/span&gt; &lt;span class="n"&gt;TimeEntry&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="n"&gt;Start&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;FromHours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                  &lt;span class="n"&gt;End&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;FromHours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;12&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="n"&gt;context&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;employee&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;transaction&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;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BeginTransaction&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="nf"&gt;SaveChanges&lt;/span&gt;&lt;span class="p"&gt;();&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;Commit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As primeiras entradas do log são de abertura da transação. Como o primeiro teste, a transação é iniciada com o nível de isolamento não especificado e criada com o nível &lt;code&gt;ReadCommited&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="n"&gt;dbug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RelationalEventId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransactionStarting&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20209&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="n"&gt;Beginning&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="k"&gt;isolation&lt;/span&gt; &lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="s1"&gt;'Unspecified'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;dbug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RelationalEventId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransactionStarted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20200&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="n"&gt;Began&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="k"&gt;isolation&lt;/span&gt; &lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="s1"&gt;'ReadCommitted'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ao executar o método &lt;code&gt;SaveChanges&lt;/code&gt;, o EF Core cria um &lt;strong&gt;savepoint&lt;/strong&gt; da transação ao invés de abrir uma transação aninhada. Os logs de detecção de alterações nas entidades foram omitidos.&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="n"&gt;dbug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CoreEventId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SaveChangesStarting&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10004&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="n"&gt;SaveChanges&lt;/span&gt; &lt;span class="n"&gt;starting&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="s1"&gt;'TestContext'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;dbug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RelationalEventId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatingTransactionSavepoint&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20212&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="n"&gt;Creating&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="n"&gt;savepoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;dbug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RelationalEventId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedTransactionSavepoint&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20213&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="n"&gt;Created&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="n"&gt;savepoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Em seguida se realiza a execução dos comandos da mesma forma do primero teste.&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="n"&gt;dbug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RelationalEventId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandExecuting&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20100&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="n"&gt;Executing&lt;/span&gt; &lt;span class="n"&gt;DbCommand&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;Parameters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;p0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'John Doe'&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;Size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="n"&gt;CommandType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Text'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CommandTimeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'30'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;NOCOUNT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Employees&lt;/span&gt;&lt;span class="p"&gt;]&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="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;p0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;SELECT&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="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Employees&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="o"&gt;@@&lt;/span&gt;&lt;span class="n"&gt;ROWCOUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope_identity&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;dbug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RelationalEventId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandExecuting&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20100&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="n"&gt;Executing&lt;/span&gt; &lt;span class="n"&gt;DbCommand&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;Parameters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'5'&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;Nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'12:00:00'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;p3&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'08:00:00'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;CommandType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Text'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CommandTimeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'30'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;NOCOUNT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TimeEntries&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;EmployeeId&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;End&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;p3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;SELECT&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="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TimeEntries&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="o"&gt;@@&lt;/span&gt;&lt;span class="n"&gt;ROWCOUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope_identity&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por fim, ao executar o método &lt;code&gt;transaction.Commit()&lt;/code&gt;, as alterações são confirmadas.&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="n"&gt;dbug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RelationalEventId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransactionCommitting&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20210&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="n"&gt;Committing&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;dbug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RelationalEventId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransactionCommitted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20202&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&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;Committed&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Diferença entre transações aninhadas e savepoints no SQL Server
&lt;/h2&gt;

&lt;p&gt;No SQL Server, o uso de transações aninhadas tem um comportamento que pode confundir os desenvolvedores. O &lt;code&gt;ROLLBACK&lt;/code&gt; reverte todas as transações abertas da sessão. Não importa se existam transações aninhadas, o &lt;code&gt;ROLLBACK&lt;/code&gt; vai desfazer todas as operações realizadas desde o primeiro &lt;code&gt;BEGIN TRANSACTION&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Já o &lt;code&gt;SAVE TRANSACTION&lt;/code&gt; coloca uma tag no ponto desejado da transação de forma a ser possível executar o &lt;code&gt;ROLLBACK&lt;/code&gt; e reverter as mudanças até esse ponto. É possível criar vários savepoints em uma mesma transação. É importante lembrar que o &lt;code&gt;SAVE TRANSACTION&lt;/code&gt; não abre uma nova transação.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7ot0fu3fh06shcbw2tp9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7ot0fu3fh06shcbw2tp9.png" title="Diferença entre transações aninhadas e savepoints" alt="diferença entre transações aninhadas e savepoints"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Savepoints no EF Core
&lt;/h2&gt;

&lt;p&gt;O EF Core também permite a criação manual de savepoints usando o método &lt;code&gt;CreateSavepoint&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;var&lt;/span&gt; &lt;span class="n"&gt;transaction&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;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BeginTransaction&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="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;Employee&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;"John Doe"&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="nf"&gt;SaveChanges&lt;/span&gt;&lt;span class="p"&gt;();&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;CreateSavepoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A"&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="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;Employee&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;"Jane Doe"&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="nf"&gt;SaveChanges&lt;/span&gt;&lt;span class="p"&gt;();&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;Commit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Versões anteriores
&lt;/h2&gt;

&lt;p&gt;A funcionalidade de savepoints foi incluída no EF Core 5. Nas versões 3.1 e 2.1 o EF Core, além de não permitir a criação de savepoints de forma manual, também não executa um &lt;code&gt;SAVE TRANSACTION&lt;/code&gt; durante a execução do método &lt;code&gt;SaveChanges&lt;/code&gt;. Ou seja, caso já exista uma transação aberta, como no segundo teste, o &lt;code&gt;SaveChanges&lt;/code&gt; apenas executa os comandos de alteração da base de dados.&lt;/p&gt;

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

&lt;p&gt;O método &lt;code&gt;SaveChanges&lt;/code&gt; facilita a vida do desenvolvedor ao executar os comandos de alteração no banco de dados dentro de uma transação. Para os outros casos, em que se faz a abertura de uma transação manualmente, o &lt;code&gt;SaveChanges&lt;/code&gt; apenas cria um savepoint dentro da transação.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Simple Logging - &lt;a href="https://docs.microsoft.com/pt-br/ef/core/logging-events-diagnostics/simple-logging" rel="noopener noreferrer"&gt;https://docs.microsoft.com/pt-br/ef/core/logging-events-diagnostics/simple-logging&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Usando transações - &lt;a href="https://docs.microsoft.com/pt-br/ef/core/saving/transactions" rel="noopener noreferrer"&gt;https://docs.microsoft.com/pt-br/ef/core/saving/transactions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Guia de Controle de Versão de Linha e Bloqueio de Transações - &lt;a href="https://docs.microsoft.com/pt-br/sql/relational-databases/sql-server-transaction-locking-and-row-versioning-guide?view=sql-server-ver15" rel="noopener noreferrer"&gt;https://docs.microsoft.com/pt-br/sql/relational-databases/sql-server-transaction-locking-and-row-versioning-guide?view=sql-server-ver15&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Nesting transactions and SAVE TRANSACTION command - &lt;a href="https://dba-presents.com/index.php/databases/sql-server/43-nesting-transactions-and-save-transaction-command" rel="noopener noreferrer"&gt;https://dba-presents.com/index.php/databases/sql-server/43-nesting-transactions-and-save-transaction-command&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SAVE TRANSACTION - &lt;a href="https://docs.microsoft.com/pt-br/sql/t-sql/language-elements/save-transaction-transact-sql?view=sql-server-ver15" rel="noopener noreferrer"&gt;https://docs.microsoft.com/pt-br/sql/t-sql/language-elements/save-transaction-transact-sql?view=sql-server-ver15&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dotnet/efcore/blob/release/5.0/src/EFCore.Relational/Update/Internal/BatchExecutor.cs#L89" rel="noopener noreferrer"&gt;https://github.com/dotnet/efcore/blob/release/5.0/src/EFCore.Relational/Update/Internal/BatchExecutor.cs#L89&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dotnet/efcore/blob/release/2.1/src/EFCore.Relational/Update/Internal/BatchExecutor.cs#L70" rel="noopener noreferrer"&gt;https://github.com/dotnet/efcore/blob/release/2.1/src/EFCore.Relational/Update/Internal/BatchExecutor.cs#L70&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>csharp</category>
      <category>efcore</category>
      <category>entityframeworkcore</category>
      <category>transactions</category>
    </item>
  </channel>
</rss>
