Cómo implementar el patrón repositorio con ADO.NET y C#

En este ejemplo me voy a centrar en usar el patrón repositorio con ADO.NET, por ser la forma más básica con la que accedemos a nuestras bases de datos. Es un patrón repositorio «simple» porque no vamos a trabajar en una clase con relaciones con otras clases con el fin de disminuir la complejidad al mínimo.

En el artículo anterior hablamos sobre qué es el patrón repositorio y cuáles son las ventajas que trae consigo, que te recomiendo leer antes de continuar.


La imagen tiene un atributo ALT vacío; su nombre de archivo es base-de-datos-original-720p-min.jpg
Qué es el patrón repositorio y cuáles son sus ventajas

El patrón repositorio es un patrón que se encarga de encapsular y centralizar la lógica relativa a la persistencia de los datos. En este artículo analizamos qué es y por qué te podría interesar implementarlo.


Si ya lo has visto – o ya dominas lo que es un repositorio y sus ventajas-, continuamos entonces. Aparte de las clases con relaciones existen otras variaciones, como aplicar un sistema de filtros fusionando el patrón repositorio con el patrón Specification. Cuando estén preparados esos artículos, los veréis en un link aquí debajo:

  1. Ejemplo de patrón repositorio en una clase con relación uno a muchos (one-to-many)
  2. Ejemplo de patrón repositorio en una clase con relación muchos a muchos (many-to-many)
  3. Ejemplo de patrón repositorio con filtros (patrón Specification)

Para el ejemplo que nos ocupa en esta ocasión, vamos a partir de una clase llamada «Artículo», sobre la que implementaremos el repositorio. Pese a que se podría crear la clase ArticuloRepository de forma directa, he optado por la forma en la que podemos hacer uso de la inyección de dependencias en ASP.NET, realizando un «contrato» con la interfaz IArticuloRepository.

public class Articulo
{
    public int Id { get; set; }
    public string Titulo { get; set; } = "";
    public string Contenido { get; set; } = "";
    public DateTime FechaCreacion { get; set; }
}
public interface IArticuloRepository
{
    IList<Articulo> GetAllArticulos();
    Articulo GetArticuloById(int id);
    void AddArticulo(Articulo articulo);
    void UpdateArticulo(Articulo articulo);
    void DeleteArticuloById(int id);
}

La tabla creada en la base de datos para este ejemplo se llama «Articulo» también, por mantener la coherencia con la clase.

Los campos para esa clase son exactamente los mismos que los de los atributos de la clase.

Como podéis intuir viendo la interfaz, nuestro repositorio va a – como mínimo – contar con cinco métodos para realizar un CRUD clásico. Hasta este punto, el código funciona para ADO.NET, Dapper y Entity Framework por igual. A continuación empezamos a definir propiamente el repositorio con ADO.NET para nuestros artículos.

using System.Data.SqlClient;

namespace PatronRepositorioConADONET.Repositories
{
  public class ArticuloRepository : IArticuloRepository
  {
    private string connectionString = "String de conexión SQL";

    public IList<Articulo> GetAllArticulos()
    {
        /* Implementado en el apartado Métodos Consulta */
    }

    public Articulo GetArticuloById(int id)
    {
        /* Implementado en el apartado Métodos Consulta */
    }

    public void AddArticulo(Articulo articulo)
    {
        /* Implementado en el apartado Métodos de Creación */
    }

    public void DeleteArticuloById(int id)
    {
        /* Implementado en el apartado Métodos de Eliminación */
    }

    public void UpdateArticulo(Articulo articulo)
    {
        /* Implementado en el apartado Métodos de Eliminación */
    }
  }
}

Para poder conectarnos a la base de datos es necesario tener hacer uso de la libreria System.Data.SqlClient, que es una librería nativa de Microsoft y mantenida por ellos. En esta librería contamos con tres clases que vamos a utilizar:

SqlConnection

Clase que permite establecer la configuración básica para la conexión con la base de datos a utilizar. Esa cadena para conectarnos a la base de datos es la que defino como «connectionString». Utiliza los métodos Open() y Close() para controlar la conexión.

/* IMPLÍCITA */
using SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
// Código para alguna acción en la bbdd
connection.Close();

/* EXPLÍCITA */
using(SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();
    // Resto de tu código
}

Si optamos por usar el implícito, es necesario hacer uso los métodos tanto Open() como Close(). Si optamos por el explícito, sólo necesitas indicar el Open(), pues tan pronto llegue a la llave de cierre, se activa el Dispose() automáticamente y te cierra la conexión.

Los métodos Dispose() y Close() son funcionalmente equivalentes, así que puedes usar cualquiera de los dos. Personalmente uso Close() por la coherencia con Open().

SqlCommand

Es la clase encargada de llevar el comando de SQL que quieres que se ejecute hasta la base de datos. Necesita una conexión activa y abierta con la base de datos para ejecutar el comando con el método ExecuteNonQuery(); o para ser utilizada por otro recurso, como el SqlReader que veremos en el siguiente apartado.

int id = 2; // Esto sería un parámetro del método
using SqlConnection con = new SqlConnection(connectionString);
con.Open();
SqlCommand cmd = new SqlCommand("DELETE FROM Articulo WHERE Id = @IdArticulo", con);
cmd.Parameters.AddWithValue("@IdArticulo", id);
cmd.Connection = con;
cmd.ExecuteNonQuery();
con.Close();

Como se puede apreciar en el código, en una sentencia SQL donde tengamos un filtro «WHERE» podemos incorporar los valores para esa condición utilizando los parámetros de SqlCommand. De hecho esta es la forma adecuada para evitar inseguridades como la inyección SQL (SQL injection).

