ORAPOCO: Update 2

Introducción

Hace unos días publique ORAPOCO, un ORM ligero para trabajar con Bases de Datos Oracle, y su primera actualización

Cambios y Mejoras

– He realizado un poco de refactoring en el código.

– He creado una clase abstracta “Base” que va a servir como Base de nuestras Entidades.

– He añadido una propiedad para indicar el modo de ejecución de los comandos: Normal(Valor por defecto) o UnitOfWork.

– He modificado la plantilla, para dar la posibilidad de utilizar el atributo “DefaultValue” con los valores por defecto de la Base de Datos.

Ejemplos:

Las nuevas entidades generadas por la plantilla tendrán un aspecto similar a:

 

   1: public class SAMPLE : Base

   2:    {

   3:        [IsPK()]

   4:        [DefaultValue("SYS_GUID()")]

   5:        public System.Guid ID { get; set; }

   6:  

   7:  

   8:        public System.String NOMBRE { get; set; }

   9:  

  10:    }

 

Un ejemplo de utilización de UoW:

   1: var db = new POCO.Ora.TP.OracleDB("MyDB",POCO.Ora.TP.Enums.Mode.UnitOfWork);

   2: var sample = new POCO.Ora.TP.SAMPLE { NOMBRE = "Javier" };

   3: db.Insert<POCO.Ora.TP.SAMPLE>(sample);

   4: db.Insert<POCO.Ora.TP.SAMPLE>(new POCO.Ora.TP.SAMPLE { NOMBRE = "Alvaro" });

   5: db.Save();

 

Descargas:

 

Como siempre tenéis las descargas en:

GitHub

Nuget

Y proximamente en CodePlex.

 

Saludos, y como siempre si me dais FeedBack Genial!

ORAPOCO: Primera actualización

Introducción

Hace unos días publique ORAPOCO, y hoy lanzo una nueva versión con algunas features y modificaciones.

Lo nuevo

Modificaciones:

– Modificación en el atributo IsPK: Tal como comentaba Eduard en el post anterior realmente no es necesario indicar True en dicho atributo puesto que solo lo van a tener los campos que formen la PK.

– Refactorización de los métodos de Inserción, Actualización y Borrado.

Mejoras:

– Creación del atributo Sequence:

   1: internal class SequenceAttribute : System.Attribute 

   2: {

   3:     public SequenceAttribute(string sequenceName)

   4:     {

   5:         if (string.IsNullOrWhiteSpace(sequenceName)) throw new Exception("Sequence name is necessary.");

   6:         SequenceName = sequenceName;

   7:     }

   8:  

   9:     public string SequenceName { get; set; }

  10: }

 

Este atributo, que se agregará de forma manual a nuestras entidades nos va a permitir simular el uso de auto numéricos en Oracle a través de una Secuencia.

Ejemplo de uso:

   1: public class TM_EMPRESAS

   2: {

   3:     [IsPK()]

   4:     [Sequence("S_Empresas")]

   5:     public System.Int16 EMPR_CODIGO_EMPRESA {get;set;}       

   6:     public System.String EMPR_NOMBRE_EMPRESA {get;set;}       

   7: ///...

   8: }

De esta manera a la hora de ejecutar la sentencia utilizará el siguiente valor auto numérico de la secuencia indicada.

Descargas:

GitHub

Nuget

 

Saludos!

[ORM]ORAPOCO: Trabajando contra Oracle

Introducción

El objeto de este post, es hablar de un pequeño proyecto que acabo de publicar: ORAPOCO. Podéis encontrarlo tanto en GITHUB como en Nuget.

¿Qué es ORAPOCO?

Es un pequeño proyecto que va a permitir trabajar con objetos POCO y contra nuestra Base de Datos Oracle.

El proyecto consta de los siguientes ficheros:

– OracleDB.cs:

Esta clase va a ser la encargada de llevar a cabo todas las acciones contra la Base de Datos.

Los métodos disponibles son:

Método Descripción
QueryAll<T> Dado un tipo T(un objeto poco) va a ejecutar una consulta sobre la BD y va a devolver una lista de dicho T.
Query<T> Del mismo modo que QueryAll, va a devolver un conjunto de resultados de T, pero con la opción de seleccionar columnas, ordenar, establecer una clausula where, hacer take y skip.
Insert<T> Dado un tipo T y una entidad de dicho tipo va a tratar de realizar una inserción en la Base de Datos.
Update<T> Dado un tipo T y una entidad de dicho tipo va a tratar de realizar una actualizacion en la Base de Datos según los valores de clave primaria de dicha entidad.
Delete<T> Dado un tipo T y una entidad de dicho tipo va a tratar de realizar un borrado en la Base de Datos según los valores de clave primaria de dicha entidad.
MultipleQuery<T1,T2> Va a devolver un objeto de Tipo Tuple con el conjunto de resultados indicados por T1, T2 y T3 (2 sobrecargas)

 

– ORAPOCO.tt: Plantilla de generación de código que a través de una conexión contra la BD ORACLE va a definir clases POCO que podremos utilizar en nuestra aplicación.

Dentro de dicha plantilla se hace uso de la primera conexión existente en el fichero de configuración de la aplicación y se define una variable “_schemma” que será la que necesitemos indicar el propietario para buscar las  tablas en Oracle.

– IsPkAttribute.cs:

Atributo que va a servir para configurar las claves primarias de nuestros Objetos POCO, y que se va a utilizar a la hora de llevar a cabo las inserciones o actualizaciones, en la Base de Datos.

¿Cómo empezar a trabajar con ORAPOCO?

Podeís bajar el código fuente desde GITHUB, o instalar el paquete desde Nuget:

PM> Install-Package ORAPOCO

Ejemplos:

Un ejemplo de objeto POCO generado a través de la plantilla:

   1: public class TA_USUARIOS

   2:    {

   3:        [IsPK(true)]

   4:        public System.Int32 USUA_CODIGO_USUARIO { get; set; }

   5:  

   6:        public System.String USUA_NOMBRE { get; set; }

   7:  

   8:        public System.String USUA_APELLIDO1 { get; set; }

   9:  

  10:        public System.String USUA_APELLIDO2 { get; set; }

  11:  

  12:        public System.DateTime? USUA_FECHA_ALTA { get; set; }

  13:  

  14:        public System.String USUA_LOGIN_USUARIO { get; set; }

  15:  

  16:    }

