Buscar este blog

Mostrando entradas con la etiqueta DataSet. Mostrar todas las entradas
Mostrando entradas con la etiqueta DataSet. Mostrar todas las entradas

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


viernes, 5 de enero de 2018

ADO.NET + DataSet + C#

En el artículo DataSet + C# se explica como trabajar con DataSet. En este voy a explicar como realizar lo mismo pero con líneas de código.

Se utiliza para este ejemplo el lenguaje de consulta SQL, el lenguaje de programación C# y el IDE Microsoft Visual Studio Community.

Situación o Negocio

Para no complicar el ejemplo se utiliza dos tablas con pocos campos.

En una universidad los estudiantes estudian una carrera de ingeniería. De los estudiantes se saben su DNI y Nombre. De la carrera de ingeniería el nombre.

La relación de estas tablas es de 1:N (Uno a varios). Un estudiante solo estudia una carrera de ingeniería y una carrera de ingeniería posee varios estudiantes.

Script – Sql


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

CREATE TABLE student
(
 [Id] BIGINT IDENTITY (1, 1) NOT NULL, 
 [DNI] VARCHAR(255) NOT NULL,    
 [Name] VARCHAR(255) NOT NULL,
 [IdEngineering] BIGINT NOT NULL, 
 PRIMARY KEY CLUSTERED ([Id] ASC),
 UNIQUE NONCLUSTERED ([DNI] ASC),
 CONSTRAINT [FK] FOREIGN KEY ([IdEngineering]) 
  REFERENCES [dbo].[engineering] ([Id]) 
  ON DELETE CASCADE 
  ON UPDATE CASCADE 
)


Paso 1
  • Abrir Visual Studio
  • Crear un proyecto de tipo Class Library. (FILE ->  new -> Proyect)
  •  Ctrl + Shift + A 
  • Crear el fichero de Sql (mdf) 

Paso 2
  • Crear el fichero app.config y escribir la cadena de conexión con la base de datos


  • Crear la clase Connection

class Connection
    {
        public static SqlConnection SqlConnectionProvider
        {
            get
            {
                var cnx =
                    ConfigurationManager.ConnectionStrings[
                        "ClassDataSetBlog.Properties.Settings.DatabaseDataSetConnectionString"].ConnectionString;
                return new SqlConnection(cnx);
            }
        }
    }


Paso 3

Se crea las clases que mapean con las tablas de la base de datos (engineering y student).

    public class Engineering
    {
        public long Id { get; set; }
        public string Name { get; set; }
    }

    public class Student
    {
        public long Id { get; set; }

        public string Dni { get; set; }

        public string Name { get; set; }

        public long IdEngineering { get; set; }
    }


Paso 4

Crear las clase Adapter de cada tabla y mapearlo con las clases del Paso 3.

Se utiliza las clases SqlDataAdapter y SqlCommand.

El SqlCommand tiene la propiedad CommandText que recibe una query de Sql o cuando se inicializa el comando puede recibir en su constructor una query

Ejemplo:


    _adapter.DeleteCommand = new SqlCommand("DELETE FROM engineering WHERE Id = " + item.Id, connection);


En este caso se va a guardar las query en un fichero de recursos. Ctrl + Shift + A


Este recurso nos sirve como un diccionario de códigos SQL