SqlReader

La clase SqlReader permite obtener un listado de elementos de la base de datos a través de un comando de tipo SqlCommand. Una vez ejecutado el comando, el SqlReader «almacena» la lista de valores obtenidos, por lo que necesitamos hacer uso de un bucle while y el método Read(). En cada iteración, el método Read() verificará si existen datos y, de existir, entramos en el bucle y podemos ir obteniendo cada columna.

using SqlConnection con = new SqlConnection(connectionString);
con.Open();
SqlCommand cmd = new SqlCommand("SELECT * FROM Articulo WHERE Id = @IdArticulo", con);
cmd.Parameters.AddWithValue("@IdArticulo", id);
SqlDataReader reader = cmd.ExecuteReader();

using (reader)
{
    while (reader.Read())
    {
        articulo.Id = (int)reader["Id"];
        articulo.Titulo = (string)reader["Titulo"];
        articulo.Contenido = (string)reader["Contenido"];
        articulo.FechaCreacion = (DateTime)reader["FechaCreacion"];
    }
}

con.Close();   

Podemos acceder a los valores del reader especificando el nombre de la columna, como si de una llave de un diccionario se tratase. Es importante indicar que hay que realziar una conversión explícita de los datos, puesto que al acceder a los datos de esa forma el dato obtenido es de tipo object, que es la clase que heredan el resto de tipos.

Nuevamente podemos tener el reader implícito y explícito como en el caso de SqlConnection.

Nuevamente podemos tener el reader implícito y explícito como en el caso de SqlConnection.

Métodos CRUD del patrón repositorio

public void AddArticulo(Articulo articulo)
{
    try
    {
        using SqlConnection con = new SqlConnection(connectionString);
        con.Open();
        SqlCommand cmd = new SqlCommand("INSERT INTO Articulo VALUES(@Titulo, @Contenido, @Fecha)", con);  
        cmd.Parameters.AddWithValue("@Titulo", articulo.Titulo);
        cmd.Parameters.AddWithValue("@Contenido", articulo.Contenido);
        cmd.Parameters.AddWithValue("@Fecha", DateTime.Now);
        cmd.ExecuteNonQuery();
        con.Close();
    }
    catch (Exception ex)
    {
        /// Gestionar la excepción
    }
}
public Articulo GetArticuloById(int id)
{
    Articulo articulo = new Articulo();

    try
    {
            using SqlConnection con = new SqlConnection(connectionString);
            con.Open();
            SqlCommand cmd = new SqlCommand("SELECT * FROM Articulo WHERE Id = @IdArticulo", con);
            cmd.Parameters.AddWithValue("@IdArticulo", id);
            SqlDataReader reader = cmd.ExecuteReader();

            while (reader.Read())
            {
                articulo.Id = Convert.ToInt32(reader["Id"]);
                articulo.Titulo = Convert.ToString(reader["Titulo"]);
                articulo.Contenido = Convert.ToString(reader["Contenido"]);
                articulo.FechaCreacion = Convert.ToDateTime(reader["FechaCreacion"]);
            }
            con.Close();            
    }
    catch (Exception ex)
    {
        /// Gestionar la excepción
    }

    return articulo;
}
public IList<Articulo> GetAllArticulos()
{
    List<Articulo> articulos = new List<Articulo>();

    try
    {
        using SqlConnection con = new SqlConnection(connectionString);                
        con.Open();
        SqlCommand cmd = new SqlCommand("SELECT * FROM Articulo", con);
        SqlDataReader reader = cmd.ExecuteReader();

        using (reader)
        {
            while (reader.Read())
            {
                Articulo articulo = new Articulo();
                articulo.Id = (int)reader["Id"];
                articulo.Titulo = (string)reader["Titulo"];
                articulo.Contenido = (string)reader["Contenido"];
                articulo.FechaCreacion = (DateTime)reader["FechaCreacion"];
                articulos.Add(articulo);
            }
        }

        con.Close();
    }
    catch (Exception ex)
    {
        /// Gestionar la excepción
    }

    return articulos;
}
public void UpdateArticulo(Articulo articulo)
{
    try
    {
        using SqlConnection con = new SqlConnection(connectionString);
        con.Open();
        SqlCommand cmd = new SqlCommand("UPDATE Articulo SET Titulo = @Titulo, Contenido = @Contenido WHERE Id = @IdArticulo", con);
        cmd.Parameters.AddWithValue("@IdArticulo", articulo.Id);
        cmd.Parameters.AddWithValue("@Titulo", articulo.Titulo);
        cmd.Parameters.AddWithValue("@Contenido", articulo.Contenido);
        cmd.ExecuteNonQuery();
        con.Close();
    }
    catch (Exception ex)
    {
        /// Gestionar la excepción
    }
}
public void DeleteArticuloById(int id)
{
    try
    {
        using SqlConnection con = new SqlConnection(connectionString);
        con.Open();
        SqlCommand cmd = new SqlCommand("DELETE FROM Articulo WHERE Id = @IdArticulo", con);
        cmd.Parameters.AddWithValue("@IdArticulo", id);
        cmd.ExecuteNonQuery();
        con.Close();
    }
    catch (Exception ex)
    {
        /// Gestionar la excepción
    }
}

¡Has llegado al final del artículo, felicidades! 🥳🎉

Hasta aquí la implementación del patrón repositorio más simple con ADO.NET. Como mencionaba anteriormente, en otros artículos hablaremos sobre la implementación de este patrón pero con más relaciones en las clases o con otros patrones.

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

%d