5.5.2 Agregando clases genéricas para validar y/o consultar información

Vamos a crear una clase genérica con los métodos comunes como son obtener todos los registros, obtener un registro por id, agregar, modificar y borrar un registro.

Como cada tabla puede tener diferentes validaciones para agregar, borrar o modificar vamos a agregar una interfaz que nos permita validar cada regla de forma individual. Por ejemplo, podemos tener:

  • Una regla para validar que el nombre de la categoría no se repita

  • Regla para validar que no se repita la clave de la categoría.

De esta forma podemos probar las reglas de forma individual para encontrar mas rápido algún error. Agregamos una interfaz IRegla en nuestra carpeta Core

IRegla.cs
public interface IRegla
{   
    CustomError customError { get; set; }
    bool EsCorrecto();
}

Primero vamos a crear una interfaz llamada IAccesoDAO en nuestra carpeta Core, esta interfaz cual contiene los métodos comunes, el cual utiliza generics, en el cual se recibe un objeto como parámetro, este objeto será el nombre de la clase de la base de datos, por ejemplo Categoria, Producto.

IAccesoDao.cs
public interface IAccesoDAO<T> where T : class
{
    CustomError customError { get; set; }
    Task<List<T>> ObtenerTodoAsync();       
    Task<T> ObtenerPorIdAsync(int id);
    Task<bool> AgregarAsync(T registro, List<IRegla> reglas);
    Task<bool> ModificarAsync(T registro, List<IRegla> reglas);
    Task<bool> BorraAsync(int id, List<IRegla> reglas);
}

Ahora vamos a crear una clase AccesoDAO el cual tiene el código general para las tablas

AccesoDAO.cs
public class AccesoDAO<TEntity> : IAccesoDAO<TEntity> where TEntity : class
{
    private readonly CaducaContext contexto;
    private readonly LocService localizacion;
    public CustomError customError { get; set; }

    public AccesoDAO(CaducaContext context, LocService locService)
    {
        this.contexto = context;
        this.localizacion = locService;
    }

    public async Task<bool> AgregarAsync(TEntity registro, 
                                         List<IRegla> reglas)
    {       
        foreach(var regla in reglas)
        {
            if (!regla.EsCorrecto())
            {
                customError = regla.customError;
                return false;
            }
        }
        contexto.Set<TEntity>().Add(registro);
        await contexto.SaveChangesAsync();

        return true;
    }

    public async Task<bool> BorraAsync(int id, List<IRegla> reglas,
                                       string nombreTabla)
    {       
         var registro = await ObtenerPorIdAsync(id);
         if (registro == null)
         {
             customError = new CustomError(404, String.Format(
               this.localizacion.GetLocalizedHtmlString("NotFound"),
                   nombreTabla), "Id");
             return false;
          }
          foreach(var regla in reglas)
          {
              if (!regla.EsCorrecto())
              {
                  customError = regla.customError;
                  return false;
              }
          }
          contexto.Set<TEntity>().Remove(registro);
          await contexto.SaveChangesAsync();
          return true;
    }

    public async Task<bool> ModificarAsync(TEntity registro, 
                                            List<IRegla> reglas)
    {           
        foreach(var regla in reglas)
        {
            if (!regla.EsCorrecto())
            {
                customError = regla.customError;
                return false;
            }
        }
        contexto.Entry(registro).State = EntityState.Modified;
        await contexto.SaveChangesAsync();
        return true;
    }
        
    public  async Task<TEntity> ObtenerPorIdAsync(int id)
    {
            return await contexto.Set<TEntity>().FindAsync(id);
    }

     public async Task<List<TEntity>> ObtenerTodoAsync()
     {
         return await contexto.Set<TEntity>().ToListAsync();
     }     
}

Agregando Reglas

Ahora cambiamos nuestra clase CategoriaDAO para que utilice nuestra clase AccesoDAO y para el método Agregar creamos 2 reglas:

  1. Validar el nombre repetido

  2. Validar clave repetida.

Agregamos una carpeta llamada Rules la cual contendrá todas las reglas para todas las tablas. Agregamos una carpeta para la tabla Categoría. Creamos 2 clases una para validar que la clave no se repita y otra para validar que el nombre no se repita.

Por defecto las consultas que se hacen a las bases de datos se regresan objetos que internamente llevan un control de los campos para al momento de realizar un SaveChanges se guarden los cambios a todos los objetos que se han cambiado. Para estas reglas solo necesitamos conocer si existe un registro no deseamos llevar el control de cambios. Esto se realiza agregando AsNoTracking() en nuestras consultas, de esta manera se le indica a Entity Framework que el objeto es de solo lectura por lo cual hace el proceso mas eficiente. Agrega un nuevo archivo con el nombre ReglaNombreUnico.cs.

ReglaNombreUnico.cs
using CaducaRest.Core;
using CaducaRest.Models;
using CaducaRest.Resources;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;

namespace CaducaRest.Rules.Categoria;

