Dapper en C#: tutorial para conectar .NET a SQL Server
Aprende Dapper 2.1.86 con .NET 10 mediante consultas parametrizadas y un CRUD completo sobre SQL Server.
Actualizado el
Dapper es un micro ORM para .NET. Te permite ejecutar SQL y mapear el resultado a clases de C# con muy poco código.
Es más ligero que Entity Framework Core, pero también te exige escribir las consultas SQL.
Cuándo usar Dapper
Dapper es buena opción si quieres:
- Controlar el SQL que se ejecuta.
- Mantener una capa de acceso a datos simple.
- Evitar el peso de un ORM completo.
- Hacer consultas rápidas y directas.
Si prefieres trabajar con LINQ, seguimiento de cambios y migraciones, consulta el tutorial de Entity Framework Core.
Crear el proyecto
La forma más rápida de crear el proyecto es la CLI:
dotnet new console -n TutorialDapper -f net10.0
cd TutorialDapperSi prefieres usar Visual Studio, abre el asistente de nuevo proyecto y selecciona Aplicación de consola. Las capturas pueden mostrar una versión anterior de Visual Studio:

Escribe TutorialDapper como nombre del proyecto y de la solución:

Selecciona la plataforma de destino (.NET 10):

Instalar Dapper
En un proyecto .NET 10 instala estos paquetes:
dotnet add package Dapper --version 2.1.86
dotnet add package Microsoft.Data.SqlClientDapper 2.1.86 es compatible con net10.0. Microsoft.Data.SqlClient se instala sin fijar versión para usar la última estable del proveedor.
Microsoft.Data.SqlClient es el proveedor para SQL Server. Si usas MySQL, PostgreSQL u otra base de datos, instala su proveedor correspondiente.
Desde el administrador de paquetes NuGet, busca e instala Dapper:

Después instala el proveedor de base de datos, en este caso Microsoft.Data.SqlClient:

Para organizar el código, crea dos carpetas en el proyecto: Entities (para los modelos) y Services (para la lógica de acceso a datos):

Tabla de ejemplo
Usaremos una tabla Marks con notas de alumnos.
CREATE TABLE Marks (
Id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
Name NVARCHAR(50) NOT NULL,
Surname NVARCHAR(100) NOT NULL,
Nota DECIMAL(18, 2) NOT NULL
);Crea una base de datos llamada Notas en SQL Server:

Tras ejecutar el script SQL, la tabla Marks estará lista con algunos registros de ejemplo:

