Recientemente en mi trabajo iniciamos el desarrollo de una aplicación donde varios desarrolladores íbamos a estar trabajando al mismo tiempo, cada quien en módulos diferentes. Y como suele pasar esto implica cambios constantes en la base de datos.

Si has trabajado con Entity Framework en equipo, probablemente ya te has topado con el problema de que el DbContext empieza a desincronizarse, migraciones que chocan, modelos que ya no representan la realidad de la base de datos y terminas perdiendo tiempo en cosas que no deberían ser problema.

Así que decidí buscar una alternativa más simple, más directa y que no presentara este problema. Ahí fue donde me encontré con Dapper.

¿Qué es Dapper?

Dapper es un micro ORM desarrollado por el equipo de Stack Overflow que diferencia de Entity Framework, aquí no hay magia pesada ni abstracciones complejas, escribes tu SQL y Dapper se encarga de mapear los resultados a tus objetos.

¿Por qué decidí usarlo?

En mi caso, lo que necesitaba era:

  • Evitar problemas de sincronización entre modelo y base de datos
  • Tener control total sobre las consultas
  • Algo rápido de implementar
  • Que funcionara bien trabajando en equipo

Aplicación de Control Gastos

Para probar Dapper decidí hacer una aplicación ultra sencilla para registrar gastos ya que necesitaba aprender a usarlo antes de implementarlo en un proyecto en mi trabajo.

Instalación

Nada complicado, como cualquier paquete de NuGet:

Install-Package Dapper

Y para SQL Server:

dotnet add package Microsoft.Data.SqlClient

Configuración básica

Yo normalmente manejo una clase base para obtener la conexión, algo así:

C#
public class BaseRepository
{
    private readonly IConfiguration _configuration;

    public BaseRepository(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    protected SqlConnection GetConnection()
    {
        return new SqlConnection(_configuration.GetConnectionString("DefaultConnection"));
    }
}

Esto me permite reutilizar la conexión en todos mis repositorios.

Modelo de ejemplo

C#
public class Usuario
{
    public int Id { get; set; }
    public string Nombre { get; set; }
    public string Email { get; set; }
}

SELECT

Aquí es donde Dapper brilla por su simplicidad.

C#
public Usuario[] GetUsuarios()
{
    using var con = GetConnection();

    return con.Query<Usuario>("SELECT * FROM Usuarios")
              .ToArray();
}

Con parámetros:

C#
public Usuario? GetUsuarioById(int id)
{
    using var con = GetConnection();

    return con.QueryFirstOrDefault<Usuario>(
        "SELECT * FROM Usuarios WHERE Id = @Id",
        new { Id = id });
}

INSERT

C#
public void InsertUsuario(Usuario usuario)
{
    using var con = GetConnection();

    con.Execute(
        "INSERT INTO Usuarios (Nombre, Email) VALUES (@Nombre, @Email)",
        usuario);
}

UPDATE

C#
public void UpdateUsuario(Usuario usuario)
{
    using var con = GetConnection();

    con.Execute(
        @"UPDATE Usuarios 
          SET Nombre = @Nombre, Email = @Email 
          WHERE Id = @Id",
        usuario);
}

DELETE

C#
public void DeleteUsuario(int id)
{
    using var con = GetConnection();

    con.Execute(
        "DELETE FROM Usuarios WHERE Id = @Id",
        new { Id = id });
}

Transacciones

Aquí es donde ya se pone interesante, porque puedes hacer exactamente lo que necesitas sin pelearte con el ORM.

En uno de mis repositorios, por ejemplo, manejo algo así:

  • Inserto una cuenta
  • Si tiene saldo inicial, inserto también un movimiento
  • Todo dentro de una transacción

Ejemplo simplificado:

C#
using var con = GetConnection();
con.Open();

using var trx = con.BeginTransaction();

try
{
    var cuentaId = con.ExecuteScalar<long>(
        @"INSERT INTO Cuentas (Nombre) 
          VALUES (@Nombre);
          SELECT SCOPE_IDENTITY();",
        new { Nombre = "Cuenta demo" },
        trx);

    con.Execute(
        @"INSERT INTO Movimientos (CuentaId, Importe)
          VALUES (@CuentaId, @Importe)",
        new { CuentaId = cuentaId, Importe = 100 },
        trx);

    trx.Commit();
}
catch
{
    trx.Rollback();
    throw;
}

Este tipo de control es justo lo que estaba buscando.

Ventajas

Después de usarlo en algunos proyectos en mi día a día, estas fueron las principales ventajas:

  • Menos fricción trabajando en equipo
  • No dependes de migraciones
  • Código más predecible
  • Mejor rendimiento
  • Debug mucho más sencillo

¿Reemplaza a Entity Framework?

No necesariamente, Entity Framework sigue siendo muy útil en muchos escenarios, sobre todo cuando:

  • No quieres escribir SQL
  • Tu modelo es muy estable
  • Necesitas rapidez para prototipar

Pero en escenarios como el mío (muchos cambios y varios desarrolladores), creo Dapper se siente mucho más cómodo.

Conclusión

En mi caso, Dapper no solo resolvió un problema que me representaría usar Entity Framework sino que me ayudó a mejorar el flujo de trabajo al no tener que sincronizar mi base de datos con el modelo. Aunque no descarto la idea de usar Entity Framework pero será en una etapa donde los cambios en la base de datos no sean tan recurrentes como ahora.