Crear la clase AdapterEngineering y AdapterStudent

    public class AdapterEngineering
    {
        private readonly SqlDataAdapter _adapter;

        public AdapterEngineering()
        {
            _adapter = new SqlDataAdapter();
        }

        public int Insert(Engineering item)
        {
            var connection = Connection.SqlConnectionProvider;
            
            _adapter.InsertCommand = new SqlCommand(ResourceQueryEngineering.Insert, connection);
            var parameter = new SqlParameter(ResourceQueryEngineering.Parameter_Name, item.Name);
            _adapter.InsertCommand.Parameters.Add(parameter);

            connection.Open();
            var value = _adapter.InsertCommand.ExecuteNonQuery();
            connection.Close();

            return value;
        }

        public int Update(Engineering item)
        {
            var connection = Connection.SqlConnectionProvider;

            _adapter.UpdateCommand = new SqlCommand(ResourceQueryEngineering.Update, connection);
            SqlParameter[] parameters =
            {
                 new SqlParameter(ResourceQueryEngineering.Parameter_Name, item.Name),
                 new SqlParameter(ResourceQueryEngineering.Parameter_Id, item.Id)
            };
            _adapter.InsertCommand.Parameters.AddRange(parameters);

            connection.Open();
            var value = _adapter.UpdateCommand.ExecuteNonQuery();
            connection.Close();

            return value;
        }

        public int Delete(Engineering item)
        {
            var connection = Connection.SqlConnectionProvider;

            _adapter.DeleteCommand = new SqlCommand(ResourceQueryEngineering.Delete, connection);
            var parameter = new SqlParameter(ResourceQueryEngineering.Parameter_Id, item.Id);
            _adapter.DeleteCommand.Parameters.Add(parameter);

            connection.Open();
            var value = _adapter.DeleteCommand.ExecuteNonQuery();
            connection.Close();

            return value;
        }

        public List Fill()
        {
            var connection = Connection.SqlConnectionProvider;

            var datatable = new DataTable();
            _adapter.SelectCommand = new SqlCommand(ResourceQueryEngineering.Select, connection);
            _adapter.Fill(datatable);

            return datatable.Rows.Count == 0 ? new List() :
                (from DataRow variable in datatable.Rows
                 select new Engineering
                 {
                     Id = (long)variable[0],
                     Name = (string)variable[1]
                 }).ToList();
        }

        public List QueryEngineeringCountStudent()
        {
            var connection = Connection.SqlConnectionProvider;

            var datatable = new DataTable();
            _adapter.SelectCommand = new SqlCommand(ResourceQueryEngineering.SelectEngineeringCountStudent, connection);
            _adapter.Fill(datatable);

            return datatable.Rows.Count == 0 ? new List() :
                (from DataRow variable in datatable.Rows
                 select new EngineeringCountStudentDto
                 {
                     EngineeringName = (string)variable[0],
                     Count = (int)variable[1]
                 }).ToList();
        } 
    }

Paso 6
 
Se tiene el diseño, las consultas y las funciones básicas de los CRUD de las tablas para gestionar la información de la base de datos. Ahora pasaremos a testear estas acciones con las pruebas unitarias.

  •  Ctrl + Shift + A 



[TestMethod]
        public void TestEgineering()
        {
            var adapter = new AdapterEngineering();
                        
            for (int i = 0; i < 10; i++)
                adapter.Insert(new Engineering{
                    Name = "Engineering " + i
                });

            var list = adapter.Fill();
            int delete = adapter.Delete(list[0]);


            Assert.AreEqual(1, delete);
        }
    

Conclusiones 

Se realiza paso por paso un pequeño ejemplo de cómo utilizar nuestro viejo DataSet y se realiza pruebas unitarias.

Espero que les haya gustado y recuerden que mi objetivo es publicar varias formas de comunicación de una Aplicación con un gestor de base de datos. Próximamente les pondré como aplicar el patrón repositorio.

ADO.NET + Patrón Repositorio + C#


Se despide
Ing. YAM

sábado, 30 de diciembre de 2017

DataSet + C#

En la actualidad se ejemplifica en artículos y videos el uso de ORM (mapeo objeto-relacional) específicamente de ADO.NET Entity Framework. Pero mis intenciones no es solo escribir sobre Entity Framework sino de publicar varias formas de comunicación de una Aplicación con un gestor de base de datos. Por ende voy a comenzar esta serie de artículos con nuestro viejo pero no olvidado DataSet.

La utilización del DataSet proviene de Microsoft .NET Framework (1.X) según Brice-Arnaud GUÉRIN en el libro ASP.NET 4.5 en C# con Visual Studio 2012 el DataSet es una caché de datos (Microsoft la llama, también, grupo) estructurados bajo la forma de DataTable y de DataRelation”.

El DataSet es un objeto que guarda en memoria caché las tablas y relaciones de una fuente de base de datos. Donde se puede utilizar el lenguaje de consulta y ejecutar los CRUD que son funciones básicas de bases de datos en un software.

Se utiliza para este ejemplo el lenguaje de consulta SQL, el lenguaje de programación C# y el IDE Microsoft Visual Studio Community.

Situación o Negocio


Para no complicar el ejemplo se utiliza dos tablas con pocos campos.

En una universidad los estudiantes estudian una carrera de ingeniería. De los estudiantes se saben su DNI y Nombre. De la carrera de ingeniería el nombre.

La relación de estas tablas es de 1:N (Uno a varios). Un estudiante solo estudia una carrera de ingeniería y una carrera de ingeniería posee varios estudiantes.

Script – Sql


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

CREATE TABLE student
(
 [Id] BIGINT IDENTITY (1, 1) NOT NULL, 
 [DNI] VARCHAR(255) NOT NULL,    
 [Name] VARCHAR(255) NOT NULL,
 [IdEngineering] BIGINT NOT NULL, 
 PRIMARY KEY CLUSTERED ([Id] ASC),
 UNIQUE NONCLUSTERED ([DNI] ASC),
 CONSTRAINT [FK] FOREIGN KEY ([IdEngineering]) 
  REFERENCES [dbo].[engineering] ([Id]) 
  ON DELETE CASCADE 
  ON UPDATE CASCADE 
)


