miércoles, 10 de mayo de 2017

Dapper Generic Repository





Dapper es un micro ORM realizado para Microsoft .NET Framework. Este proporciona una serie de acciones para mapear nuestros objetos POCO a bases de datos Relacionales.

Fue desarrollado por el equipo de StackExchange para sus webs (StackOverflow, Mathematics, etc), ya que la tecnología que utilizaban Linq To Sql no les proporcionaba un rendimiento óptimo en su desempeño de acceso a datos.







Dapper es un proyecto Open Source con licencia Apache License 2.0  y puede instalarse fácilmente desde Nuget:



Índice

  • Características de Dapper
  • Base de Datos del Proyecto
  • La clase DPGenericRepository
  • Creando un objeto DPGenericRepository
  • Descripción de métodos
    • All / AllAsync
    • GetData(object parameters) / GetDataAsync
    • GetData(string qry, object parameters) / GetDataAsync
    • Find(object pks) / FindAsync
    • Add(TEntity entity) / AddAsync
    • Remove(object pk) / RemoveAsync
    • Update(TEntity entity, object pk) / RemoveAsync
    • InsertOrUpdate(TEntity entity, object pk) / RemoveAsync




Características de Dapper

Sus principales ventajas comparadas a otros ORMs son las siguientes:
  • Rendimiento, es el ORM más rápido dentro del mundo .NET.
  • Es necesario escribir menos líneas de código
  • Mapea objetos.
  • Se puede elegir entre bindeo estático o dinámico.
  • Sencillo manejo de consultas SQL.
  • Soporte de múltiples consultas.
  • Soporte para procedimientos almacenados.
  • Extiende directamente la interfaz IDBConnection.
  • Posee la funcionalidad de Bulk data insert para inserciones masivas.

Datos de la Wikipedia.
La mayor desventaja de este fenomenal ORM es que vuelve a las consultas inyectadas en cadenas de texto, perdiendo la validación del compilador en tiempo de ejecución y haciendo nuestro trabajo un poco más complicado.

He creado un Repositorio Genérico basado en Dapper, aprovechando la filosofía de Entitiy Framework, borrando el uso de consultas en strings, en la medida de lo posible, sobre todo para los métodos primarios All, insert, delete, update.

He tratado de crear una librería con la comodidad de Entity Framework y el rendimiento de Dapper.
He testeado DPGenericRepository con Sql Server y Oracle, aunque puede ser compatible con otras bases de datos soportadas por Dapper.

He creado otras librerías de Repositorios genéricos para Entity Framework y para Dapper + Entity Framework, que veremos en siguientes entradas en el blog. Éstas son compatibles entre sí, ya que implementan IGenericRepository y pueden ser fácilmente sustituibles.

Este proyecto es open source y está disponible en Git Hub.

Podemos instalarlo mediante Nuget:










Base de Datos para el Proyecto

La base de datos de Tests incluida en el proyecto es un fichero interno y puede ejecutarse automáticamente:











Tiene 2 tablas:






























La clase DPGenericRepository

DPGenericRepository es la clase principal del namespace MoralesLarios.Data.Dapper . Esta contiene la funcionalidad de crear y/o transformar nuestras clases en repositorios genéricos de Dapper.

Aquí podemos ver un diagrama de la clase principal en grande y las interfaces que implemente. También podemos ver en el diagrama los dos repositorios genéricos que veremos en las entregas siguientes con sus respectivas implementaciones y sus compatibilidades.




























La interfaz IGenericRepository:

public interface IGenericRepository<TEntity> : IDisposable where TEntity : class
{
    IEnumerable<TEntity>       All();
    Task<IEnumerable<TEntity>> AllAsync();
    IEnumerable<TEntity>       GetData(string qry, object parameters);
    Task<IEnumerable<TEntity>> GetDataAsync(string qry, object parameters);
    TEntity                    Find(object pksFields);
    Task<TEntity>              FindAsync(object pksFields);
    int                        Add(TEntity entity);
    Task<int>                  AddAsync(TEntity entity);
    int                        Add(IEnumerable<TEntity> entities);
    Task<int>                  AddAsync(IEnumerable<TEntity> entities);
    void                       Remove(object key);
    Task                       RemoveAsync(object key);
    int                        Update(TEntity entity, object pks);
    Task<int>                  UpdateAsync(TEntity entity, object pks);
    int                        InstertOrUpdate(TEntity entity, object pks);
    Task<int>                  InstertOrUpdateAsync(TEntity entity, object pks);
}


La interfaz IDPGenericRepository:

public interface IDPGenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
    IEnumerable<TEntity>       GetData(object filter);
    Task<IEnumerable<TEntity>> GetDataAsync(object filter);
}


DPGenericRepository implementa IDPGenericRepository la cual a su vez inmplementa IGenericRepository que es la que nos proporciona compatibilidad total con los demás repositorios de la librería.
DPGenericRepository contiene un set regular de métodos básicos en la base de datos, pero puede extender su funcionalidad mediante la herencia.



Creando un objeto DPGenericRepository

Dapper está basado en una clase de extensión para la interfaz IDbConnection, por lo cual DPGenericRepository necesita un objeto IDbConnection inyectado en su contructor.
Podeos crear objetos DPGenericRepository de dos formas diferentes:

1.- Directamente en código:

using (var conn = new SqlConnection(cs))
{
 
 
    var departmentRepository = new DPGenericRepository<Departments>(conn);
// inject IDbConnection

 
}


También podemos usar un segundo constructor con un nuevo parámetro de tipo Char . Este nuevo parámetro ‘parameterIdentified’, muestra el indicador de parámetros para la base de datos ‘@’ en caso de Sql Server (que es el parámetro por defecto) y ‘:’ (dos puntos) en caso de Oracle.

Ejemplo para Oracle:

using (var conn = new SqlConnection(cs))
{
 
 
    var departmentRepository = new DPGenericRepository<Departments>(conn, parameterIdentified:':'
);
// inject IDbConnection
 
}


2.- Por herencia:


public class DepartmentRepository : DPGenericRepository<Departments>
{
    public DepartmentRepository(IDbConnection conn, char parameterIdentified = '@') : base(conn, parameterIdentified)
    {
 
    }
 
}


Hemos cambiado el valor del parámetro parameterIdentified a ‘:’ para Oracle:


public class DepartmentRepository : DPGenericRepository<Departments>
{
    public DepartmentRepository(IDbConnection conn, char parameterIdentified = ':') : base(conn, parameterIdentified)
    {
 
    }
 
}




Descripción de métodos

Vamos a tartar de explicar todos los métodos de DPGenericRepository con un ejemplo para cada uno de ellos.
Fíjese:

Es importante puntualizar que algunos métodos toman un objeto con la definición de with ‘pk’ o ‘parameters’ . Estos son métodos son de tipo object y generalmente son alimentados con tipos anónimos con un alias igual al campo de la clase POCO (tabla) que referencian.




Vamos a ver todos sus métodos:



All / AllAsync

El método All devuelve todos los datos de la tabla:

var departmentRepository = new DPGenericRepository<Departments>(conn);
 
var allDepartments = departmentRepository.All();




GetData(object parameters) / GetDataAsync

El metodo GetData con un parámetro, construye una consulta para Dapper con un conjunto de sentencia de igualdad con clausulas AND.

var employeesRepository = new DPGenericRepository<Employees>(conn);
 
object parameters = new { Name = "Peter", Age = 30, Incomes = 35000 };
 
var employeesPeter30years35000Incomes = employeesRepository.GetData(parameters);
 
//  ** Esta es la consulta automática que genera por detrás **
//  "SELECT * FROM EMPLOYEES " + 
//  " WHERE NAME    = @Name " +
//  " AND   AGE     = @Age " + 
//  " AND   INCOMES = @Incomes";




