Buscar este blog

jueves, 29 de marzo de 2018

ADO.NET + Patrón Repositorio + C#

En dos artículos anteriores explique cómo utilizar ADO.NET y DataSet. En este artículo les voy a explicar cómo utilizar ADO.NET y el patrón repositorio con dos gestores de bases de datos de forma genérica.

Se utiliza los lenguaje de consulta SQL, MySQL, lenguaje de programación C# y el entorno de desarrollo integrado Microsoft Visual Studio Community.

Situación o Negocio

Una tabla llamada Ingeniería que posee dos campos un ID auto-numérico incremental y NAME una cadena de texto. Se quiere que la Aplicación se conecte con dos bases de datos SQL y MySQL.

Paso 1

Script – SQL

CREATE SCHEMA DatabaseTest

CREATE TABLE engineering
(
 [Id] BIGINT IDENTITY (1, 1) NOT NULL, 
    [Name] VARCHAR(255) NOT NULL,
 PRIMARY KEY CLUSTERED ([Id] ASC),
    UNIQUE NONCLUSTERED ([Name] ASC)
)

Script – MySql

CREATE SCHEMA `databasetest` ;

CREATE TABLE `databasetest`.`engineering` (
  `Id` BIGINT(20)  NOT NULL AUTO_INCREMENT COMMENT '' ,
  `Name` VARCHAR(255) NOT NULL COMMENT '',
  PRIMARY KEY (`Id`)  COMMENT '',
  UNIQUE INDEX `Name_UNIQUE` (`Name` ASC)  COMMENT '');


En este ejemplo se utiliza nuestra vieja técnica de procedimiento almacenado (stored procedure):

stored procedure – SQL

CREATE PROCEDURE InsertEngineering
 @Name varchar(255)
AS
 INSERT Engineering(Name) Values(@Name)
 RETURN @@Identity


stored procedure – MySql

DELIMITER $$
USE `databasetest`$$
CREATE PROCEDURE `InsertEngineering` (in `@Name` varchar(255))
BEGIN
 insert engineering(Id) values(`@Name`);
END
$$ DELIMITER ;

Paso 2 

Crear un proyecto de tipo Class Library 

Por defecto Visual Studio tiene como referencia la librería para Sql pero no siendo el caso con MySql. Pues sencillo, hay que agregar esta referencia. Lo pueden hacer con Nuget.


Paso 3

Crear las cadenas de conexión en el fichero App.config


Se tiene dos cadenas de conexión con las bases de datos y un elemento o llave llamado DataProvider con valor Sql. Si observan tiene el mismo nombre que la cadena de conexión para Sql. Esta llave DataProvider tiene como objetivo establecer por defecto en configuración que conexión se va a ejecutar.

Paso 4

Se crea un enum llamado EnumDataProvider. Tiene como objetivo establecer y chequear el tipo de conexión a realizar en el código.


 public enum EnumDataProvider
    {
        Sql,
        MySql
    }

Paso 5 

Crear la clase CommandCommon que su objetivo es establecer un tipo de comando genérico común para Sql y MySql.


class CommandCommon
    {
        private readonly DbCommand _command;

        public CommandCommon(DbCommand command, DbConnection connection)
        {
            _command = command;
            _command.Connection = connection;
            _command.CommandType = CommandType.StoredProcedure;
        }

        public int ExecuteComand(string procedure, ParameterCommon[] parameters)
        {
            _command.CommandText = procedure;
            ParameterCommon.AddParameter(_command, parameters);

            _command.Connection.Open();
            var rowCount = _command.ExecuteNonQuery();
            _command.Connection.Close();

            return rowCount;
        }

        public int ExecuteComand(string procedure)
        {
            return ExecuteComand(procedure, new ParameterCommon[0]);
        }

        public DataTable Fill(string procedure, ParameterCommon[] parameters)
        {
            _command.CommandText = procedure;
            ParameterCommon.AddParameter(_command, parameters);
            var dataTable = new DataTable();

            _command.Connection.Open();
            var datareader = _command.ExecuteReader();
            dataTable.Load(datareader);
            datareader.Close();
            _command.Connection.Close();

           return dataTable;
        }

        public DataTable Fill(string procedure)
        {
            return Fill(procedure, new ParameterCommon[0]);
        }

        public object ExecuteScalar(string procedure, ParameterCommon[] parameters)
        {
            _command.CommandText = procedure;
            
            ParameterCommon.AddParameter(_command, parameters);

            _command.Connection.Open();
            var value = _command.ExecuteScalar();
            _command.Connection.Close();

            return value; 
        }

        public object ExecuteScalar(string procedure)
        {
            return ExecuteScalar(procedure, new ParameterCommon[0]);
        }
    }