¿Cómo  llevar a cabo una inserción?

   1: var db = new POCO.Ora.TP.OracleDB("SIFCO");

   2:  

   3:   var usu = new POCO.Ora.TP.TA_USUARIOS { 

   4:                               USUA_APELLIDO1 = "Torrecilla",

   5:                               USUA_APELLIDO2 = "Puertas", 

   6:                               USUA_CODIGO_USUARIO = 874,

   7:                               USUA_FECHA_ALTA = DateTime.Now, 

   8:                               USUA_LOGIN_USUARIO = "test", 

   9:                               USUA_NOMBRE = "Javi" };

  10:   db.Insert<POCO.Ora.TP.TA_USUARIOS>(usu);

¿Cómo llevar a cabo una actualización?

   1: var usu = new POCO.Ora.TP.TA_USUARIOS { 

   2:                             USUA_APELLIDO1 = "Torrecilla",

   3:                             USUA_APELLIDO2 = "Puertas", 

   4:                             USUA_CODIGO_USUARIO = 874,

   5:                             USUA_FECHA_ALTA = DateTime.Now, 

   6:                             USUA_LOGIN_USUARIO = "test", 

   7:                             USUA_NOMBRE = "Javi" };

   8: db.Update<POCO.Ora.TP.TA_USUARIOS>(usu);

 

¿Cómo eliminar un registro?

   1: var usu = new POCO.Ora.TP.TA_USUARIOS { 

   2:                             USUA_APELLIDO1 = "Torrecilla",

   3:                             USUA_APELLIDO2 = "Puertas", 

   4:                             USUA_CODIGO_USUARIO = 874,

   5:                             USUA_FECHA_ALTA = DateTime.Now, 

   6:                             USUA_LOGIN_USUARIO = "test", 

   7:                             USUA_NOMBRE = "Javi" };

   8: db.Delete<POCO.Ora.TP.TA_USUARIOS>(usu);

 

¿Cómo consultar datos?

– Todos los resultados:

   1: var query = db.QueryAll<POCO.Ora.TP.TA_USUARIOS>();

 

– Tomar 5 elementos y saltar 5:

   1: var query = db.Query<POCO.Ora.TP.TA_USUARIOS>(take: 5);

 

   1: var query = db.Query<POCO.Ora.TP.TA_USUARIOS>(skip: 5);

 

 

– Resultados Ordenando:

   1: var query = db.Query<POCO.Ora.TP.TA_USUARIOS>(order: "USUA_APELLIDO1 ASC, USUA_APELLIDO2 ASC");

-Resultados filtrando:

   1: var query = db.Query<POCO.Ora.TP.TA_USUARIOS>(where: "USUA_APELLIDO1 LIKE (:0)",args: new object[]{"%or%"});

– Multiples resultados:

   1: var multiQuery = db.MultipleQuery<POCO.Ora.TP.TA_USUARIOS, POCO.Ora.TP.TA_FUNCIONALIDADES>();

multiQuery tendrá dentro de “item1” la colección de Usuarios, y dentro de “item2” la colección de Funcionalidades.

 

 

Bueno espero que me dejéis comentarios, lo probéis, me digáis posibles mejoras, es decir, quiero feedback :p.

 

Saludos!

Comparativa ORM-Lite, EF y ADO.NET

Introducción

A raíz de la serie de post que estoy escribiendo acerca de ORMS ligeros con (Dapper, Massive y PetaPoco), y a pesar de no haber podido meter en la comparativa a Massive (lo intentaré actualizar esta noche), he decidido hacer una pequeña comparativa que me ha parecido interesante.

Comparativa

Para realizar la comparativa he utilizado los siguientes fragmentos de código, para realizar la inserción de 500 Registros en la misma tabla:

Entity Framework

   1: private static void UsingEF_B()

   2:        {

   3:            var watch = Stopwatch.StartNew();

   4:            var context = new Main.MYCVEntities1();

   5:            for (int i = 0; i < 500; i++)

   6:            {

   7:                context.CandidateInfo.AddObject(new Main.CandidateInfo

   8:                {

   9:                    CandidateId = Guid.NewGuid(),

  10:                    CandidateName = "Álvaro",

  11:                    CandidateLastName = "Torrecilla",

  12:                    CandidateEmail = "a@torrecilla.com",

  13:                    CandidateBirthDate = Convert.ToDateTime("30/12/2009"),

  14:                    CandidateCountry = 1,

  15:                    CandidateCity = 1

  16:                });

  17:  

  18:            }

  19:            context.SaveChanges();

  20:  

  21:            watch.Stop();

  22:            Console.WriteLine(String.Format("EF: {0}", watch.Elapsed.ToString()));

  23:        }

PetaPoco

   1: private static void UsingPetaPoco()

   2:         {

   3:             var watch = Stopwatch.StartNew();

   4:             //var db = new PetaPoco.Database(@"Data Source=.SQLEXPRESS;Initial Catalog=MYCV;Integrated Security=True");

   5:             var db = PetaPocoDB.GetInstance();

   6:             var candidate = new PetaPoco.CandidateInfo

   7:             {

   8:                 

   9:                 CandidateName = "Álvaro",

  10:                 CandidateLastName = "Torrecilla",

  11:                 CandidateEmail = "a@torrecilla.com",

  12:                 CandidateBirthDate = Convert.ToDateTime("30/12/2009"),

  13:                 CandidateCountry = 1,

  14:                 CandidateCity = 1

  15:             };

  16:             for (int i = 0; i < 500; i++)

  17:             {

  18:                 candidate.CandidateId = Guid.NewGuid();

  19:                 db.Insert(candidate);

  20:             }

  21:             watch.Stop();

  22:             Console.WriteLine(String.Format("PetaPoco: {0}",watch.Elapsed.ToString()));

  23:             

  24:         }

 