GetData(string qry, object parameters) / GetDataAsync
GetData method with two parameters, is a method with less automation, because it isn’t possible infer data and your execution is practically equal with a normal query dapper execute.

El método GetData con dos parámetros, es un método con menos posibilidad de automatización, ya que no es posible inferir datos en ejecución y prácticamente es igual a su ejecución en Dapper normal.

var employeesRepository = new DPGenericRepository<Employees>(conn);
 
string qry = "SELECT * FROM EMPLOYEES WHERE AGE > @Age AND INCOMES > @Incomes";
 
object parameters = new { Age = 30, Incomes = 35000 };
 
var employeesMore30yearsMore35000 = employeesRepository.GetData(qry, parameters);




Find(object pks) / FindAsync

Find es un método muy similar a GetData(object paramaters). Su firma existe por dar compatibilidad a la librería genérica de Entity Framework, en la cual esta tiene sentido por su arquitectura. El parámetro PKs, debe estar formado por el número de propiedad que forman la PK de la tabla en el mismo orden.

propiedad que forman la PK de la tabla en el mismo orden.
var employeesRepository = new DPGenericRepository<Employees>(conn);
 
object pk = new { EmployeeID = 3 };
 
var employee3 = employeesRepository.Find(pk);




Add(TEntity entity) / AddAsync

Añade un registro a la base de datos.

var employeesRepository = new DPGenericRepository<Employees>(conn);
 
var newEmployee = new Employees
{
    Name         = "Lucas",
    Age          = 19,
    Incomes      = 15000,
    DepartmentID = 3,
    EntryDate    = DateTime.Today
};
 
var rowsInserted = employeesRepository.Add(newEmployee);




Add(IEnumerable<TEntity> entities) / AddAsync

Inserta un grupo de registros en BD. Este método poseé la versatilidad de Dapper y realiza las inserciones mediante Bulk Copy, haciendo el proceso mucho más rápido.

var newEmployees = new List<Employees>()
{
    new Employees
    {
        Name         = "Lucas",
        Age          = 19,
        Incomes      = 15000,
        DepartmentID = 3,
        EntryDate    = DateTime.Today
    },
    new Employees
    {
        Name         = "Edgar",
        Age          = 64,
        Incomes      = 100000,
        DepartmentID = 3,
        EntryDate    = DateTime.Today
    }
};
 
 
var rowsInserted = employeesRepository.Add(newEmployees);





Remove(object pk) / RemoveAsync

Elimina un registro de base de datos mediante el valor de su PK.

var employeesRepository = new DPGenericRepository<Employees>(conn);
 
object pk = new { EmployeeID = 5 };
 
employeesRepository.Remove(pk);




Update(TEntity entity, object pk) / RemoveAsync

Actualiza un registro de base de datos mediante el valor de su PK.

var employeesRepository = new DPGenericRepository<Employees>(conn);
 
object pk = new { EmployeeID = 1 };
 
var employeeOne = employeesRepository.Find(pk);
 
employeeOne.DepartmentID = 2;
employeeOne.Incomes = 300000;
 
employeesRepository.Update(employeeOne, pk);




InsertOrUpdate(TEntity entity, object pk) / RemoveAsync

Este método chequea si existe el registro en base de datos mediante su PK, en caso afirmativo realiza una actualización de mismo y en caso negativo lo inserta.

Este método es muy práctico para las típicas pantallas de inserción/actualización, ya que se puede reutilizar prácticamente todo el código.


var employeesRepository = new DPGenericRepository<Employees>(conn);
 
var employee = myFile.GetEmployee();
 
var pk = new { employee.EmployeeID };
 
/// we don't know if employee exists
/// InsertOrUpdate method be responsible for INSERT OR UPDATE
employeesRepository.InstertOrUpdate(employee, pk);






Podéis probar todos los métodos en el proyecto de tests.





Download de Test Project here.