Crear el modelo
public class Mark
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Surname { get; set; } = string.Empty;
public decimal Nota { get; set; }
}Las propiedades deben coincidir con las columnas que devuelve la consulta, o al menos tener nombres compatibles.
Crear el servicio
using Dapper;
using Microsoft.Data.SqlClient;
public class MarkService
{
private readonly string _connectionString;
public MarkService(string connectionString)
{
_connectionString = connectionString;
}
private SqlConnection CreateConnection()
{
return new SqlConnection(_connectionString);
}
}No guardes usuarios y contraseñas directamente en el código. Usa configuración, variables de entorno o secretos del entorno donde despliegues.
Consultar registros con Dapper
public List<Mark> GetMarks()
{
const string sql = @"
SELECT Id, Name, Surname, Nota
FROM Marks
ORDER BY Surname";
using var db = CreateConnection();
return db.Query<Mark>(sql).ToList();
}Query<Mark> ejecuta el SQL y convierte cada fila en un objeto Mark.
Buscar por id
public Mark? GetMark(int id)
{
const string sql = @"
SELECT Id, Name, Surname, Nota
FROM Marks
WHERE Id = @id";
using var db = CreateConnection();
return db.QueryFirstOrDefault<Mark>(sql, new { id });
}El parámetro @id evita concatenar valores en el SQL y reduce el riesgo de inyección SQL. Dapper resuelve los parámetros a partir de las propiedades del objeto o de las de un objeto anónimo, como en este caso.
Insertar un registro
public int CreateMark(Mark mark)
{
const string sql = @"
INSERT INTO Marks (Name, Surname, Nota)
OUTPUT INSERTED.Id
VALUES (@Name, @Surname, @Nota);";
using var db = CreateConnection();
return db.QuerySingle<int>(sql, mark);
}OUTPUT INSERTED.Id devuelve el id generado por SQL Server. Los nombres @Name, @Surname y @Nota se resuelven a partir de las propiedades del objeto mark; nunca concatenes valores del usuario directamente en el SQL.
Actualizar un registro
public int UpdateMark(Mark mark)
{
const string sql = """
UPDATE Marks
SET Name = @Name,
Surname = @Surname,
Nota = @Nota
WHERE Id = @Id
""";
using var db = CreateConnection();
return db.Execute(sql, mark);
}Execute devuelve el número de filas afectadas, así que devolverlo permite al llamador comprobar si la actualización encontró el registro. Los parámetros @Name, @Surname, @Nota y @Id se toman de las propiedades del objeto mark.
Eliminar un registro
public int DeleteMark(int id)
{
const string sql = "DELETE FROM Marks WHERE Id = @id";
using var db = CreateConnection();
return db.Execute(sql, new { id });
}Igual que en UpdateMark, devolver el número de filas afectadas permite comprobar si se eliminó algún registro. Para operaciones que no devuelven filas, usa Execute.
Dapper también ofrece versiones asíncronas de estos métodos, como QueryAsync<T> y ExecuteAsync, para aplicaciones con operaciones de entrada/salida. Los ejemplos principales y la prueba del servicio se mantienen síncronos para no duplicar todo el código.
Métodos asíncronos alternativos
Si necesitas llamadas asíncronas, puedes añadir estos métodos a MarkService sin cambiar los síncronos:
public async Task<IEnumerable<Mark>> GetMarksAsync()
{
const string sql = @"
SELECT Id, Name, Surname, Nota
FROM Marks
ORDER BY Surname";
using var db = CreateConnection();
return await db.QueryAsync<Mark>(sql);
}
public async Task<int> DeleteMarkAsync(int id)
{
using var db = CreateConnection();
return await db.ExecuteAsync("DELETE FROM Marks WHERE Id = @id", new { id });
}Desde un método asíncrono, puedes llamarlos con await:
var marks = await service.GetMarksAsync();
var filasAfectadas = await service.DeleteMarkAsync(id);DeleteMarkAsync devuelve el número de filas afectadas. Dapper abre y cierra la conexión si estaba cerrada al ejecutar la operación; using var la desecha al terminar.
Probar el servicio
var connectionString =
Environment.GetEnvironmentVariable("SQLSERVER_CONNECTION_STRING")
?? throw new InvalidOperationException(
"Define la variable SQLSERVER_CONNECTION_STRING.");
var service = new MarkService(connectionString);
var id = service.CreateMark(new Mark
{
Name = "Juan",
Surname = "Pablo",
Nota = 8.00m
});
var mark = service.GetMark(id);
if (mark != null)
{
mark.Name = "Pedro";
service.UpdateMark(mark);
}
service.DeleteMark(id);Leer la cadena de conexión del entorno evita dejarla escrita en el código fuente.
Recomendaciones
- Usa siempre parámetros, nunca concatenes valores del usuario en el SQL.
- Usa
ExecuteparaINSERT,UPDATEyDELETEcuando no necesites devolver datos. - Mantén las consultas en servicios o repositorios para no mezclar SQL con la lógica de la aplicación.
- Guarda la cadena de conexión fuera del código fuente.
Preguntas frecuentes
¿Cómo ejecutar un procedimiento almacenado con Dapper?
Indica CommandType.StoredProcedure y pasa los parámetros de entrada como objeto. Este ejemplo usa MarkService y Mark; el nombre del procedimiento y su firma son ilustrativos:
public List<Mark> GetMarksBySurname(string surname)
{
const string procedure = "GetMarksBySurname"; // Ejemplo ilustrativo
var parameters = new { Surname = surname };
using var db = CreateConnection();
return db.Query<Mark>(
procedure,
parameters,
commandType: System.Data.CommandType.StoredProcedure
).ToList();
}Query<Mark> recupera las filas devueltas y las mapea a objetos Mark. Si el procedimiento no devuelve un conjunto de resultados y solo necesitas ejecutarlo, usa Execute; este método no materializa filas.
Código fuente complementario del tutorial original: Tutorial Dapper. Es un repositorio complementario, así que sus dependencias no tienen por qué estar actualizadas a las versiones que se muestran en este artículo.