Paso 1
  • Abrir Visual Studio
  • Crear un proyecto de tipo Class Library. (FILE ->  new -> Proyect)
  •  Ctrl + Shift + A 
  • Crear el fichero de Sql (mdf) 

Paso 2

  • Ctrl + Shift + A
  • Crear el DataSet
  • Dar doble click en el fichero mdf y se muestra
  • Click derecho en el fichero mdf y seleccionar New Queryv

  • Copiar los dos Script – Sql descrito con anterioridad y crea las dos tablas
  • Mover las dos tablas al DataSet

Paso 3 

Por defecto el DataSet crea por cada tabla un DataTable que contiene los campos de la tabla original de la base de datos y un TableAdapter encargada de sincronizar un DataTable con la tabla de la base de datos.

Los TableAdapter contiene por defecto:
  •  La conexión con la base de datos (Esta cadena de conexión se guarda en el fichero app.config)
  •  Los comandos: Select, Insert, Update y Delete con sus sintaxis predeterminada del lenguaje de consulta Sql 

En el caso de la tabla engineering el comando eliminar (DeleteCommand) posee el código siguiente por defecto.


Se Puede cambiar el código de estos comando según su interés, es este caso lo cambiamos a


DELETE FROM engineering
WHERE (Id = @Original_Id)

El parámetro @Original_Id se guarda en una lista de parámetros que posee este comando. Con el objetivo de recibir un parámetro con un valor de entrada cuando se ejecute.


Si observan en el TableAdapter existe un método llamado Fill que contiene un método llamado GetData. Este método responde al comando seleccionar (SelectCommand). Este método devuelve un DataTable con los registros de datos de la tabla de la base de datos y se ejecuta de modo lectura.


 Paso 4

Si se desea crear más consultas que no sean estos comandos por defectos pueden dar un click derecho en el TableAdapter y seleccionar Add Query y muestra la siguiente ventana.


Donde puede escoger tres opciones:

  • Crear una sintaxis Sql 
  • Crear un procedimiento almacenado (stored procedure) 
  • Usar un procedimiento almacenado existente. 

Nota: Los procedimiento almacenado contienen código Sql que se ejecutan directamente en el motor de bases de datos.

En este caso vamos a seleccionar una sintaxis Sql y muestra la ventana siguiente


Se necesita crear una consulta que diga cuantas carreras de ingeniería tiene la universidad.



Paso 5

Se desea saber la cantidad de alumnos por carrera de ingeniería. En este caso como necesitamos tener dos campos que tenga el nombre de la carrera de ingeniería y otro campo que diga la cantidad de alumnos; es necesario crear un nuevo TableAdapter que genere este DataTable:

 Se da un click derecho al DataSet y se crea un TableAdapter



Este TableAdapter tiene una particularidad: Es de solo lectura, puede realizar consultar con Select pero no puede ni eliminar, insertar o actualizar un registro.

Paso 6

Se tiene el diseño, las consultas y las funciones básicas de los CRUD de las tablas para gestionar la información de la base de datos. Ahora pasaremos a testear estas acciones con las pruebas unitarias.
  •  Ctrl + Shift + A 


        [TestMethod]
        public void TestInsertEngineering()
        {
            var adapter = new engineeringTableAdapter();
            for (int i = 0; i < 10; i++)
            {
                adapter.Insert("Engineering " + i);
            }

            var datatable1 = new DataSet1.engineeringDataTable();
            adapter.Fill(datatable1);

            foreach (var row in datatable1)
                Console.WriteLine(row.Name);

            Assert.AreEqual(10, datatable1.Count);
        }


 var adapterCount = new engineeringCountStudentTableAdapter();
 var datatable3 = new DataSet1.engineeringCountStudentDataTable();
 adapterCount.Fill(datatable3);

 foreach (var item in datatable3)
 {
  Console.WriteLine(string.Format("Carrera = {0}, Cantidad = {1}",item.Name, item.CountStudent));
 }


Conclusiones

Se realiza paso por paso un pequeño ejemplo de cómo utilizar nuestro viejo DataSet y se realiza pruebas unitarias.

Espero que les haya gustado y recuerden que mi objetivo es publicar varias formas de comunicación de una Aplicación con un gestor de base de datos.

ADO.NET + DataSet + C#


Se despide
 YAM