You are currently browsing the tag archive for the ‘.NET’ tag.

Hasta la versión 2.0 del Framework.NET, la declaración de un delegado sólo era posible programando métodos con nombre. A partir de la versión 2.0 se introduce el concepto Método Anónimo.

Un método anónimo nos permite definir una función de manera implícita, sin necesidad de declararla dándole un nombre.

Como los Métodos Anónimos no tienen nombre, la forma de invocarlos es a través de un delegado.

Usar un Método Anónimo es pasar como parámetro a un bloque de código definido como tipo Delegate, sin asignarle un nombre.

Por ejemplo, si en el constructor de un formulario insertamos la siguiente línea de código:

 

  this.Click += delegate { MessageBox.Show(@”Funciona.”); };

 

Al hacer un click sobre el formulario se desplegará un MessageBox que muestre el string “Funciona”.

En el ejemplo podemos ver que en la misma línea se define el Delegate y su implementación. Eso es exactamente un Método Anónimo.

Los Métodos Anónimos reducen la sobrecarga de código al crear instancias de delegados sin tener que crear métodos independientes.
En caso que quisiéramos capturar algún parámetro del evento, sólo tenemos que declarar los argumentos a continuación de la palabra delegate:

 

  this.Click += delegate(object sender, EventArgs e)

  {

      System.Windows.Forms.MessageBox.Show(@”Funciona. “ + e.ToString());

  };

 

Para el uso de Métodos Anónimos existen algunas restricciones:

  • No se puede invocar código no manejado en un Método Anónimo.
  • Las variables declaradas en el mismo bloque donde se declara un Método Anónimo se llaman Externas (Outer) al Método Anónimo. No pueden ser accedidos parámetros de ámbito externo calificados con ref u out.
  • En un Método Anónimo no pueden usarse goto, break ni continue si apunta fuera del ámbito del Método Anónimo, tampoco pueden usarse desde fuera del Método Anónimo si el destino se encuentra dentro del bloque del Método Anónimo. Más allá de Método Anónimo, siempre es bueno evitar el uso de goto, break y continue.
  • No pueden usarse Método Anónimo en el lado izquierdo del operador is.

Los Métodos Anónimos son ideales en el manejo de eventos cuando lo que hay que hacer son cosas simples permitiendo un código compacto. Pero no hay que perder de vista que el abuso de esta técnica puede tornar nuestro código como algo muy difícil de leer y entender.

En el ámbito del desarrollo de software, siempre es bueno eliminar dependencias. En otros términos, bajar el nivel de acoplamiento.

Hay casos en que se logra más directamente y otros en que no es tan simple.

Para estos últimos contamos con un pattern llamado Dependency Injection, Inyección de Dependencia, término por primera vez usado por Martin Fowler. Si te interesa ir a la fuente, acá está su documento sobre Dependency Injection.

En este post voy a intentar explicar que viene a resolver este patrón y sus distintas implementaciones.

Cuando tenemos un objeto que necesita de otro para funcionar correctamente, tenemos definida una dependencia. Esta dependecia puede ser altamente acoplada, tight coupling, o levemente acoplada, loose coupling. Si el acoplamiento es bajo el objeto independiente es fácilmente reemplazable, si, en cambio el acoplamiento es alto, el reemplazo no es fácil y se dificulta el diseño de tests unitarios.

Supongamos que tenemos una clase que define un producto, que necesita un componente, este componente puede provenir de distintos fabricantes, en general cubren las mismas funciones. Los productos son varios y alternativamente pueden valerse del componente de cualquier fabricante.

Si instanciamos uno de los componentes en forma directa necesitamos referenciarlo. Si el mercado nos exige cambiar de componente, tendríamos que eliminar la referencia anterior, crear la nueva, revisar si los métodos, funciones y propiedades son homogéneos, si no lo son, corregir el código donde sea necesario… Si a esto le agregamos que valdría la pena tener una versión con el primer componente y otra con el segundo, la situación empeora. Si hubiera cincuenta componentes alternativos, la situación ya sería inmanejable.

Acá es donde la necesidad del patrón Dependency Injection se hace evidente.

Supongamos que este conjunto de componentes ejecuta básicamente cuatro métodos, que son los que nosotros necesitamos: Initialize, Shutdown, Prepare y DoIt.

Entonces comencemos por escribir una interface que defina estos cuatro métodos:

[VB]

Public Interface ISomeHardware

Sub Initialization()

Sub Shutdown()

Sub Prepare()

Sub DoIt()

End Interface

