# 4.7.1 Creando Objetos de Accesos a Datos

Vamos a agregar una clase que se encargue del acceso a datos. Debido a que la aplicación es sencilla y solo utilizaremos como acceso a la base de datos el Entity Framework pasaremos directamente la clase CaducaContext.

Si por ejemplo tu aplicación es más grande y en un futuro puedes utilizar ADO .NET para el acceso a la base de datos o alguno de las alternativas ORM como repoDB, Dapper o Stored Procedures lo correcto sería crear una interfaz. Por lo general se llama IRepository la cual tiene los métodos para agregar, modificar, borrar o consultar registros de la base de datos. Como es una interfaz solo define los métodos y no la clase que se encarga de la conexión a la base de datos. En este ejemplo  se conectará mediante Entity Framework y ADO .net

```csharp
//Interfaz con los métodos comunes como agregar, buscar
public interface ICategoriaRepository
{
    Task<Categoria> ObtenerCategoriaAsync(int id); 
    void Agregar(Categoria categoria);
    void Borrar(Categoria categoria);
}

//Puedes crear una clase que se conecte a la base de datos
// por medio de entity framework core
public class CategoriaEFRepository : ICategoriaRepository
{
    private readonly CaducaContext contexto;
    public CategoriaRepository (CaducaContext contexto)
    {
        this.context = context;
    }
    
    public async Task<Categoria> ObtenerPorIdAsync(int id)
    {
        return await contexto.Categoria.FindAsync(id);
    }
    //Los demás métodos
}

//Puedes tener otra clase que se conecte a la base de datos
// con ADO .NET
public class CategoriaADORepository : ICategoriaRepository
{
    private SqlConnection connection;
    private string connectionString;
    
    public CategoriaRepository (SqlConnection connection, 
                                string connectionString)
    {
        this.connection =  connection;
        this.connectionString = connectionString;
    }
    
    public async Task<Categoria> ObtenerPorIdAsync(int id)
    {
        string queryString = "SELECT Id, Clave, Nombre FROM " +
                                      "cateogoria WHERE Id> @id ";
        Categoria categoria = new Categoria();
        using (SqlConnection connection =
                               new SqlConnection(connectionString))
        {
            // Create the Command and Parameter objects.
            SqlCommand command = new SqlCommand(queryString, 
                                                       connection);
            command.Parameters.AddWithValue("@id", id);

            connection.Open();
            SqlDataReader reader = await command.ExecuteReaderAsync();
            
            categoria.Id =  (int)reader[0];
            categoria.Clave =  reader[1].toString();
            categoria.Nombre=  reader[2].toString();
            reader.Close();
         } 
         return categoria;
    }
    //Los demás métodos
}

public class CategoriasController : ControllerBase
{
    private ICategoriaRepository categoriaRepository;
    //El constructor del controller recibe como parámetro la 
    // interfaz en el cual podemos pasarle un objeto de
    // cualquiera de nuestras 2 clases
    public CategoriasController(
                     ICategoriaRepository categoriaRepository)
    {
       this.categoriaRepository = categoriaRepository;
    }
}

```

Vamos a crear una clase para conectarnos a la base de datos que reciba como parámetro el objeto CaducaContext el cual podemos cambiarlo para que sea Mysql, SQL Server, SQL Lite o en memoria para pruebas unitarias.

Vamos a crear una nueva carpeta llamada **DAO** (Data Access Object), en esta carpeta&#x20;

* Agregaremos una clase por cada tabla
* Se encargará de:
  * Agregar un nuevo registro&#x20;
  * Modificar&#x20;
  * Borrar
  * Obtener un registro
  * Obtener todos los registros

Dentro de la carpeta DAO crea una nueva clase llamada CategoriaDAO

