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.
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 6Crear 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 7Se 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 8Se 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" });
}