[C#]

public interface ISomeHardware

{

void Initialization();

void Shutdown();

void Prepare();

void DoIt();

}

Las clases que definan los componentes deben implementar la interface ISomeHardware. En nuestro ejemplo, ComponentA y ComponentB:

[VB]

Public Class ComponentA

Implements ISomeHardware

Public Sub InitializationBegin() Implements ISomeHardware.Initialization

End Sub

Public Sub ShutdownBegin() Implements ISomeHardware.Shutdown

End Sub

Public Sub PrepareBegin() Implements ISomeHardware.Prepare

End Sub

Public Sub DoItBegin() Implements ISomeHardware.DoIt

End Sub

End Class

Public Class ComponentB

Implements ISomeHardware

Public Sub InitializationBegin() Implements ISomeHardware.Initialization

End Sub

Public Sub ShutdownBegin() Implements ISomeHardware.Shutdown

End Sub

Public Sub PrepareBegin() Implements ISomeHardware.Prepare

End Sub

Public Sub DoItBegin() Implements ISomeHardware.DoIt

End Sub

End Class

[C#]

class ComponentA : ISomeHardware

{

public void Initialization() { }

public void Shutdown() { }

public void Prepare() { }

public void DoIt() { }

}

class ComponentB : ISomeHardware

{

public void Initialization() { }

public void Shutdown() { }

public void Prepare() { }

public void DoIt() { }

}

Aclaración: Acá hago una simplificación a efecto didáctico. Las clases que definen ComponentA y ComponentB, en realidad debieran ser wrappers de las dll’s provistas por los fabricantes. Para no complicar inútilmente las dejaré así.

Veremos tres implementaciones del patrón Dependency Injection: por Constructor, por Setter y por Interface.

Por Constructor:

Ahora voy a usar otro patrón llamado Facade o Fachada, la idea es encapsular todos los aspectos complejos de un subsistema de clases en una única y simple interface. En nuestro caso, ProductFacade:

[VB]

Public Class ProductFacade

Private SomeHardware As ISomeHardware

Public Sub New(ByVal pSomeHardware As ISomeHardware)

SomeHardware = pSomeHardware

End Sub

End Class

[C#]

public class ProductFacade

{

private ISomeHardware SomeHardware;

public ProductFacade(ISomeHardware SomeHardware)

{

this.SomeHardware = SomeHardware;

}

}

En esta clase se implementa la Inyección de Dependencia por medio del constructor, que acepta un parámetro de entrada de tipo de interface ISomeHardware. Este punto es determinante. Cualquier clase que implemente la interface ISomeHardware es aceptada como parámetro del constructor de ProductFacade. Este es todo el secreto.

Entonces, para inyectar la dependencia de un componente de un fabricante u otro por Constructor, se hace así:

[VB]

Dim ComponentA As ISomeHardware = New ComponentA

Dim Product01 As ProductFacade = New ProductFacade(ComponentA)

Dim ComponentB As ISomeHardware = New ComponentB

Dim Product02 As ProductFacade = New ProductFacade(ComponentB)

[C#]

ISomeHardware componentA = new ComponentA();

ProductFacade product01 = new ProductFacade(componentA);

ISomeHardware componentB = new ComponentB();

ProductFacade product02 = new ProductFacade(componentB);

Realmente elegante.

Para las siguientes dos implementaciones del patrón, usaremos la misma interface ISomeHardware y la definición de las clases ComponentA y ComponentB.

Las variaciones estarán en la Fachada y en la Inyección de Dependencia.

Por Setter:

En este caso la Inyección de Dependencia se efectiviza a través de una propiedad definida en la Fachada.

[VB]

Public Class ProductFacade

Private mSomeHardware As ISomeHardware

Public Property SomeHardware() As ISomeHardware

Get

Return mSomeHardware

End Get

Set(ByVal value As ISomeHardware)

mSomeHardware = value

End Set

End Property

End Class

[C#]

public class ProductFacade

{

private ISomeHardware SomeHardware;

public ISomeHardware Component

{

get

{

return SomeHardware;

}

set

{

SomeHardware = value;

}

}

}

Para el caso de Setter, la Inyección de Dependencia queda determinada así:

[VB]

Dim ComponentA As ISomeHardware = New ComponentA

Dim Product01 As ProductFacade = New ProductFacade()

Product01.SomeHardware = ComponentA

Dim ComponentB As ISomeHardware = New ComponentB

Dim Product02 As ProductFacade = New ProductFacade()

Product02.SomeHardware = ComponentB

[C#]

ISomeHardware componentA = new ComponentA();

ProductFacade product01 = new ProductFacade();

product01.Component = componentA;

ISomeHardware componentB = new ComponentB();

ProductFacade product02 = new ProductFacade();

product02.Component = componentB;

Por Interface:

Ahora la Inyección de Dependencia está implementada sobre un método que acepta un parámetro de tipo de interface ISomeHardware.

[VB]

Public Class ProductFacade

Private mSomeHardware As ISomeHardware

Public Sub SetComponent(ByVal pSomeHardware As ISomeHardware)

mSomeHardware = pSomeHardware

End Sub

End Class

[C#]

public class ProductFacade

{

private ISomeHardware SomeHardware;

public void SetComponent(ISomeHardware SomeHardware)

{

this.SomeHardware = SomeHardware;

}

}

Cuando la Inyección de Dependencia es por Interface, se invoca de esta forma:

[VB]

Dim ComponentA As ISomeHardware = New ComponentA

Dim Product01 As ProductFacade = New ProductFacade()

Product01.SetComponent(ComponentA)

Dim ComponentB As ISomeHardware = New ComponentB

Dim Product02 As ProductFacade = New ProductFacade()

Product02.SetComponent(ComponentB)

[C#]

ISomeHardware componentA = new ComponentA();

ProductFacade product01 = new ProductFacade();

product01.SetComponent(componentA);

ISomeHardware componentB = new ComponentB();

ProductFacade product02 = new ProductFacade();

product02.SetComponent(componentB);

Las tres implementaciones difieren sutilmente entre si. La diferencia más importante que yo encuentro es que, Por Constructor, nos obliga a inyectar al crear la clase, mientras que las otras modalidades, la difieren para más adelante.

La diferencia entre estas dos últimas está en si se prefiere inyectar la dependencia en el set de una propiedad, Por Setter, o como argumento de un parámetro de entrada a un método o función, Por Interface. Cuestión de gusto sintáctico.

Espero que les sea útil. 🙂

En este post voy a escribir sobre métodos genéricos. La idea es presentar un método que acepte dos parámetros de tipo genérico.

Para quien no maneje el concepto de Generics recomiendo antes leer este post.

Vamos a usar un proyecto de tipo Consola con Visual Studio .NET, y la misma clase Libro que usé en el post anterior.

Primero, referenciamos lo siguiente:

C#

using System;

using System.Collections.Generic;

VB.NET

Imports System

Imports System.Collections.Generic

Ahora si, la clase Libro:

C#

public class Libro

{

public string sTitulo, sAutor;

public Libro(string Titulo, string Autor)

{

sTitulo = Titulo;

sAutor = Autor;

}

public override string ToString()

{

return string.Format(“{0} escribió {1}.”, sAutor, sTitulo);

}

}

VB.NET

Public Class Libro

Dim sTitulo, sAutor As String

Public Sub New(ByVal Titulo As String, ByVal Autor As String)

MyBase.New()

sTitulo = Titulo

sAutor = Autor

End Sub

Public Overrides Function ToString() As String

Return String.Format(“{0} escribió {1}.”, sAutor, sTitulo)

End Function

End Class

En el módulo principal codificamos nuestro método MostrarPorConsola que podrá recibir dos parámetros de cualquier tipo:

C#

static void MostrarPorConsola<T>(ref T a, ref T b)

{

Console.WriteLine(“Primer  argumento del método MostrarPorConsola(), {0}, de tipo {1}.”, a, typeof(T));

Console.WriteLine(“Segundo argumento del método MostrarPorConsola(), {0}, de tipo {1}.”, b, typeof(T));

}

VB:NET

Private Sub MostrarPorConsola(Of T)(ByRef a As T, ByRef b As T)

Console.WriteLine(“Primer  argumento del método MostrarPorConsola(), {0}, de tipo {1}.”, a, GetType(T))

Console.WriteLine(“Segundo argumento del método MostrarPorConsola(), {0}, de tipo {1}.”, b, GetType(T))

End Sub

El método imprime una línea con el primer argumento y otra más con el segundo, sin interesar de que tipo son.

En la rutina principal consumimos al método genérico, primero declaro dos enteros, les asigno un valor y se los paso como parámetros al método MostrarPorConsola, luego declaro dos objetos de tipo Libro, los hidrato y se los paso como parámetros al mismo método.

Parece mentira… pero funciona 🙂

A partir de la versión 2.0 del Framework .NET contamos con el namespace Generics que define una cantidad de clases e interfaces que permiten administrar sub ítems en una variedad de contenedores. En este post me voy a dedicar al contenedor List<T> o List(Of T) según sea C# o VB.NET

Veamos un ejemplo de colección sin Generics. Con Visual Studio .NET, abrimos un nuevo proyecto de Consola, si queremos manipular un conjunto de objetos, por ejemplo Libros, en primer lugar tenemos que referenciar lo siguiente:

C#

using System;

using System.Collections;

VB.NET

Imports System

Imports System.Collections

y definir la clase Libro:

C#

public class Libro

{

public string sTitulo, sAutor;

public Libro(string Titulo, string Autor)

{

sTitulo = Titulo;

sAutor = Autor;

}

public override string ToString()

{

return string.Format(“{0} escribió {1}.”, sAutor, sTitulo);

}

}

VB.NET

Public Class Libro

Dim sTitulo, sAutor As String

Public Sub New(ByVal Titulo As String, ByVal Autor As String)

MyBase.New()

sTitulo = Titulo

sAutor = Autor

End Sub

Public Overrides Function ToString() As String

Return String.Format(“{0} escribió {1}.”, sAutor, sTitulo)

End Function

End Class

Además, necesitamos programar otra clase, por ejemplo BibliotecaCollection que implemente la intefaz IEnumerable. En nuestro caso le agregaremos un método AgregarLibro que recibe un parámetro de tipo Libro y lo agrega a un ArrayList que la clase encapsula.

El tipo ArrayList en el namespace Collection, es el equivalente al tipo List<T> o List(Of T) del namespace Generics

C#

public class BibliotecaCollection : IEnumerable

{

private ArrayList arBiblioteca = new ArrayList();

public BibliotecaCollection() { }

public void AgregarLibro(Libro pLibro)

{

arBiblioteca.Add(pLibro);

}

IEnumerator IEnumerable.GetEnumerator()

{

return arBiblioteca.GetEnumerator();

}

}

VB.NET

Public Class BibliotecaCollection

Implements IEnumerable

Private arBiblioteca As ArrayList = New ArrayList

Public Sub New()

MyBase.New()

End Sub

Public Sub AgregarLibro(ByVal pLibro As Libro)

arBiblioteca.Add(pLibro)

End Sub

Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator

Return arBiblioteca.GetEnumerator

End Function

End Class

Cualquier otro tipo de función que queramos que soporte la clase, debemos codificarla. Para este ejemplo sólo vamos a dejar definida la función AgregarLibro.

Ahora si podemos hacer uso de nuestra biblioteca en una aplicación de consola:

C#

class Program

{

static void Main(string[] args)

{

Console.WriteLine(“Collections\n”);

BibliotecaCollection miBiblioteca = new BibliotecaCollection();

miBiblioteca.AgregarLibro(new Libro(“Trópico de Cáncer”, “Henry Miller”));

miBiblioteca.AgregarLibro(new Libro(“Yonqui”, “William Burrougs”));

miBiblioteca.AgregarLibro(new Libro(“Esperando a Godot”, “Samuel Beckett”));

miBiblioteca.AgregarLibro(new Libro(“La senda del perdedor”, “Charles Bukowski”));

miBiblioteca.AgregarLibro(new Libro(“En el camino”, “Jacques Kerouac”));

foreach (Libro pLibro in miBiblioteca)

Console.WriteLine(pLibro);

Console.ReadLine();

}

}

VB.NET

Module Module1

Sub Main()

Console.WriteLine(“Collections”)

Dim miBiblioteca As BibliotecaCollection = New BibliotecaCollection

miBiblioteca.AgregarLibro(New Libro(“Trópico de Cáncer”, “Henry Miller”))

miBiblioteca.AgregarLibro(New Libro(“Yonqui”, “William Burrougs”))

miBiblioteca.AgregarLibro(New Libro(“Esperando a Godot”, “Samuel Beckett”))

miBiblioteca.AgregarLibro(New Libro(“La senda del perdedor”, “Charles Bukowski”))

miBiblioteca.AgregarLibro(New Libro(“En el camino”, “Jacques Kerouac”))

For Each pLibro As Libro In miBiblioteca

Console.WriteLine(pLibro)

Next

Console.ReadLine()

End Sub

End Module

Ahora, con Generics, no necesitamos de la clase BibliotecaCollection, ni implementar la interfaz IEnumerable, ni el método adicional AgregarLibro.

Sólo necesitamos declarar una lista genérica de Libros de la siguiente forma:

C#

List<Libro> miBiblioteca = new List<Libro>();

VB.NET

Dim miBiblioteca As List(Of Libro) = New List(Of Libro)

Tenemos que modificar levemente las referencias a:

C#

using System;

using System.Collections.Generic;

VB.NET

Imports System

Imports System.Collections.Generic

Y con la misma declaración de la clase Libro que tenemos más arriba usamos nuestra lista genérica de Libros de esta forma:

C#

class Program

{

static void Main(string[] args)

{

Console.WriteLine(“Generics\n”);

List<Libro> miBiblioteca = new List<Libro>();

miBiblioteca.Add(new Libro(“Trópico de Cáncer”, “Henry Miller”));

miBiblioteca.Add(new Libro(“Yonqui”, “William Burrougs”));

miBiblioteca.Add(new Libro(“Esperando a Godot”, “Samuel Beckett”));

miBiblioteca.Add(new Libro(“La senda del perdedor”, “Charles Bukowski”));

miBiblioteca.Add(new Libro(“En el camino”, “Jacques Kerouac”));

foreach (Libro pLibro in miBiblioteca)

Console.WriteLine(pLibro);

Console.ReadLine();

}

}

VB.NET

Module Module1

Sub Main()

Console.WriteLine(“Generics”)

Dim miBiblioteca As List(Of Libro) = New List(Of Libro)

miBiblioteca.Add(New Libro(“Trópico de Cáncer”, “Henry Miller”))

miBiblioteca.Add(New Libro(“Yonqui”, “William Burrougs”))

miBiblioteca.Add(New Libro(“Esperando a Godot”, “Samuel Beckett”))

miBiblioteca.Add(New Libro(“La senda del perdedor”, “Charles Bukowski”))

miBiblioteca.Add(New Libro(“En el camino”, “Jacques Kerouac”))

For Each pLibro As Libro In miBiblioteca

Console.WriteLine(pLibro)

Next

Console.ReadLine()

End Sub

End Module

Como vemos en el namespace Generics se resuelve todo lo que antes debíamos codificar en una clase adicional, en este ejemplo BibliotecaCollection.

Los otros contenedores del namespace que no cubro en este post son:

Collection<T>, la base de una colección genérica,

Comparer<T>, Comparación de igualdad entre dos objetos genéricos,

Dictionary<K, V>, colección genérica de pares nombre/valor,

Queue<T>, implementación genérica de lista FIFO (first-in, first-out),

SortedDictionary<K, V>, implementación genérica de un conjunto ordenado de pares nombre/valor,

Stack<T>, implementación genérica de lista LIFO (last-in, first-out),

LinkedList<T>, implementación genérica de una lista doblemente vinculada,

ReadOnlyCollection<T>, implementación de un conjunto de items genéricos de sólo lectura.

En fin, contamos con una nueva serie de elementos para aprovechar la potencia del Framework.

Siguiendo con la serie de posts sobre Cooperator Framework, esta vez voy a mostrarles cómo codificar un ABM de lo más simple.

Seguramente van a encontrar muchas formas de mejorar el ejemplo. La idea es brindar un punto de partida.

Para los que todavía no conocen Cooperator Framework les recomiendo los posts anteriores sobre el tema:

Introducción a Cooperator Framework,

Cooperator, un ejemplo sencillo para comenzar y

Cooperator, modificando el modelo.

Supongamos que tenemos una entidad en nuestro dominio denominada Occupations, con un ID autonumérico y un campo Descripcion de tipo VARCHAR.

Vamos a tener un formulario Occupations que contiene una DataGridView, de nombre dgDatos, con la lista de Occupations y un formulario Occupation con el que veremos el detalle de la Occupation al agregar o modificar un registro.

En el formulario Occupations tendremos una botonera y/o menú con las opciones de Agregar, Eliminar y Modificar.

La grilla de Occupations la completo con el siguiente código:

C#

OccupationList mOccupationList = OccupationMapper.Instance().GetAll;

OccupationListView mOccupationListView = new OccupationListView(mOccupationList);

mOccupationListView.Sort(“Description”, true);

dgDatos.DataSource = mOccupationListView;

dgDatos.Columns[ColumnaGrilla.ID].Visible = false;

dgDatos.Columns[ColumnaGrilla.Description].HeaderText = “Ocupación”;

VB.NET

Dim mOccupationList As OccupationList = OccupationMapper.Instance().GetAll

Dim mOccupationListView As OccupationListView = New OccupationListView(mOccupationList)

mOccupationListView.Sort(“Description”, True)

dgDatos.DataSource = mOccupationListView

dgDatos.Columns(ColumnaGrilla.ID).Visible = False

dgDatos.Columns(ColumnaGrilla.Description).HeaderText = “Ocupación”

En la primera linea declaro una lista que completo con el método GetAll del Mapper de Occupation. Luego completo una ListView a partir de la lista que complete con el GetAll. En la tercera linea le practico un ordenamiento, en la cuarta linea le paso este ListView al DataSource de la DataGridView con lo que queda poblada. En la quinta linea hago invisible la columna del ID y en la última linea de código le doy un título a la columna Descripcion.

Veamos ahora el código correspondiente a Eliminar:

C#

if ((dgDatos.SelectedRows == null))

{

MsgBox(“No se seleccionó ninguna fila de la grilla.”, ((MsgBoxStyle)((MsgBoxStyle.Exclamation + MsgBoxStyle.OkOnly))));

}

else if ((MsgBox((“¿Está a punto de eliminar a la Ocupación “

+ (dgDatos.SelectedRows.Item[0].Cells[ColumnaGrilla.Description].Value.ToString.Trim + “?”)), ((MsgBoxStyle)((MsgBoxStyle.Exclamation + (MsgBoxStyle.YesNo + MsgBoxStyle.DefaultButton2))))) == MsgBoxResult.Yes))

{

OccupationMapper.Instance.Delete(((int)(dgDatos.SelectedRows.Item[0].Cells[ColumnaGrilla.ID].Value)));

MsgBox((“La Ocupación ” + (dgDatos.SelectedRows.Item[0].Cells[ColumnaGrilla.Description].Value.ToString.Trim + ” se eliminó exitosamente.”)), ((MsgBoxStyle)((MsgBoxStyle.Information + MsgBoxStyle.OkOnly))));

CargarGrilla();

}

VB.NET

If dgDatos.SelectedRows Is Nothing Then

MsgBox(“No se seleccionó ninguna fila de la grilla.”, CType(MsgBoxStyle.Exclamation + MsgBoxStyle.OkOnly, MsgBoxStyle))

Else

If MsgBox(“¿Está a punto de eliminar a la Ocupación “ + dgDatos.SelectedRows.Item(0).Cells(ColumnaGrilla.Description).Value.ToString.Trim + “?”, CType(MsgBoxStyle.Exclamation + MsgBoxStyle.YesNo + MsgBoxStyle.DefaultButton2, MsgBoxStyle)) = MsgBoxResult.Yes Then

OccupationMapper.Instance.Delete(CType(dgDatos.SelectedRows.Item(0).Cells(ColumnaGrilla.ID).Value, Integer))

MsgBox(“La Ocupación “ + dgDatos.SelectedRows.Item(0).Cells(ColumnaGrilla.Description).Value.ToString.Trim + ” se eliminó exitosamente.”, CType(MsgBoxStyle.Information + MsgBoxStyle.OkOnly, MsgBoxStyle))

CargarGrilla()

End If

End If

ColumnaGrilla es un Enum donde ColumnaGrilla.ID es igual a cero y ColumnaGrilla.Descripcion es igual a uno.

CargarGrilla es el primer bloque de código del post.

Ahora les muestro el código que corresponde a Agregar:

C#

Occupation miOccupation = new Occupation();

miOccupation.ShowDialog(this);

if (!miOccupation.Cancelo)

{

CargarGrilla();

}

miOccupation = null;

VB.NET

Dim miOccupation As New Occupation

miOccupation.ShowDialog(Me)

If Not miOccupation.Cancelo Then

CargarGrilla()

End If

miOccupation = Nothing

Y lo correspondiente a Modificar

C#

int intPivot;

if ((dgDatos.SelectedRows == null))

{

MsgBox(“No se seleccionó ninguna fila de la grilla.”, ((MsgBoxStyle)((MsgBoxStyle.Exclamation + MsgBoxStyle.OkOnly))));

}

else

{

CoopRules.Entities.Occupation mOccupation = new CoopRules.Entities.Occupation();

intPivot = ((int)(dgDatos.SelectedRows.Item[0].Index));

mOccupation = OccupationMapper.Instance.GetOne(((int)(dgDatos.SelectedRows.Item[0].Cells[ColumnaGrilla.ID].Value)));

Occupation miOccupation = new Occupation(mOccupation);

miOccupation.ShowDialog(this);

if (!miOccupation.Cancelo)

{

CargarGrilla();

dgDatos.Rows.Item[intPivot].Selected = true;

dgDatos.CurrentCell = dgDatos.Rows[intPivot].Cells[ColumnaGrilla.Description];

MsgBox(“La modificación se registró exitosamente.”, ((MsgBoxStyle)((MsgBoxStyle.Information + MsgBoxStyle.OkOnly))));

}

miOccupation = null;

}

VB.NET

Dim intPivot As Integer

If dgDatos.SelectedRows Is Nothing Then

MsgBox(“No se seleccionó ninguna fila de la grilla.”, CType(MsgBoxStyle.Exclamation + MsgBoxStyle.OkOnly, MsgBoxStyle))

Else

Dim mOccupation As New CoopRules.Entities.Occupation

intPivot = CType(dgDatos.SelectedRows.Item(0).Index, Integer)

mOccupation = OccupationMapper.Instance.GetOne(CType(dgDatos.SelectedRows.Item(0).Cells(ColumnaGrilla.ID).Value, Integer))

Dim miOccupation As New Occupation(mOccupation)

miOccupation.ShowDialog(Me)

If Not miOccupation.Cancelo Then

CargarGrilla()

dgDatos.Rows.Item(intPivot).Selected = True

dgDatos.CurrentCell = dgDatos.Rows(intPivot).Cells(ColumnaGrilla.Description)

MsgBox(“La modificación se registró exitosamente.”, CType(MsgBoxStyle.Information + MsgBoxStyle.OkOnly, MsgBoxStyle))

End If

miOccupation = Nothing

End If

Acá llamo la atención especialmente en la diferencia en la invocación al constructor del formulario Occupation entre Agregar y Modificar

C#

Agregar

Occupation miOccupation = new Occupation();

Modificar

Occupation miOccupation = new Occupation(mOccupation);

VB.NET

Agregar

Dim miOccupation As New Occupation

Modificar

Dim miOccupation As New Occupation(mOccupation)

Podemos observar que en el caso de Modificar al constructor le pasamos un Objeto Entity de tipo Occupation y en Agregar no.

Esto es porque el constructor del formulario Occupation tiene un parámetro opcional de tipo Occupation.

Les muestro el código del formulario Occupation:

C#

using CoopRules.Entities;

using CoopRules.Objects;

using CoopRules.Mappers;

using CoopCore.Core;

public class Occupation {

private bool mCancelo = true;

private CoopRules.Objects.OccupationObject mOccupation = new CoopRules.Objects.OccupationObject();

private CoopCore.Core.Estado mEstadoActual;

public Occupation(CoopRules.Entities.Occupation pOccupation = Nothing) {

InitializeComponent();

if ((pOccupation == null)) {

this.EstadoActual = CoopCore.Core.Estado.Agregar;

}

else {

mOccupation = pOccupation;

txtDescription.Text = pOccupation.Description;

this.Text = pOccupation.Description;

this.EstadoActual = CoopCore.Core.Estado.Modificar;

}

txtDescription.Focus();

}

public CoopCore.Core.Estado EstadoActual {

get {

return mEstadoActual;

}

set {

mEstadoActual = value;

}

}

bool Cancelo {

get {

return mCancelo;

}

set {

mCancelo = value;

}

}

private void Occupation_Load(object sender, System.EventArgs e) {

}

private void btnCancelar_Click(object sender, System.EventArgs e) {

this.Close();

}

private void btnAceptar_Click(object sender, System.EventArgs e) {

try {

Validar();

Grabar();

}

catch (Exception ex) {

if (ex.Message.Contains(“Cannot insert duplicate key row”)) {

MsgBox(“Ya existe una Ocupación con ese nombre.”, MsgBoxStyle.OkOnly, NombreEmpresa);

}

else {

MsgBox(ex.Message.ToString, MsgBoxStyle.OkOnly, NombreEmpresa);

}

}

}

private void BlanquearControles() {

txtDescription.Text = “”;

}

private void Validar() {

mOccupation.Description = txtDescription.Text;

mOccupation.Validate();

}

private void Grabar() {

if ((this.EstadoActual == CoopCore.Core.Estado.Agregar)) {

CoopRules.Entities.Occupation eOccupation = new CoopRules.Entities.Occupation();

eOccupation.Description = mOccupation.Description;

OccupationMapper.Instance.Insert(eOccupation);

this.Cancelo = false;

MsgBox(“La Ocupación se agregó exitosamente.”, MsgBoxStyle.OkOnly);

BlanquearControles();

txtDescription.Focus();

}

else {

OccupationMapper.Instance.Save(mOccupation);

this.Cancelo = false;

this.Close();

}

}

}

VB.NET

Imports CoopRules.Entities

Imports CoopRules.Objects

Imports CoopRules.Mappers

Imports CoopCore.Core

Public Class Occupation

#Region “Declaraciones Privadas”

Private mCancelo As Boolean = True

Private mOccupation As New CoopRules.Objects.OccupationObject

Private mEstadoActual As CoopCore.Core.Estado

#End Region

#Region “Eventos”

Public Sub New(Optional ByVal pOccupation As CoopRules.Entities.Occupation = Nothing)

InitializeComponent()

If pOccupation Is Nothing Then

Me.EstadoActual = CoopCore.Core.Estado.Agregar

Else

mOccupation = pOccupation

txtDescription.Text = pOccupation.Description

Me.Text = pOccupation.Description

Me.EstadoActual = CoopCore.Core.Estado.Modificar

End If

txtDescription.Focus()

End Sub

Private Sub Occupation_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

End Sub

Private Sub btnCancelar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancelar.Click

Me.Close()

End Sub

Private Sub btnAceptar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAceptar.Click

Try

Validar()

Grabar()

Catch ex As Exception

If ex.Message.Contains(“Cannot insert duplicate key row”) Then

MsgBox(“Ya existe una Ocupación con ese nombre.”, MsgBoxStyle.OkOnly, NombreEmpresa)

Else

MsgBox(ex.Message.ToString, MsgBoxStyle.OkOnly, NombreEmpresa)

End If

End Try

End Sub

#End Region

#Region “Propiedades”

Public Property EstadoActual() As CoopCore.Core.Estado

Get

Return mEstadoActual

End Get

Set(ByVal value As CoopCore.Core.Estado)

mEstadoActual = value

End Set

End Property

Property Cancelo() As Boolean

Get

Return mCancelo

End Get

Set(ByVal value As Boolean)

mCancelo = value

End Set

End Property

#End Region

#Region “Rutinas y Funciones Privadas”

Private Sub BlanquearControles()

txtDescription.Text = “”

End Sub

Private Sub Validar()

mOccupation.Description = txtDescription.Text

mOccupation.Validate()

End Sub

Private Sub Grabar()

If Me.EstadoActual = CoopCore.Core.Estado.Agregar Then

Dim eOccupation As New CoopRules.Entities.Occupation

eOccupation.Description = mOccupation.Description

OccupationMapper.Instance.Insert(eOccupation)

Me.Cancelo = False

MsgBox(“La Ocupación se agregó exitosamente.”, MsgBoxStyle.OkOnly)

BlanquearControles()

txtDescription.Focus()

Else

OccupationMapper.Instance.Save(mOccupation)

Me.Cancelo = False

Me.Close()

End If

End Sub

#End Region

End Class

Espero les sea útil. 🙂

Otra vez acá.

Basado en el ejemplo del post anterior, voy a introducir modificaciones al modelo original con la idea de ver que tan fácil es hacerlo y regenerar código.

Partimos entonces de la solución generada que pueden descargar desde acá.

Supongamos que ahora nos piden que incluyamos un campo Telefono para la tabla Sucursal.

Conviene agregar algunos registros en las tablas para facilitar la prueba.En primer lugar abrimos SQL Server Management Studio, buscamos en la base de datos Prueba y en la tabla Sucursal agregamos un campo Telefono de tipo VARCHAR(50), grabamos los cambios.

Luego abrimos nuestro modelo con el Modeler y pulsamos Refresh. Al desplegar Sucursal veremos lo siguiente:

Ahora hacemos click sobre Quick generation, al finalizar abrimos la solución con Visual Studio y ejecutamos.Podremos ver que la grilla ya contiene la columna Telefono y si cargaron algunos registros correctamente en las tablas, podrán ver los valores.

Seguimos pensando en otro tipo de modificación, por ejemplo una Asociación, supongamos que nos piden poder calificar las Sucursales por Provincia. Esto nos obliga a crear una tabla Provincia con un campo ID (PK, autonumérico e INT) y Descripcion (VARCHAR(50)). Para vincularla a la tabla Sucursal, agregamos en esta última un campo IDProvincia (FK e INT) luego establecemos la relación entre la PK de Provincia con la FK de Sucursal.

Hacemos un Refresh desde el Modeler,

Desplegamos Sucursal, chequeamos ProvinciaString y quitamos el check de ProvinciaEntity, click en Save model,

Ahora un Quick Generation y volvemos a la solución.

Veremos que se han producido una serie de erroes.

Para solucionarlo iremos primero al proyecto PruebaEntities, en la carpeta Objects agregamos la clase ProvinciaObject.vb y en la carpeta Object/Auto la clase ProvinciaObject.Auto.vb.

Luego en el proyecto PruebaData en la carpeta Gateways agregamos a ProvinciaGateway.vb y en Gateways/Auto la clase ProvinciaGateway.Auto.vb.

Finalmente en la carpeta Mappers la clase ProvinciaMapper.vb y en Mappers/Auto la clase ProvinciaMapper.Auto.vb

Si ejecutamos la solución, vamos a ver la columna Provincia incorporada a la grilla.

Supongamos que nos indican que cada Sucursal está asociada a un Depósito y cada una tiene uno propio. Podemos suponer esto como una Agregación, una relación uno a uno.

Importante: Tengamos en cuenta que para que una relación quede establecida como uno a uno en una base de datos, la PK y la FK deben ser exactamente iguales, mismos campos, mismos tipos, mismos nombres.

En nuestro ejemplo la PK de Sucursal la forman los campos ID e IDCliente (podría haber sido ID solamente) entonces vamos crear una tabla Deposito con estos dos campos un ID (PK, autonumérico, INT)y un campo Descripcion (VARCHAR(50)).

Luego debemos crear una relación uno a uno entre Sucursal y Deposito.

Volvemos al Modeler y hacemos un Refresh. Desplegamos Sucursal y vemos un nuevo elemento llamado DepositoAggregation, si intentamos chequearlo vamos a ver que en pocos segundos pierde el check. Esto sucede porque todavía no definimos su dependencia. Para eso hacemos click sobre el elemento Deposito, en la propiedad GenerateAsChildOf seleccionamos Sucursal. Ahora si podemos chequear sobre el elemento DepositoAggregation de Sucursal.

Ahora vamos a mirar el elemento Cliente, lo desplegamos y seleccionamos SucursalCollection, si vemos su propiedad GenerateAsType vale ObjectList, esto significa que la entidad Cliente, trae una colección de objetos. Como antes les conté, los objetos mapean uno a uno con las tablas de la base de datos, esto quiere decir que la agregación que recién incorporamos no es accesible desde la entidad Cliente.

Para que esto suceda hay que fijar la propiedad GenerateAsType de SucursalCollection con valor EntityList en la entidad Cliente.

Volvemos a hacer un Quick generation y abrimos la solución.

Vamos al proyecto PruebaEntities, en la carpeta Objects agregamos la clase DepositoObject.vb y en la carpeta Object/Auto la clase DepositoObject.Auto.vb.  En la carpeta Entities agregamos la clase Deposito.vb y en la carpeta Entities/Auto la clase Deposito.Auto.vb

Luego en el proyecto PruebaData en la carpeta Gateways agregamos a DepositoGateway.vb y en Gateways/Auto la clase DepositoGateway.Auto.vb.

Finalmente en la carpeta Mappers la clase DepositoMapper.vb y en Mappers/Auto la clase DepositoMapper.Auto.vb

Si todo esto fue bien todavía nos va a quedar un error en la lista:

Value of type ‘PruebaRules.Entities.SucursalList’ cannot be converted to ‘PruebaRules.Objects.SucursalObjectList’.

Esto es en el método CargarGrillaSucursal del fromulario Form1. La linea que muestra el error es la siguiente:

Dim mSucursalList As SucursalObjectList = SucursalMapper.Instance.GetByCliente(CType(cboCliente.SelectedValue, Integer))

Esto es poruqe a la collección de Sucursales contenida en la entidad Cliente la cambiamos de ObjectList a EntityList. Corregida queda:

Dim mSucursalList As SucursalList = SucursalMapper.Instance.GetByCliente(CType(cboCliente.SelectedValue, Integer))

Cambiamos los tipos SucursalObjectList por tipos SucursalList.

Ahora tenemos un error en la siguiente linea:

Dim mSucursalListView As SucursalObjectListView = New SucursalObjectListView(mSucursalList)

Corregida queda:

Dim mSucursalListView As SucursalListView = New SucursalListView(mSucursalList)

Y esto es todo. Cuando lo ejecutemos en la grilla no vamos a ver automáticamente los campos de la agregación, pero van a notar que la agregacion ya es accesible.

Para terminar con esto incorporamos una Composición. Por ejemplo, nos cuentan que vamos a tener una lista de Contactos por cada Sucursal. Ya nos estamos imaginando una tabla Contacto con ID (PK, autonumérico, INT), IDSucursal (FK, INT), NombreApellido (VARCHAR(50)) y antes de establecer la relación entre Sucursal y Contacto, creamos un índice IsUnique sobre la columna ID

Ahora si podemos establecer la relación entre Contacto y Sucursal:

Volvemos una vez más al Modeler y hacemos Refresh.

Desplegamos Sucursal y vemos el nuevo elemento llamado ContactoCollection.

Antes de chequear ContactoCollection, debemos ir a la entidad Contacto a la propiedad GenerateAsChildOf la fijamos en el valor Sucursal. Ahora si, en la entidad Sucursal, chequemos ContactoCollection

Última vuelta al Modeler, Refresh y Quick generation, volvemos a la solución.

Vamos al proyecto PruebaEntities, en la carpeta Objects agregamos la clase ContactoObject.vb y en la carpeta Object/Auto la clase ContactoObject.Auto.vb.

Luego en el proyecto PruebaData en la carpeta Gateways agregamos a ContactoGateway.vb y en Gateways/Auto la clase ContactoGateway.Auto.vb.

Finalmente en la carpeta Mappers la clase ContactoMapper.vb y en Mappers/Auto la clase ContactoMapper.Auto.vb

Una vez hecho esto podemos acceder a un Contacto a través de la entidad Cliente, por ejemplo:

Dim mCliente As Cliente = ClienteMapper.Instance.GetOne(1)

mCliente.SucursalCollection.Item(2).ContactoCollection.Item(3).NombreApellido.Trim()

En la primera linea recuperamos el Cliente con ID igual a 1, en la segunda linea obtenemos el Nombre y Apellido del cuarto Contacto de la tercera Sucursal.

Repasando, con estos cuatro ejemplos que les presento vimos que las tablas pueden relacionarse de tres formas distintas en Cooperator, estas son:

  • Asociación, en el Modeler se ve como xxxxEntity, representa una relación que califica a la entidad base, en nuestro ejemplo, Sucursal Provincia.
  • Composición, en el Modeler se ve como xxxxCollection, es una relación 1 a n, hay que definir que la entidad colección sea definida como hija de la entidad base por medio de la propiedad GenerateAsChildOf.
  • Agregación, en el Modeler se ve como xxxxAggregate, es una relación 1 a 1 (las PK y FK deben se exactamente iguales), hay que definir que la entidad agregación sea definida como hija de la entidad base por medio de la propiedad GenerateAsChildOf.

Ýa en el estribo, tengamos en cuenta que, eL Mapper graba la Entidad con todas sus Composiciones y Agregaciones, no así con sus Asociaciones. Si el ID de la Asociación, no existe el motor SQL devuelve un error de integridad.

Acá les dejo el script de la base de datos resultante, el archivo Cooperator y el código generado para que jueguen.

Ojalá les sea útil   🙂

Para quien no conoce nada acerca de Cooperator Framework le recomiendo leer mi post anterior Introducción a Cooperator Framework.

Comencemos por descargar la herramienta, aquí encontramos el último release de Cooperator Modeler, al finalizar la descarga, descomprimimos el archivo y podremos ver el ejecutable de nombre CooperatorModeler.exe, con un doble click veremos lo su interfaz gráfica:

La prueba la vamos a realizar sobre SQL Server 2005, entonces vamos a abrir SQL Server Management Studio y creamos una base de datos de nombre Prueba, donde crearemos dos tablas, Cliente y Sucursal con una relación 1 a n.

La tabla Cliente tiene un campo ID (PK, INT y autonumérico) y RazonSocial (VARCHAR(50)).

La tabla Sucursal tiene un campo ID (PK, INT y autonumérico), IDCliente (FK, INT y autonumérico) y Denominacion (VARCHAR(50)).

Establecemos la relación entre la PK de Cliente y la FK de Sucursal.

Volvamos ahora al Modeler y hagamos un click sobre el botón New Model, cuando se abra el formulario hacemos click sobre el botón Get Databases, seleccionamos la nuestra y Ok:

Ahora veremos el Modelo:

Desplegamos las Entidades:

Vemos que Cliente tiene una colección de Sucursales, pero si la queremos incluir en el modelo es necesario chequearla.

Seleccionemos ahora Sucursal:

Podemos ver la propiedad GenerateAsChildOf con el valor Cliente, si esto no fuera así, antes no hubieramos podido seleccionar, en Cliente, la colección de Sucursales. También vemos que en Sucursal existe una ClienteEntity, si se chequea esto la entidad Sucursal incuirá la entidad Cliente (padre) con todas sus propiedades, incluirla o no es una desición de diseño.

El elemento ClienteString va a mostrar el campo de la tabla Cliente que nosotros determinemos, seleccionemos el elemento RazonSocial de la tabla Cliente, la propiedad GenerateAsDescriptionField vale True, esto define que RazonSocial es el campo que se mostrará cuando se invoque Sucursal.ClienteString.

Un punto muy importante que resuelve Cooperator es la aplicación del patrón LazyLoad y lo hace sin ningún esfuerzo extra de nuestra parte. Seleccionemos el elemento SucursalCollection de la entidad Cliente, vemos que la propiedad GenerateAsLazyLoad está en True, no hace falta nada más.

Supongamos que nuestro Modelo, por ahora termina acá, lo salvamos y hacemos un click sobre Code Generation

Hacemos un click sobre Generate SP, al finalizar otro click sobre Generate source code y cuando finalice seleccionamos una carpeta en la que querramos generar nuestra solución y luego un click sobre Generate Solution (para esta primera vez).

Modeler casi inmediatamente nos va a avisar que la solución se generó exitosamente y nos preguntará si queremos abrirla, le contestamos que si y vamos al Visual Studio que se está abriendo.

Abramos el formulario en modo diseño y agreguemos un ComboBox de nombre cboCliente y un DataGridView de nombre dtgSucursal.

Para hacer la prueba vamos a cargar a mano algunos registros en las tablas.

Ahora vamos a agregar código al formulario Form1, comenzando por los Imports:

Imports PruebaRules.Entities

Imports PruebaRules.Objects

Imports PruebaRules.Mappers

Imports PruebaRules.Views

 

En el evento Load del formulario disparamos CargarComboCliente

Private Sub CargarComboCliente()

Dim mClienteList As ClienteList = ClienteMapper.Instance.GetAll()

Dim mClienteListView As ClienteListView = New ClienteListView(mClienteList)

cboCliente.DisplayMember = “RazonSocial”

cboCliente.ValueMember = “ID”

Dim mCliente As New Cliente

mCliente.RazonSocial = “(Ninguno)”

mClienteListView.Add(mCliente)

mClienteListView.Sort(“RazonSocial”, True)

cboCliente.DataSource = mClienteListView

cboCliente.SelectedIndex = 0

End Sub

Los Clientes se recuperan en un objeto ClienteList a través de su correspondiente Mapper,  en la segunda linea asigno este ClienteList a otro objeto ClienteListView que me permite más versatilidad. La tercer y cuarta linea de código me permiten definir que valores se van a ver en el ComboBox y cuales voy a recuperar al seleccionar un Cliente por medio de las propiedades DisplayMember y ValueMember respectivamente. En las siguientes tres lineas creo un objeto Cliente, le asigno el valor (Ninguno) y lo agrego en el ComboBox. Luego ordeno el objeto de tipo ClienteListView por medio de un Sort, le asigno dicho ListView a la propiedad DataSource del ComboBox y finalmente me posiciono en el valor (Ninguno) del ComboBox.

En el SelectedIndexChanged del ComboBox de Clientes disparo CargarGrillaSucursal:

Private Sub CargarGrillaSucursal()

Dim mSucursalList As SucursalObjectList = SucursalMapper.Instance.GetByCliente(CType(cboCliente.SelectedValue, Integer))

Dim mSucursalListView As SucursalObjectListView = New SucursalObjectListView(mSucursalList)

dtgSucursal.DataSource = mSucursalListView

End Sub

Acá  usamos un objeto de tipo SucursalObjectList para obtener, por medio de su Mapper, la lista de Sucursales correspondiente al Cliente seleccionado en el ComboBox. Vemos que se usa un objeto de tipo SucursalObjectList y no uno de tipo SucursalList. Si vamos al Modeler y vemos el elemento SucursalCollection, tiene una propiedad de nombre GenerateAsType con valor ObjectList, si en esta propiedad seleccionamos el valor EntityList, podríamos haber usado un objeto de tipo SucursalList a cambio de SucursalObjectList.

A esta altura es necesario aclarar que un Object mapea uno a uno contra una tabla de la base de datos, mientras que en un objeto de tipo Entity no necesariamente es así.

Ahora F5 y a ver como anda.

No intenten hacer esto solos en sus casas.

Voy a subir el código de este post ya que va a ser la base de otros posteriores.

Prueba.coop es el archivo con que el Modeler persiste la información.

Script.sql, para crear la base de datos.

Prueba.zip la solución Visual Studio 2005

Espero les sirva y espero sus comentarios. 🙂

Cooperator es otro Generador de Código, con otras características.

El proyecto está pensado y desarrollado fundamentalmente por Daniel Calvin y Eugenio Serrano.

Si bien Cooperator está en evolución, ya hay mucha gente que está desarrollando software de alta calidad con esta herramienta y entre ellos me cuento.

Hay muy buen material de este proyecto, les recomiendo algunos links con los que se puede avanzar mucho:

Con esto hay para entretenerse un buen rato. Voy a volver sobre este proyecto ya que va a ser una de las vertientes principales de mi blog.

Un afectuoso saludo a la buena gente de Cooperator Framework. 🙂

Asesoramiento a Organizaciones Públicas y Privadas.

Cursos a grupos ya constituidos.

Charlas de orientación.

Metodologías Ágiles.

Startups.

Visitas

  • 135,199 hits

Carlos Marcelo Santos

Mejor calificado

del.icio.us