/// <summary>
/// Valida que el nombre no se repita
/// </summary>
public class ReglaNombreUnico : IRegla
{
    private string nombre;
    private readonly CaducaContext contexto;
    private readonly LocService localizacion;
    private int id;

    /// <summary>
    /// Mensaje de error
    /// </summary>
    public CustomError customError { get; set; }

    /// <summary>
    /// Valida que una cateogría no se llame igual al agregar
    /// </summary>
    /// <param name="nombre">Nombre de la categoría</param>
    /// <param name="context">Objeto para la bd</param>
    /// <param name="locService">Objeto para mensajes en varios
    /// idiomas</param>
    public ReglaNombreUnico(int id, string nombre, CaducaContext context,
                            LocService locService)
    {
        this.nombre = nombre;
        this.contexto = context;
        this.localizacion = locService;
        this.id = id;
    }

    /// <summary>
    /// Permite validar que el nombre de una categoría no se
    /// repita al agregar
    /// </summary>
    /// <returns>True si no se repite la categoría</returns>
    public bool EsCorrecto()
    {
        var registroRepetido = contexto.Categoria.AsNoTracking()
                                 .FirstOrDefault(c => c.Nombre == nombre
                                                 && c.Id != id);
        if (registroRepetido != null)
        {
            customError = new CustomError(400, 
               String.Format(this.localizacion
                .GetLocalizedHtmlString("Repeteaded"), 
                             "categoría", "nombre"), "Nombre");
            return false;
        }
        return true;
    }
}

Agrega otra regla para que la clave no se repita.

ReglaClaveUnico.cs
using CaducaRest.Core;
using CaducaRest.Models;
using CaducaRest.Resources;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;

namespace CaducaRest.Rules.Categoria;

/// <summary>
/// Permite validar que no se repita la clave de una categoría
/// al agregar
/// </summary>
public class ReglaClaveUnico: IRegla
{
    private int clave;
    private int id;
    private readonly CaducaContext contexto;
    private readonly LocService localizacion;

    /// <summary>
    /// Mensaje de error
    /// </summary>
    public CustomError customError { get; set; }

    /// <summary>
    /// Constructor para verificar que la clave no se repite
    /// en una categoría al agregar
    /// </summary>
    /// <param name="clave">Clave de la categoría</param>
    /// <param name="context">Objeto para la bd</param>
    /// <param name="locService">Objeto para traducuir a varuis idiomas</param>
    public ReglaClaveUnico(int id, int clave, CaducaContext context, 
                           LocService locService)
    {
        this.clave = clave;
        this.contexto = context;
        this.localizacion = locService;
        this.id = id;
    }
    
    /// <summary>
    /// Indica si la clave de la categoría no se repite
    /// al agregar
    /// </summary>
    /// <returns></returns>
    public bool EsCorrecto()
    {
        var registroRepetido = contexto.Categoria.AsNoTracking()
                                .FirstOrDefault(c => c.Clave == clave
                                                && c.Id != id);
        if (registroRepetido != null)
        {
            customError = new CustomError(400, String.Format(
            this.localizacion.GetLocalizedHtmlString("Repeteaded"), 
            "categoría", "clave"), "Clave");
            return false;
        }
        return true;
    }
}

Modificamos nuestra clase CategoriaDAO agregando nuestra clase AccesoDAO pasando como parámetro la tabla Categoria, cambiamos el método AgregarAsync para crear una regla para validar que no este repetido el nombre y otra para que no este repetido la clave. Luego pasamos el arreglo de reglas a validar.

CategoriaDAO.cs
 public class CategoriaDAO
{
    //..Código
    
    public async Task<bool> AgregarAsync(Categoria categoria)
    {
        ReglaNombreUnico nombreEsUnico = new ReglaNombreUnico
            (categoria.Id, categoria.Nombre, contexto, localizacion);
        ReglaClaveUnico claveEsUnica = new ReglaClaveUnico
            (categoria.Id, categoria.Clave, contexto, localizacion);

        List<IRegla> reglas = new List<Core.IRegla>();
        reglas.Add(nombreEsUnico);
        reglas.Add(claveEsUnica);

        if (await categoriaDAO.AgregarAsync(categoria, reglas))
            return true;
        else
        {
            customError = categoriaDAO.customError;
            return false;
        }
    }
    
 }

Esta forma es una alternativa para validar reglas personalizadas a cualquier objeto independiente al entity framework. Por ejemplo validaciones que se pueden hacer a un archivo de excel antes de importarlo. Puede parecer muchísimo más código pero nos permite validar de forma individual cada regla, lo cual lo hace más fácil de probar. También es mas claro saber las reglas que se siguen sin tener que leer muchas líneas de código.

El entity framework de .net ofrece una forma más sencilla de realizar validaciones personalizadas, esto lo veremos en la siguiente lección

Para practicar puedes cambiar el método Modificar y crear las reglas personalizadas para modificar y comparar con mi solución en github

Last updated