En cada método se le pasa un parámetro con el nombre del procedure que son iguales en las dos bases de datos y un arreglo de parámetros de tipo de datos ParameterCommon

sealed class ParameterCommon
    {
        public string Name { get; set; }
        public object Value { get; set; }

        public static void AddParameter(IDbCommand comand, ParameterCommon[] parameters)
        {
            if (comand.Parameters.Count > 0)
                comand.Parameters.Clear();

            foreach (var parameter in parameters)
            {
                var p = comand.CreateParameter();
                p.ParameterName = parameter.Name;
                p.Value = parameter.Value;
                comand.Parameters.Add(p);
            }
        }
    }

Paso 6

Crear la clase CommandContext con el objetivo de establecer qué tipo de conexión se va a realizar. Dentro de CommandContext se tiene la propiedad abstracta y genérica DbContext el cual recibe qué tipo de conexión se va a realizar y así establecer la comunicación con la base de datos predeterminada. Hasta ahora se tiene una comunicación genérica con la base de datos de tal forma que esta desacoplada. Es fácil adicionar o quitar un gestor de base de datos sin tener que realizar cambios en toda la Aplicación.

class CommandContext
    {
        public CommandCommon DbContext { get; private set; }

        public CommandContext()
        {
            var provider = ConfigurationManager.AppSettings.Get("DataProvider");
            
            EnumDataProvider enumProvider;
            Enum.TryParse(provider, out enumProvider);

            if (Enum.TryParse(provider, out enumProvider))
                GetValue(enumProvider);
        }

        public CommandContext(EnumDataProvider enumProvider)
        {
            GetValue(enumProvider);
        }

        private void GetValue(EnumDataProvider enumProvider)
        {
            var cnx = ConfigurationManager.ConnectionStrings[enumProvider.ToString()].ConnectionString;
            DbConnection connection = null;
            DbCommand command = null;

            switch (enumProvider)
            {
                case  EnumDataProvider.Sql:
                    connection = new SqlConnection(cnx);
                    command = new SqlCommand();
                    break;
                case EnumDataProvider.MySql:
                    connection = new MySqlConnection(cnx);
                    command = new MySqlCommand();
                    break;
            }
            
            DbContext = new CommandCommon(command, connection);
        }
    }

Paso 7

Se mapea el DataTable con un objeto


public static class MapperEngineering
    {
        public static IEnumerable Convert(DataTable datatable)
        {
            return datatable.Rows.Count == 0 ? new List() :
              (from DataRow variable in datatable.Rows
               select new Engineering
               {
                   Id = (long) variable["Id"],
                   Name = (string) variable["Name"]

               } );
        }
    }

Paso 8
Se crea el patrón repositorio.

interface IRepository where T:Entity
    {
        #region CRUD
        void Insert(T entidad);

        void Delete(T entidad);

        void Update(T entidad);

        IEnumerable FindAll();

        #endregion
    }

 interface IRepositoryEngineering: 
        IRepository
    {
         
    }

public sealed class RepositoryEngineering :  IRepositoryEngineering
    {
        private readonly CommandContext _context;

        public RepositoryEngineering()
        {
            _context = new CommandContext();
        }

        public RepositoryEngineering(EnumDataProvider provider)
        {
            _context = new CommandContext(provider);
        }

        public void Insert(Engineering entidad)
        {
            _context.DbContext.ExecuteComand("InsertEngineering",
                new []
                {
                    new ParameterCommon { Name = "@Name", Value = entidad.Name }
                });
        }

        public void Delete(Engineering entidad)
        {
            _context.DbContext.ExecuteComand("InsertEngineering", 
                new[]
                {
                    new ParameterCommon { Name = "@Id", Value = entidad.Id }
                });
        }

        public void Update(Engineering entidad)
        {
            _context.DbContext.ExecuteComand("InsertEngineering",
                new[]
                {
                    new ParameterCommon { Name = "@Name", Value = entidad.Name },
                    new ParameterCommon { Name = "@Id", Value = entidad.Id }
                });
        }

        public IEnumerable FindAll()
        {
            return MapperEngineering.Convert(_context.DbContext.Fill("SelectAllEngineering"));
        }
    }

TEST

[TestMethod]
        public void SelectRepository()
        {
            var context = new RepositoryEngineering();
            var n = context.FindAll();

            Assert.AreEqual(n.Count(), 7);
        }

[TestMethod]
        public void InsertRepository()
        {
            var context = new RepositoryEngineering(EnumDataProvider.MySql);
            context.Insert(new Engineering { Name = "Engineering" });
        }

EntityFramework + Patrón Repositorio