Aumentando a performance do Dapper
Tags: dotnet, dotnetcore, database, performance
Published: 2023-02-28 17:24:30

Muitas vezes começamos a escrever códigos e deixamos passar detalhes que podem afetar a performance do nosso sistema. Acreditamos que o problema de performance é maior do que realmente é e, consequentemente, acabamos seguindo caminhos mais complicados.

Vamos considerar algo simples. Você está usando o Dapper, um Micro ORM, que é conhecido por sua rapidez em relação a outros ORMs. Mas será que você está usando o Dapper corretamente e aproveitando ao máximo essa performance?

Você costuma identificar cuidadosamente os parâmetros que passa para suas consultas?

Vamos criar uma classe Cliente e uma tabela para armazenar os clientes:

```csharp
class Cliente
{
    public string Nome { get; set; }
    public int Id { get; set; }
    public string CPF { get; set; }
}
```

```sql
CREATE TABLE CLIENTE (
    ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
    NOME VARCHAR(200) NOT NULL,
    CPF CHAR(11) NOT NULL
)
```

Depois de criar a tabela, vamos inserir 1000 CPFs (falsos) em um loop e criar um índice para CPF:

```sql
CREATE UNIQUE NONCLUSTERED INDEX IX_CLIENTE_CPF   
    ON Cliente (Cpf)
    INCLUDE (Nome)
```

Agora, vamos escrever um código simples para obter um cliente através do CPF, que é um código comum em muitas aplicações:

```csharp
IConfigurationBuilder builder = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json", false, true);
        IConfigurationRoot config = builder.Build();

        SqlConnection sqlConnection =
                new SqlConnection(
                    config.GetConnectionString("ecommerceConnectionString")
                    );
        sqlConnection.Open();

        var cliente = sqlConnection.QueryFirstOrDefault<Cliente>(
            "select * from Cliente where CPF = @cpf",
            new { cpf = "00000000090" });

        sqlConnection.Close();
```

O que esperamos é que a query executada no banco de dados seja a mais performática possível uma vez que temos um índice pela coluna CPF. Vamos provar que o índice existe e que estamos com o melhor plano de execução possível.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dms9cru54veuocihf66v.png)

A query foi executada realizando um Index Seek no índice por CPF que criamos anteriormente.

Agora vamos executar pela aplicação a "mesma" query.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aaxuhfos3vkvhn1g3x6f.png)

Executando a query direto no SQL e obtendo o plano de execução.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/86lhtq6lwq10ghg4573y.png)

O resultado não foi o esperado. Ao invés de executar um Index Seek, a query gerada pelo Dapper realizou um Index Scan. Isso ocorreu porque o Dapper interpretou o parâmetro da consulta como NVARCHAR por padrão, o que fez com que o SQL Server convertesse o parâmetro e alterasse o plano de execução, afetando a performance da consulta.

Para evitar esse problema e melhorar a performance da consulta, é necessário informar o tipo do parâmetro de string corretamente. No Dapper, podemos usar a classe DbString para especificar o tipo de parâmetro.

```csharp
var cliente = sqlConnection.QueryFirstOrDefault<Cliente>(
            "select * from Cliente where CPF = @cpf",
            new
            {
                cpf = new DbString
                {
                    IsAnsi = true,
                    IsFixedLength = true,
                    Length = 11,
                    Value = "00000000090"
                }
            });
```

Ao definir as propriedades de DbString, o Dapper gera uma consulta com o tipo de parâmetro correto, permitindo que o SQL Server faça um Index Seek. Note que é importante especificar a propriedade corretamente, de acordo com o tipo de dado da coluna no banco de dados.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5d7xbje9u9oeo5g1duj7.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cej2f3gcmpmice6w80a0.png)

O DbString é uma classe do Dapper que permite especificar o tipo exato de um parâmetro que será passado para o banco de dados. Assim o Dapper gera uma instrução que utiliza o tipo de dado correto para o parâmetro. Com isso, evitamos conversões desnecessárias e ajudamos a melhorar a performance das consultas.

O construtor do DbString permite definir várias propriedades, como IsAnsi, IsFixedLength, Length e Value. A propriedade IsAnsi indica se o tipo de dado é ANSI ou Unicode. A propriedade IsFixedLength indica se o tipo de dado é de comprimento fixo ou variável. A propriedade Length indica o tamanho máximo do campo, e a propriedade Value é o valor do parâmetro.

Parâmetros de acordo com o tipo:

![Parametros](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ffhbsxp9wmosqt8x597i.png)

Esse pequeno detalhe muitas vezes passa despercebido durante as nossas implementações e pode afetar significativamente a performance de nossas aplicações, impactando diretamente na experiência dos usuários. Por isso, é importante dedicar tempo para otimizar o código, utilizando boas práticas e ferramentas que nos ajudem a identificar e corrigir possíveis problemas.

Até a próxima!