Dapper

   1: private static void UsingDapper()

   2:         {

   3:             using (var connection = new SqlConnection(@"Data Source=.SQLEXPRESS;Initial Catalog=MYCV;Integrated Security=True"))

   4:             {

   5:                 var watch = Stopwatch.StartNew();

   6:                 connection.Open();

   7:  

   8:                 var lista = new List<dynamic>();

   9:                 for (int i = 0; i < 500; i++)

  10:                 {

  11:                     lista.Add(new

  12:                     {

  13:                         CandidateName = "Álvaro",

  14:                         CandidateLastName = "Torrecilla",

  15:                         CandidateEmail = "a@torrecilla.com",

  16:                         CandidateBirthDate = Convert.ToDateTime("30/12/2009"),

  17:                         CandidateCountry = 1,

  18:                         CandidateCity = 1

  19:                     });

  20:                 }

  21:  

  22:                 Dapper.SqlMapper.Execute(connection, @"INSERT INTO CANDIDATEINFO (CandidateName,CandidateLastName,

  23:                                                     CandidateEmail,CandidateBirthDate,CandidateCountry,CandidateCity)

  24:                          VALUES(@CandidateName,@CandidateLastName,@CandidateEmail,@CandidateBirthDate,@CandidateCountry,@CandidateCity)",

  25:                    lista);

  26:                 watch.Stop();

  27:                 Console.WriteLine(String.Format("Dapper: {0}", watch.Elapsed.ToString()));

  28:                 

  29:             }

  30:  

  31:         }