![](https://3731331968-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LKmGSg4qhizjqeDPHJC%2F-LQHgLEcNizi-jrkC8pt%2F-LQHhs4MgQp6Ckc7t_O3%2Fimage.png?alt=media\&token=83ae2229-e7f0-4496-8b15-16e5dcf1e0bc)

Agrega como variable privada el contexto (CaducaContext) y en el constructor de la clase recibe como parámetro el contexto.

{% code title="CategoriaDAO.cs" %}

```csharp
public class CategoriaDAO
{
        private readonly CaducaContext contexto;

        /// <summary>
        /// Clase para acceso a la base de datos
        /// </summary>
        /// <param name="context"></param>
        public CategoriaDAO(CaducaContext context)
        {
            this.contexto = context;
        }
}
```

{% endcode %}

### Método para obtener todos los registros

Agrega un método ObtenerTodo a tu clase CategoridaDAO el cual va a obtener todas las categorías. Copia el método de la clase CategoriasController.cs del método Get, recuerda presionar CTRL + . para agregar los import necesarios. Aquí regreso directamente un objeto List para poder facilitar el Debug de tu código.

{% code title="CategoriaDAO.cs" %}

```csharp
/// <summary>
/// Obtiene todas las categorias
/// </summary>
/// <returns></returns>
public async Task<List<Categoria>> ObtenerTodoAsync()
{
    return await contexto.Categoria.ToListAsync();
}
```

{% endcode %}

Cambia tu servicio para inicializar el objeto en el constructor del servicio

{% code title="CategoriasController.cs" %}

```csharp
using System.Collections.Generic;

public class  CategoriasController: ControllerBase
{
    private readonly CaducaContext _context;
    //Agrega el ojeto categoriaDAO
    private CategoriaDAO categoriaDAO;

    public CategoriasController(CaducaContext context)
    {
        _context = context;
        //Inicializa categoriaDAO con el contexto recibido
        // como parámetro
        categoriaDAO = new CategoriaDAO(context);
    }
    
    [HttpGet]
    public async Task<List<Categoria>> GetCategoriaAsync()
    {        
        //Cambia el método get para utilizar el 
        // objeto categoriaDAO
        return await categoriaDAO.ObtenerTodoAsync();
    }
```

{% endcode %}

### Método para buscar un registro por Id

En tu clase **CategoriaDAO** agrega un método para obtener una categoría por medio de su Id. Puedes copiar el método de tu controller a la función. Con Entitty Framework todas las tablas contienen un método Find y FindAsync por si deseas que sea síncrono o asíncrono en el cual busca un registro por medio del campo definido como llave primaria.

{% code title="CategoriaDAO.cs" %}

```csharp
public async Task<Categoria> ObtenerPorIdAsync(int id)
{
     return await contexto.Categoria.FindAsync(id);
}
```

{% endcode %}

Cambia tu controller para que obtenga la categoría de la nueva clase. Aquí borre el código autogenerado para validar el modelo ya que no hay modelo a validar y la validación del modelo se realizará de forma general más adelante.

{% code title="CategoriaController.cs" %}

```csharp
[HttpGet("{id}")]
public async Task<IActionResult> GetCategoria(
                                    [FromRoute] int id)
{
    var categoria = await categoriaDAO
                                 .ObtenerPorIdAsync(id);
    if (categoria == null)
         return NotFound();
    return Ok(categoria);
}
```

{% endcode %}

### Clase para manejo de errores

Crea una nueva carpeta llamada **Core** el cual tendrá clases con funciones genéricas. La clase se llama **CustomError**. Aquí vamos a regresar los mensajes de error de acuerdo al estándar de los servicios rest el cual incluye el StatusCode, el mensaje y adicionalmente  el nombre del campo del error.

```csharp
namespace CaducaRest.Core
{
    /// <summary>
    /// Mensjaes de error 
    /// </summary>
    public class CustomError
    {
        /// <summary>
        /// Código de error 
        /// </summary>
        public int StatusCode;

        /// <summary>
        /// Mensaje del error
        /// </summary>
        public string Message;

        /// <summary>
        /// Campo con el error
        /// </summary>
        public string Field;

        /// <summary>
        /// Constructor de la clase
        /// </summary>
        /// <param name="statusCode">Código de erorr</param>
        /// <param name="message">Mensaje que explica el error</param>
        /// <param name="field">Campo que tiene el error</param>
        public CustomError(int statusCode, string message, 
                           string field)
        {
            StatusCode = statusCode;
            Message = message;
            Field = field;
        }

    }
}
```

### Método para agregar una nueva categoría

En la clase **CategoriaDAO** vamos a crear un nuevo método que se llame **AgregarAsync** el cual recibe como parámetro un objeto Categoria. En este método vamos a agregar código para verificar que no guarden categorías con claves o nombres duplicados. Agregamos una variable llamada customError a nuestra clase CategoriaDAO.cs

{% code title="CategoriaDAO.cs" %}

```csharp
public class CategoriaDAO
{
    public CustomError customError;
    public async Task<bool> AgregarAsync(Categoria categoria)
    {
        Categoria registroRepetido;

        registroRepetido = contexto.Categoria
           .FirstOrDefault(c => c.Nombre == categoria.Nombre);
        if (registroRepetido != null)
        {
             customError = new CustomError(400, 
                   "Ya existe una categoría con este nombre, " +
                   "por favor teclea un nombre diferente", "Nombre");
             return false;
        }
        registroRepetido = contexto.Categoria
          .FirstOrDefault(c => c.Clave == categoria.Clave);
        if (registroRepetido != null)
        {
             customError = new CustomError(400, 
                    "Ya existe una categoría con esta clave, " +
                    "por favor teclea una clave diferente", "Nombre");
             return false;
        }

     contexto.Categoria.Add(categoria);
     await contexto.SaveChangesAsync();
     return true;
}
```

{% endcode %}

Cambiamos el controlador para utilizar la función que acabamos de crear categoriaDAO.

La función **StatusCode** nos permite regresar los mensajes de error de acuerdo al estándar REST, el primer parámetro es el código de error y el segundo es un objeto,  puedes regresar un arreglo con todos los errores encontrados o un string con el mensaje de error.

{% code title="CategoriasController.cs" %}

```csharp
[HttpPost]
public async Task<IActionResult> PostCategoria(
                                     [FromBody] Categoria categoria)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);
    //Cambiamos el código para agregar aquí la clase.
    //Si no fue correcto regresamos el mensaje de error devuelto 
    if (!await categoriaDAO.AgregarAsync(categoria))
    {   
        return StatusCode(categoriaDAO.customError.StatusCode, 
                          categoriaDAO.customError.Message);
     }
     return CreatedAtAction("GetCategoria", 
                               new { id = categoria.Id }, categoria);
 }
```

{% endcode %}

### Método para modificar una categoría

Vamos a agregar el método para modificar una categoría

{% code title="CategoriaDAO.cs" %}

```csharp
/// <summary>
/// Modidica una categoria
/// </summary>
/// <param name="categoria">Datos de la categoria</param>
/// <returns></returns>
public async Task<bool> ModificarAsync(Categoria categoria)
{
     Categoria registroRepetido;
     try
     {
          //Se busca si existe una categoria con el mismo nombre 
          //pero diferente Id
          registroRepetido = contexto.Categoria
                      .FirstOrDefault(c => c.Nombre == categoria.Nombre
                                       && c.Id != categoria.Id);
          if (registroRepetido != null)
          {
               customError = new CustomError(400,
                              "Ya existe una categoría con este nombre, " +
                              "por favor teclea un nombre diferente",
                               "Nombre");
               return false;
           }
           registroRepetido = contexto.Categoria
                      .FirstOrDefault(c => c.Clave == categoria.Clave
                                      && c.Id != categoria.Id);
           if (registroRepetido != null)
           {
               customError = new CustomError(400,
                                "Ya existe una categoría con esta clave, "+
                                "por favor teclea una clave diferente",
                                 "Nombre");
               return false;
            }
            contexto.Entry(categoria).State = EntityState.Modified;
            await contexto.SaveChangesAsync();
     }
     catch (DbUpdateConcurrencyException)
     {
         if (!ExisteCategoria(categoria.Id))
          {
               customError = new CustomError(400, "La categoría" +
                                                 "ya no existe", 
                                                 "Categoría");
               return false;
           }
     }
     return true;
}

private bool ExisteCategoria(int id)
{
    return contexto.Categoria.Any(e => e.Id == id);
}
```

{% endcode %}

En tu clase controller cambiamos el código para que utilice nuestra clase

{% code title="CategoriaController.cs" %}

```csharp
[HttpPut("{id}")]
public async Task<IActionResult> PutCategoria([FromRoute] int id,
                                  [FromBody] Categoria categoria)
{
     if (!ModelState.IsValid)
         return BadRequest(ModelState);

     if (id != categoria.Id)
         return BadRequest();

     if (!await categoriaDAO.ModificarAsync(categoria))
     {
         return StatusCode(categoriaDAO.customError.StatusCode,
                                  categoriaDAO.customError.Message);
     }

     return NoContent();
}
```

{% endcode %}

### Método para borrar una categoría

Por último vamos a separar el método para borrar una categoría. Utilizamos el método para encontrar la categoría que desea borrar el usuario y si no existe mandamos un mensaje de error.

{% code title="CategoriaDAO.cs" %}

```csharp
/// <summary>
/// Permite borrar una categoría por Id
/// </summary>
/// <param name="id">Id de la categoría</param>
/// <returns></returns>
public async Task<bool> BorraAsync(int id)
{
    var categoria = await ObtenerPorIdAsync(id);
    if (categoria == null)
    {
         customError = new CustomError(400,
                      "La categoría que deseas borrar ya no existe, " +
                      "probablemente fue borrada por otro usuario", "Id");
          return false;
     }

     contexto.Categoria.Remove(categoria);
     await contexto.SaveChangesAsync();
     return true;
}
```

{% endcode %}

En el método creado por visual studio se regresa el objeto que fue borrado, en mi caso como no se desea realizar ninguna acción con el registro borrado solo regresare un Ok.

{% code title="CategoriasController.cs" %}

```csharp
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteCategoria([FromRoute] int id)
{
     if (!await categoriaDAO.BorraAsync(id))
     {
           return StatusCode(categoriaDAO.customError.StatusCode,
                                  categoriaDAO.customError.Message);
     }
     return Ok();
}
```

{% endcode %}

Al cambiar el acceso a la base de datos a una clase aparte, nuestro código tiene funciones con pocas líneas de código que realizan una única función.