SQLCommand Simple

   1: private static void UsingSQL_A()

   2:         {

   3:             using (var connection = new SqlConnection(@"Data Source=.SQLEXPRESS;Initial Catalog=MYCV;Integrated Security=True"))

   4:             {

   5:                 var watch = Stopwatch.StartNew();

   6:                 connection.Open();

   7:                 using (var cmd = new SqlCommand(@"INSERT INTO CANDIDATEINFO (CandidateName,CandidateLastName,

   8:                                                     CandidateEmail,CandidateBirthDate,CandidateCountry,CandidateCity)

   9:                          VALUES(@CandidateName,@CandidateLastName,@CandidateEmail,@CandidateBirthDate,@CandidateCountry,@CandidateCity)", connection))

  10:                 {

  11:                     cmd.Parameters.AddWithValue("CandidateName", "Álvaro");

  12:                     cmd.Parameters.AddWithValue("CandidateLastName", "Torrecilla");

  13:                     cmd.Parameters.AddWithValue("CandidateEmail", "a@t.com");

  14:                     cmd.Parameters.AddWithValue("CandidateBirthDate", Convert.ToDateTime("30/12/2009"));

  15:                     cmd.Parameters.AddWithValue("CandidateCountry", 1);

  16:                     cmd.Parameters.AddWithValue("CandidateCity", 1);

  17:                     for (int i = 0; i < 500; i++)

  18:                     {

  19:                         cmd.ExecuteNonQuery();

  20:                     }

  21:                 }

  22:                 watch.Stop();

  23:                 Console.WriteLine(String.Format("SQL: {0}", watch.Elapsed.ToString()));

  24:                

  25:             }

  26:         }

SQLCommand con StringBuilder

   1: private static void UsingSQL_B()

   2:        {

   3:            StringBuilder builder = new StringBuilder();

   4:            using (var connection = new SqlConnection(@"Data Source=.SQLEXPRESS;Initial Catalog=MYCV;Integrated Security=True"))

   5:            {

   6:                var watch = Stopwatch.StartNew();

   7:                connection.Open();

   8:                builder.Append("INSERT INTO CANDIDATEINFO (CandidateName,CandidateLastName,CandidateEmail,CandidateBirthDate,CandidateCountry,CandidateCity) Values ");

   9:  

  10:                for (int i = 0; i < 500; i++)

  11:                {

  12:                    builder.Append("(@CandidateName,@CandidateLastName,@CandidateEmail,@CandidateBirthDate,@CandidateCountry,@CandidateCity),");

  13:                }

  14:                builder.Remove(builder.Length - 1, 1);

  15:                using (var cmd = new SqlCommand(builder.ToString(), connection))

  16:                {

  17:                    cmd.Parameters.AddWithValue("CandidateName", "Álvaro");

  18:                    cmd.Parameters.AddWithValue("CandidateLastName", "Torrecilla");

  19:                    cmd.Parameters.AddWithValue("CandidateEmail", "a@t.com");

  20:                    cmd.Parameters.AddWithValue("CandidateBirthDate", Convert.ToDateTime("30/12/2009"));

  21:                    cmd.Parameters.AddWithValue("CandidateCountry", 1);

  22:                    cmd.Parameters.AddWithValue("CandidateCity", 1);

  23:                   

  24:                        cmd.ExecuteNonQuery();

  25:                   

  26:                }

  27:                watch.Stop();

  28:                Console.WriteLine(String.Format("StringBuilder: {0}", watch.Elapsed.ToString()));

  29:                

  30:            }

  31:        }

Massive

   1: var

   2: candidateInfo = new App_Code.CandidateInfo();

   3: for (int i = 0; i < 500; i++)

   4: {

   5: var dato = candidateInfo.Insert(new

   6: {

   7: CandidateName =

   8: "Javier",

   9: CandidateLastName =

  10: "Torrecilla",

  11: CandidateBirthDate =

  12: DateTime.Now,

  13: CandidateCountry = 1,

  14: CandidateCity = 1,

  15: CandidateEmail =

  16: "r@yahoo.es"

  17: });

  18: }

SQLBULKCopy

   1: private static void UsingBulkCopy()

   2:         {

   3:             var watch = Stopwatch.StartNew();

   4:             using (var con = new OleDbConnection(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:pruebas;Extended Properties=""text;HDR=Yes;FMT=Delimited(';')"";"))

   5:             {

   6:                 con.Open();

   7:                 using (var reader = new OleDbCommand("Select * from fichero.txt", con).ExecuteReader())

   8:                 {

   9:                     using (var connection = new SqlConnection(@"Data Source=.SQLEXPRESS;Initial Catalog=MYCV;Integrated Security=True"))

  10:                     {

  11:                         connection.Open();

  12:                         using (var bulk = new SqlBulkCopy(connection))

  13:                         {

  14:                             bulk.DestinationTableName =

  15:                             "CandidateInfo";

  16:                             bulk.ColumnMappings.Add(0,

  17:                             "CandidateName");

  18:                             bulk.ColumnMappings.Add(1,

  19:                             "CandidateLastName");

  20:                             bulk.ColumnMappings.Add(2, "CandidateBirthDate");

  21:                             bulk.ColumnMappings.Add(3,

  22:                             "CandidateCountry");

  23:                             bulk.ColumnMappings.Add(4,

  24:                             "CandidateCity");

  25:                             bulk.ColumnMappings.Add(5,

  26:                             "CandidateEmail");

  27:                             bulk.WriteToServer(reader);

  28:                             watch.Stop();

  29:                             Console.WriteLine(String.Format("BulkCopy: {0}", watch.Elapsed.ToString()));

  30:                         }

  31:                     }

  32:                 }

  33:  

  34:             }

  35:         }

 

Una vez ejecutados todos los métodos he obtenido los siguientes resultados

 

Función Tiempo
Entity Framework 00:00:02.5485
PetaPoco 00:00:02.2437
Dapper 00:00:01.5729
SQL command Simple 00:00:01.4206
Sql command StringBuilder 00:00:00.0166
Massive 00:00:01:6641
SQL Bulk Copy 00:00:00:1712

 

Aunque los resultados entre ejecución y ejecución varían como es normal(, y mi entorno de pruebas no es un I7 con mucha ram … XD) son los resultados más comunes, aunque a veces EF era mas rápido que PetaPoco y Dapper y entre ellos mismos alternaban su orden…

Cuando publique el próximo artículo actualizaré los datos de la tabla incluyendo también los de Massive.

Saludos

UPDATE 1: He agregado el código para usar SQLBulkCopy y Massive.

PD: En poco tiempo publicaré otra actualización.

[ORM-Lite] PetaPoco

Introducción

En este post voy a continuar la serie acerca de ORMS ligeros donde ya he hablado de Dapper y Massive, en esta ocasion voy a tratar PetaPoco.

¿Qué es PetaPoco?

PetaPoco es otro mini ORM basado en Massive, pero a diferencia de este para trabajar con objetos POCO.

Algunas de sus caracteristicas:

– Es un único fichero que sea agrega al proyecto.

– Permite trabajar con objetos POCO.

– Permite la generación de los objetos POCO a través de unas plantillas T4.

¿Por dónde empezar?

Para poder empezar a trabajar con PetaPoco, podemos realizar de la siguiente manera:

– Agregando desde Nuget el paquete a nuestro proyecto.

– Agregar el fichero desde GitHub.

En el ejemplo que voy a utilizar, he agregado el paquete desde Nuget. Y he configurado las plantillas de tal forma que me genere las clases poco contra SQL Server y las mismas tablas que hemos utilizado en ejemplos anteriores.

Algunos Ejemplos

Gracias a la plantilla T4, se ha generado la siguiente clase en mi proyecto:

   1: public partial class PetaPocoDB : Database

   2:     {

   3:         public PetaPocoDB() 

   4:             : base("")

   5:         {

   6:             CommonConstruct();

   7:         }

   8:  

   9:         public PetaPocoDB(string connectionStringName) 

  10:             : base(connectionStringName)

  11:         {

  12:             CommonConstruct();

  13:         }

  14:         

  15:         partial void CommonConstruct();

  16:         

  17:         public interface IFactory

  18:         {

  19:             PetaPocoDB GetInstance();

  20:         }

  21:         

  22:         public static IFactory Factory { get; set; }

  23:         public static PetaPocoDB GetInstance()

  24:         {

  25:             if (_instance!=null)

  26:                 return _instance;

  27:                 

  28:             if (Factory!=null)

  29:                 return Factory.GetInstance();

  30:             else

  31:                 return new PetaPocoDB();

  32:         }

  33:  

  34:         [ThreadStatic] static PetaPocoDB _instance;

  35:         

  36:         public override void OnBeginTransaction()

  37:         {

  38:             if (_instance==null)

  39:                 _instance=this;

  40:         }

  41:         

  42:         public override void OnEndTransaction()

  43:         {

  44:             if (_instance==this)

  45:                 _instance=null;

  46:         }

  47:         

  48:         public class Record<T> where T:new()

  49:         {

  50:             public static PetaPocoDB repo { get { return PetaPocoDB.GetInstance(); } }

  51:             public bool IsNew() { return repo.IsNew(this); }

  52:             public object Insert() { return repo.Insert(this); }

  53:             public int Update(IEnumerable<string> columns) { return repo.Update(this, columns); }

  54:             public static int Update(string sql, params object[] args) { return repo.Update<T>(sql, args); }

  55:             public static int Update(Sql sql) { return repo.Update<T>(sql); }

  56:             public int Delete() { return repo.Delete(this); }

  57:             public static int Delete(string sql, params object[] args) { return repo.Delete<T>(sql, args); }

  58:             public static int Delete(Sql sql) { return repo.Delete<T>(sql); }

  59:             public static int Delete(object primaryKey) { return repo.Delete<T>(primaryKey); }

  60:             public static bool Exists(object primaryKey) { return repo.Exists<T>(primaryKey); }

  61:             public static T SingleOrDefault(object primaryKey) { return repo.SingleOrDefault<T>(primaryKey); }

  62:             public static T SingleOrDefault(string sql, params object[] args) { return repo.SingleOrDefault<T>(sql, args); }

  63:             public static T SingleOrDefault(Sql sql) { return repo.SingleOrDefault<T>(sql); }

  64:             public static T FirstOrDefault(string sql, params object[] args) { return repo.FirstOrDefault<T>(sql, args); }

  65:             public static T FirstOrDefault(Sql sql) { return repo.FirstOrDefault<T>(sql); }

  66:             public static T Single(object primaryKey) { return repo.Single<T>(primaryKey); }

  67:             public static T Single(string sql, params object[] args) { return repo.Single<T>(sql, args); }

  68:             public static T Single(Sql sql) { return repo.Single<T>(sql); }

  69:             public static T First(string sql, params object[] args) { return repo.First<T>(sql, args); }

  70:             public static T First(Sql sql) { return repo.First<T>(sql); }

  71:             public static List<T> Fetch(string sql, params object[] args) { return repo.Fetch<T>(sql, args); }

  72:             public static List<T> Fetch(Sql sql) { return repo.Fetch<T>(sql); }

  73:             public static List<T> Fetch(long page, long itemsPerPage, string sql, params object[] args) { return repo.Fetch<T>(page, itemsPerPage, sql, args); }

  74:             public static List<T> Fetch(long page, long itemsPerPage, Sql sql) { return repo.Fetch<T>(page, itemsPerPage, sql); }

  75:             public static List<T> SkipTake(long skip, long take, string sql, params object[] args) { return repo.SkipTake<T>(skip, take, sql, args); }

  76:             public static List<T> SkipTake(long skip, long take, Sql sql) { return repo.SkipTake<T>(skip, take, sql); }

  77:             public static Page<T> Page(long page, long itemsPerPage, string sql, params object[] args) { return repo.Page<T>(page, itemsPerPage, sql, args); }

  78:             public static Page<T> Page(long page, long itemsPerPage, Sql sql) { return repo.Page<T>(page, itemsPerPage, sql); }

  79:             public static IEnumerable<T> Query(string sql, params object[] args) { return repo.Query<T>(sql, args); }

  80:             public static IEnumerable<T> Query(Sql sql) { return repo.Query<T>(sql); }

  81:             

  82:             private Dictionary<string,bool> ModifiedColumns;

  83:             private void OnLoaded()

  84:             {

  85:                 ModifiedColumns = new Dictionary<string,bool>();

  86:             }

  87:             protected void MarkColumnModified(string column_name)

  88:             {

  89:                 if (ModifiedColumns!=null)

  90:                     ModifiedColumns[column_name]=true;

  91:             }

  92:             public int Update() 

  93:             { 

  94:                 if (ModifiedColumns==null)

  95:                     return repo.Update(this); 

  96:  

  97:                 int retv = repo.Update(this, ModifiedColumns.Keys);

  98:                 ModifiedColumns.Clear();

  99:                 return retv;

 100:             }

 101:             public void Save() 

 102:             { 

 103:                 if (repo.IsNew(this))

 104:                     repo.Insert(this);

 105:                 else

 106:                     Update();

 107:             }

 108:         }

 109:     }

 

Y las clases relativas a mis entidades:

   1: [TableName("CandidateInfo")]

   2:     [PrimaryKey("CandidateId", autoIncrement=false)]

   3:     [ExplicitColumns]

   4:     public partial class CandidateInfo : PetaPocoDB.Record<CandidateInfo>  

   5:     {

   6:         [Column] 

   7:         public Guid CandidateId 

   8:         { 

   9:             get

  10:             {

  11:                 return _CandidateId;

  12:             }

  13:             set

  14:             {

  15:                 _CandidateId = value;

  16:                 MarkColumnModified("CandidateId");

  17:             }

  18:         }

  19:         Guid _CandidateId;

  20:  

  21:         [Column] 

  22:         public string CandidateName 

  23:         { 

  24:             get

  25:             {

  26:                 return _CandidateName;

  27:             }

  28:             set

  29:             {

  30:                 _CandidateName = value;

  31:                 MarkColumnModified("CandidateName");

  32:             }

  33:         }

  34:         string _CandidateName;

  35:  

  36:         [Column] 

  37:         public string CandidateLastName 

  38:         { 

  39:             get

  40:             {

  41:                 return _CandidateLastName;

  42:             }

  43:             set

  44:             {

  45:                 _CandidateLastName = value;

  46:                 MarkColumnModified("CandidateLastName");

  47:             }

  48:         }

  49:         string _CandidateLastName;

  50:  

  51:         [Column] 

  52:         public DateTime CandidateBirthDate 

  53:         { 

  54:             get

  55:             {

  56:                 return _CandidateBirthDate;

  57:             }

  58:             set

  59:             {

  60:                 _CandidateBirthDate = value;

  61:                 MarkColumnModified("CandidateBirthDate");

  62:             }

  63:         }

  64:         DateTime _CandidateBirthDate;

  65:  

  66:         [Column] 

  67:         public byte[] CandidatePhoto 

  68:         { 

  69:             get

  70:             {

  71:                 return _CandidatePhoto;

  72:             }

  73:             set

  74:             {

  75:                 _CandidatePhoto = value;

  76:                 MarkColumnModified("CandidatePhoto");

  77:             }

  78:         }

  79:         byte[] _CandidatePhoto;

  80:  

  81:         [Column] 

  82:         public int CandidateCountry 

  83:         { 

  84:             get

  85:             {

  86:                 return _CandidateCountry;

  87:             }

  88:             set

  89:             {

  90:                 _CandidateCountry = value;

  91:                 MarkColumnModified("CandidateCountry");

  92:             }

  93:         }

  94:         int _CandidateCountry;

  95:  

  96:         [Column] 

  97:         public int CandidateCity 

  98:         { 

  99:             get

 100:             {

 101:                 return _CandidateCity;

 102:             }

 103:             set

 104:             {

 105:                 _CandidateCity = value;

 106:                 MarkColumnModified("CandidateCity");

 107:             }

 108:         }

 109:         int _CandidateCity;

 110:  

 111:         [Column] 

 112:         public string CandidateEmail 

 113:         { 

 114:             get

 115:             {

 116:                 return _CandidateEmail;

 117:             }

 118:             set

 119:             {

 120:                 _CandidateEmail = value;

 121:                 MarkColumnModified("CandidateEmail");

 122:             }

 123:         }

 124:         string _CandidateEmail;

 125:  

 126:     }

 127: [TableName("CountryInfo")]

 128: [PrimaryKey("CountryID")]

 129: [ExplicitColumns]

 130: public partial class CountryInfo : PetaPocoDB.Record<CountryInfo>  

 131: {

 132:     [Column] 

 133:     public int CountryID 

 134:     { 

 135:         get

 136:         {

 137:             return _CountryID;

 138:         }

 139:         set

 140:         {

 141:             _CountryID = value;

 142:             MarkColumnModified("CountryID");

 143:         }

 144:     }

 145:     int _CountryID;

 146:  

 147:     [Column] 

 148:     public string CountryName 

 149:     { 

 150:         get

 151:         {

 152:             return _CountryName;

 153:         }

 154:         set

 155:         {

 156:             _CountryName = value;

 157:             MarkColumnModified("CountryName");

 158:         }

 159:     }

 160:     string _CountryName;

 161:  

 162: }

 163:  

 164: [TableName("CityInfo")]

 165: [PrimaryKey("CityID")]

 166: [ExplicitColumns]

 167: public partial class CityInfo : PetaPocoDB.Record<CityInfo>  

 168: {

 169:     [Column] 

 170:     public int CityID 

 171:     { 

 172:         get

 173:         {

 174:             return _CityID;

 175:         }

 176:         set

 177:         {

 178:             _CityID = value;

 179:             MarkColumnModified("CityID");

 180:         }

 181:     }

 182:     int _CityID;

 183:  

 184:     [Column] 

 185:     public string CityName 

 186:     { 

 187:         get

 188:         {

 189:             return _CityName;

 190:         }

 191:         set

 192:         {

 193:             _CityName = value;

 194:             MarkColumnModified("CityName");

 195:         }

 196:     }

 197:     string _CityName;

 198:  

 199:     [Column] 

 200:     public int CountryID 

 201:     { 

 202:         get

 203:         {

 204:             return _CountryID;

 205:         }

 206:         set

 207:         {

 208:             _CountryID = value;

 209:             MarkColumnModified("CountryID");

 210:         }

 211:     }

 212:     int _CountryID;

 213:  

 214: }

Bien el primer paso vamos a ver como realizar un conjunto  de consultas a la Base de Datos:

Consultar Toda la Tabla

 

   1: var db = PetaPocoDB.GetInstance();

   2: var candidates = db.Fetch<PetaPoco.CandidateInfo>("SELECT * FROM CANDIDATEINFO");

   3: foreach (var candidate in candidates)

   4: {

   5:     Console.WriteLine(String.Format("Name: {0}", candidate.CandidateName));

   6: }

 

Consultar Filtrando

   1: var candidates = db.Fetch<PetaPoco.CandidateInfo>("SELECT * FROM CANDIDATEINFO WHERE CANDIDATELASTNAME LIKE @0","%Puertas%");

   2:  

   3:            foreach (var candidate in candidates)

   4:            {

   5:                Console.WriteLine(String.Format("Name: {0}", candidate.CandidateName));

   6:            }

 

   1: var candidates = db.Fetch<PetaPoco.CandidateInfo>(PetaPoco.Sql.Builder.

   2:                                                                     Select("*").

   3:                                                                     From("CandidateInfo").

   4:                                                                     Where("CandidateLastName like @0", "%Puertas%"));

   5:  

   6:             foreach (var candidate in candidates)

   7:             {

   8:                 Console.WriteLine(String.Format("Name: {0}", candidate.CandidateName));

   9:             }

 

Consulta Scalar

   1: var results = db.ExecuteScalar<long>("SELECT COUNT(*) FROM CANDIDATEINFO");

   2: Console.WriteLine(results.ToString());

Insertar un Dato

   1: var candidate = new CandidateInfo

   2:     {

   3:         CandidateName = "Álvaro",

   4:         CandidateLastName = "Torrecilla",

   5:         CandidateEmail = "a@torrecilla.com",

   6:         CandidateBirthDate = Convert.ToDateTime("30/12/2009"),

   7:         CandidateCountry = 1,

   8:         CandidateCity = 1

   9:     };

  10: db.Insert(candidate);

Actualizar un Dato

   1: var candidate = new CandidateInfo

   2: {

   3:     CandidateName = "Álvaro",

   4:     CandidateLastName = "Torrecilla",

   5:     CandidateEmail = "a@torrecilla.com",

   6:     CandidateBirthDate = Convert.ToDateTime("28/12/2009"),

   7:     CandidateCountry = 1,

   8:     CandidateCity = 1

   9: };

  10: db.Update(candidate, new Guid("00000000-0000-0000-0000-000000000000"));

Eliminar un Dato

   1: db.Delete<CandidateInfo>(new Guid("00000000-0000-0000-0000-000000000000"));

Peculiaridades

Durante las pruebas y examinando el código me he encontrado con algunas cosas curiosas:

– Los métodos IsNew y Save solo funcionan con columnas definidas en la Base de datos como Identity.

– Para Base de Datos Oracle se puede configurar a una determinada columna una secuencia.

– El mapeo de campos de tipo Image de SQL server los realiza a NVarchar por lo que genera problemas a la hora de generar inserciones a la BD.

– Si existe un valor por defecto definido en una columna de la BD, no lo utiliza, sino que genera el comando completo.

 

Conclusiones

PetaPoco nos permite de una forma rápida generar las clases POCO de nuestra BD gracias a la utilización de unas Plantillas T4, lo cual nos permite configurar y aportar nuestras propias mejoras a las mismas.

Tiene cosas mejorables, pero al ser un fichero que agregamos a nuestro proyecto, podemos incluir las características que consideremos oportunas.

[ORM-Lite] Massive

Introducción

Hace unos días realice una entrada sobre Dapper un mini ORM, y como ya comente iba a realizar una serie sobre más ejemplos de ORM ligeros. En esta ocasión le ha tocado a Massive.

¿Qué es Massive?

Bien Massive es otro ORM ligero creado por Rob Conery(Blog y Twitter), que nos permite realizar consultas a una Base de datos y su explotación en un sistema a través de objetos Expando.

¿Por dónde empezar?

Para poder trabajar con Massive, tenemos dos posibilidades:

– Agregarlo como un paquete a nuestro proyecto desde Nuget (No es la ultima versión a fecha de hoy)

– Descargar el único fichero que genera desde GitHub.

Existe un fichero disponible para los siguientes tipos de Base de datos:

– Sql Server.

– Oracle.

– SQLLite.

– PostgreSQL.

Para este post vamos a utilizar el de SQL Server, y la misma tabla que vimos el otro día sobre “CandidateInfo”.

Algunos Ejemplos

Vamos a ver algunos ejemplos de la utilización de Massive dentro de un proyecto de .NET.

El primer paso, como decía antes, será agregar la clase de Massive a nuestro proyecto.

A continuación vamos a definir una clase que va servir para trabajar con la tabla CandidateInfo de nuestra Base de Datos:

   1: public class CandidateInfo : Massive.DynamicModel 

   2:  {

   3:     public CandidateInfo() : base("MyCV","CandidateInfo","CandidateId")

   4:     {

   5:          

   6:     }

   7:  }

 

Nuestra clase CandidateInfo va a heredar de la Clase DynamicModel, y vamos a hacer que se implemente el constructor base, indicándole los siguientes parámetros:

– Cadena de Conexión: conexión existente en el fichero de configuración de la aplicación que queremos utilizar.

– Nombre de la tabla que queremos utilizar.

– Campo clave de la tabla.

 

Una vez definida nuestra clase vamos a ver los propios ejemplos:

Consulta de Datos

Para realizar una simple consulta de datos, vamos a necesitar declarar un objeto del tipo de la clase anteriormente indicada:

 
   1: var candidateInfo = new App_Code.CandidateInfo();

 

Una vez definida nuestra tabla, vamos a obtener:

– Todos los resultados:

   1: var query = candidateInfo.All();

   2: foreach (var candidato in query)

   3: {

   4:     var dictionary = ObjectExtensions.ToDictionary(candidato);

   5:     foreach (var item in dictionary)

   6:     {

   7:         Console.WriteLine(String.Format("{0} : {1}", item.Key, item.Value));

   8:     }

   9: }  

 

– Resultados Filtrados:

 
   1: var query = candidateInfo.All("CandidateName like '%a%'");

   2: foreach (var candidato in query)

   3: {

   4:     var dictionary = ObjectExtensions.ToDictionary(candidato);

   5:     foreach (var item in dictionary)

   6:     {

   7:         Console.WriteLine(String.Format("{0} : {1}", item.Key, item.Value));

   8:     }

   9: }  

El Método All, dispone de los siguientes parámetros:

– Where: para realizar un filtro.

– Order: para establecer el orden de los resultados.

– Limit: Para establecer un Top de los resultados.

– Columnas: para indicar que columnas se van a establecer.

– Parámetros: se pueden establecer los valores del filtro como parámetros.

Todos los parámetros son opcionales.

 

Al tratarse de tipos anónimos podríamos realizar la siguiente consulta:

   1: var query =  candidateInfo.Query(

   2:                "Select CandidateName,CountryName from CandidateInfo inner join CountryInfo on CountryId=CandidateCountry");

   3:  

   4:            

   5:            foreach(var dato in query)

   6:            {

   7:  

   8:                var dictionary = ObjectExtensions.ToDictionary(dato);

   9:                foreach (var item in dictionary)

  10:                {

  11:                    Console.WriteLine(String.Format("{0} : {1}", item.Key, item.Value));

  12:                }

  13:            }

Como crear un  nuevo registro:

   1: var dato = candidateInfo.Insert(new

   2:                              {

   3:                                  CandidateName = "Javier",

   4:                                  CandidateLastName = "Torrecilla",

   5:                                  CandidateBirthDate = DateTime.Now,

   6:                                  CandidateCountry = 1,

   7:                                  CandidateCity = 1,

   8:                                  CandidateEmail = "r@yahoo.es"

   9:                              });

 

Como realizar la eliminación de un registro:

   1:  int delete = candidateInfo.Delete(@where: "CandidateName =@0", args: "j");

Como realizar una actualización de un dato:

   1:   candidateInfo.Update(new {CandidateLastName = "Torrecilla Puertas"}, guid);

 

También existe la posibilidad de ejecutar una sentencia sql, un ojbeto DBCommand o una lista de DBCommand a través del método Execute.

 

Conclusiones

Massive, nos provee una forma bastante rápida de manejar nuestra Base de Datos, sin tener que escribir gran cantidad de código.

Nos da la flexibilidad que aporta dynamic y el trabajo con tipos anónimos.

Por otro lado, al ser un único fichero podemos realizar nuestras propias modificaciones, aunque nos salgamos de la línea definida por Rob.

El próximo post de la serie será acerca de PetaPoco

Dapper Dot Net

Introducción

Hace unos días Jorge Gamba comentaba en un twit algunos ORMS Ligeros para utilizar en algunos proyectos donde realmente no se requiere la potencia de Entity Framework o NHibernate. Esto venía a través de un intenso debate (y lucha) para intentar dejar a un lado los DataSets y DataTables que se utilizaban antiguamente.

¿Qué es Dapper?

Podríamos decir que Dapper es un mini-ORM (o un ORM Lite) compuesto por un conjunto de clases que nos van a permitir mapear nuestra base de datos con nuestras clases POCO.

Este “ORM” es independiente de la Base de Datos, puesto que hace uso de la interfaz IDbConnection, ya la mayoría de conexiones (SQL Server, Oracle, My SQL, OLEDB) tienen una Clase connection que implementa dicha interfaz.

¿Por donde empezar?

Para poder empezar a utilizar Dapper (no lo busquéis en Nuget, no está aún), tenéis que descargar un único fichero CS y agregarlo en vuestro proyecto de Visual Studio.

Algunos ejemplos

Vamos a ver algunos ejemplos muy sencillos de como utilizar Dapper, y ver la simplicidad de utilización que nos aporta:

Lo primero que vamos a hacer es declarar una variable conexión del tipo IDbConnection, para que podamos modificar el motor de Base de Datos de una forma muy rapida:

 

   1: IDbConnection connection;

CONSULTA DE DATOS

El primer ejemplo lo voy a realizar sobre una Base de Datos SQL Server Express, y contra una tabla “Candidates” que voy a mapear a una clase como la siguiente:

   1: public class CandidateInfo

   2:  {

   3:     public Guid CandidateId { get; set; }

   4:     public string CandidateName { get; set; }

   5:     public string CandidateLastName { get; set; }

   6:     public DateTime CandidateBirthDate { get; set; }

   7:     public byte[] CandidatePhoto { get; set; }

   8:     public int CandidateCountry { get; set; }

   9:     public int CandidateCity { get; set; }

  10:     public string CandidateEmail { get; set; }

  11:  }

Para obtener el conjunto de resultados de la tabla Candidates, ejecutariamos este bloque de código:

   1: using (connection = new SqlConnection(@"Data Source=.SQLEXPRESS;Initial Catalog=MYCV;Integrated Security=True"))

   2: {

   3:     connection.Open();

   4:     var Candidates = Dapper.SqlMapper.Query<CandidateInfo>(connection, "Select * From CandidateInfo");

   5:  

   6: }

La Clase SqlMapper, está incluida en el fichero que hemos descargado. A través del método genérico Query se realiza el “mapeo” de la consulta que enviamos como parámetro al método.

Si en lugar de utilizar SQL Server estuviéramos usando cualquier otro tipo de BD, bastaría con modificar el bloque using y utilizar la clase de conexión del motor que tengamos.

El segundo ejemplo, va a ser para devolver un único tipo de dato simple:

   1: using (connection = new SqlConnection(@"Data Source=.SQLEXPRESS;Initial Catalog=MYCV;Integrated Security=True"))

   2:     {

   3:         connection.Open();

   4:         var Candidates = Dapper.SqlMapper.Query<int>(connection, "Select CandidateCountry From CandidateInfo");

   5:     }

   6:                 

En este caso vamos a obtener un conjunto de datos “int”.

A continuación, vamos a ver como utilizar parámetros:

   1: using (connection = new SqlConnection(@"Data Source=.SQLEXPRESS;Initial Catalog=MYCV;Integrated Security=True"))

   2: {

   3:     connection.Open();

   4:     var Candidates = Dapper.SqlMapper.Query<CandidateInfo>

   5:         (connection,

   6:         "Select * From CandidateInfo where CandidateLastName like @a",

   7:        new { a = "%Puertas%" });

   8: }

El tercer parámetro del método Query, va a ser un array dinámico de Pares valor que se enlazarán a los parámetros de la query definida.

Ejemplo de consulta multiple, vamos a definir un conjunto de consultas contra el servidor y sus relativos Mapeos. Para este ejemplo vamos a hacer uso también de la siguiente clase:

   1: public class CountryInfo

   2: {

   3:     public int CountryID { get; set; }

   4:     public string CountryName { get; set; }

   5: }

   6:  

El ejemplo consistirá en obtener la información de Candidates y Country:

   1: using (connection = new SqlConnection(@"Data Source=.SQLEXPRESS;Initial Catalog=MYCV;Integrated Security=True"))

   2: {

   3:     connection.Open();

   4:     var sql =

   5:         @"

   6:         select * from CandidateInfo

   7:         select * from CountryInfo";

   8:         using (var multi = Dapper.SqlMapper.QueryMultiple(connection, sql))

   9:         {

  10:             var candidates = multi.Read<CandidateInfo>().ToList();

  11:             var countries = multi.Read<CountryInfo>().ToList();

  12:         }

  13: }

El método QueryMultiple se encargará de dividir  las consulta SQL en varías, y el objeto GridReader existente en el fichero descargado se encargará de realizar los mapeos de cada una de las consultas a través del método genérico Read.

Por otro lado, también existe la posibilidad de utilizar procedimientos almacenados y mapearlo a las entidades de las que disponemos.

EJECUCIÓN DE SENTENCIAS

Una cosa que se encuentra en la cola de solicitudes / incidencias de Dapper, es que no tiene un método de inserción, actualización o borrado directo, pero se pueden ejecutar este tipo de sentencias de manera muy sencilla:

Ejemplo de Inserción simple:

   1: using (connection = new SqlConnection(@"Data Source=.SQLEXPRESS;Initial Catalog=MYCV;Integrated Security=True"))

   2:            {

   3:                connection.Open();

   4:               

   5:                Dapper.SqlMapper.Execute(connection, 

   6:                                         "INSERT INTO COUNTRYINFO (COUNTRYNAME) VALUES(@COUNTRY)",

   7:                                        new { COUNTRY = "Benin" });

   8:            }

Ejemplo de Inserción múltiple (va a ejecutar la misma sentencia, cambiando los valores de los parámetros según la lista de valores que dispongamos):

   1: using (connection = new SqlConnection(@"Data Source=.SQLEXPRESS;Initial Catalog=MYCV;Integrated Security=True"))

   2: {

   3:     connection.Open();

   4:  

   5:     var lista = new List<dynamic>();

   6:     lista.Add(new { COUNTRY = "Italia" });

   7:     lista.Add(new { COUNTRY = "Francia" });

   8:     lista.Add(new { COUNTRY = "Alemania" });

   9:     Dapper.SqlMapper.Execute(connection, "INSERT INTO COUNTRYINFO (COUNTRYNAME) VALUES(@COUNTRY)",

  10:        lista);

  11: }

 

Existe la posibilidad de hacer uso de transacciones (IDbTransaction) a la hora de ejecutar las sentencias, ya que el método Execute dispone de un parámetro opcional que la puede administrar, pero para hacer la confirmación (“Commit”) sería necesario ejecutarlo después de la ejecución.

Conclusiones

Dapper presenta algunas funcionalidades curiosas y unos tiempos (que veremos en una futura comparativa que voy a publicar) bastante interesantes, y que habría que tenerlo en cuenta, tal como decía al principio, si no necesitamos la potencia de algo mas grande.

 

Voy a realizar el análisis de otras herramientas similares a Dapper y al final publicaré una comparativa sobre tiempos.