Archive for the ‘.NET’ Category

Inyección de Dependencia con Spring.Net

Spring.Net es uno de los frameworks más conocidos para implementar Inyección de Dependencia (también conocido como Inversión de Control -IoC). Otros que nos permiten hacer este trabajo son: PicoContainer, ObjectBuilder, Windsor Container.

Basicamente, qué queremos lograr? Fácil, crear nuestros objetos, sin importar quién me provea la implementación.

En vez de hacer esto:

(1) Cliente obj_cliente = new Cliente();

Podemos hacer esto:

(2) ICliente obj_cliente = (ICliente) AppContext.Instance.GetObject("Cliente");

Expliquemos, en (1) le estamos diciendo a obj_cliente quién va a crearlo: new Cliente(); Es decir, le estamos diciendo quien le va a proveer de la implementación, y nunca vamos a poder cambiar esto, a menos que lo hagamos y volvamos a compilar. En (2) es distinto, estamos pidiendo una implementación, pero no sabemos quien nos la va a dar. Solo sabemos que quién nos proveea la construcción, va a implementar la interfaz ICliente. AppContext es un wrapper que hice para crear un singleton del contexto de toda la aplicación (está en el codigo fuente que se puede descargar más abajo).

El lugar donde le decimos qué clase se va a encargar de la implementación, es en el app.config (en una de las tantas lineas para configurar Spring.Net):

<object name=”Cliente” type=”Entities.Cliente, Entities” singleton=”false”/>

En esta línea de código Entities.Cliente es la Clase que nos proveerá la implementación, y Entities es el assembly (que TIENE que estar en la carpeta de salida, donde está el .exe). También podemos ver cuan fácil es implementar un singleton de esta manera, solamente escribiendo singleton=”true” (ú obviandolo, es el valor por defecto), entonces no tenemos que hacerlo programaticamente.

Para el ejemplo lo que hice es organizar los proyectos de esta forma:

Lo que hariamos normalmente es referenciar Entities desde todas las partes del proyecto, en vez de esto, lo haremos con Entities.Contracts que contienen las interfaces, de modo que siempre programaremos contra las interfaces, nunca contra la implementación. Los proyectos no poseen relación de conocimiento con Entities, en ningún momento se lo referencia.

El tip del día: Hay que programar contra las interfaces.

Descargar codigo fuente [Proyecto hecho con SharpDevelop]

Donde más podemos utilizarlo ? En la capa de acceso a datos, podríamos persistir los objetos con db4o, y en otra implementación lo podríamos hacer usando NHibernate! Y esto lo lograríamos creando una interfaz IBaseRepository que tenga metodos como Guardar, Eliminar, Buscar y luego crear las implementaciones para cada uno de los providers por ejemplo BaseRepositoryDb4o y BaseRepositoryNH.

Espero que sirva!

Anuncios

using NHibernate.Tool.hbm2ddl

Una herramienta muy importante de NHibernate, a la vez, deseable por los ORM, es la generación de código.

Para generar el DDL de la base de datos, nos podemos valer de la información del esquema, que nos brindan los archivos de mapeo ó mapping files: hbm.xml.

Un vez que tenemos bien configurados estos archivos, podemos generar las tablas con solamente incluir un par de sentencias en .Net y configurar un archivo xml. Tambien se tiene que tener referenciado a NHibernate.

Código en C#:

using System;
using NHibernate.Cfg;
using  NHibernate.Tool.hbm2ddl;
public class MyClass
{
    public static void Main()
    {
            Configuration config = new Configuration();
            config.Configure();
            SchemaExport exporter = new SchemaExport(config);
            //exporter.SetOutputFile(@"c:testDDL.sql");
            exporter.Drop(true, true);
            exporter.Create(true, true);
    }
}

Archivo hibernate.cfg.xml (debe ir copiado en el directorio de salida):

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration  xmlns="urn:nhibernate-configuration-2.0" >
 <session-factory name="NHibernate">	
	<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
	<property name="connection.driver_class">NHibernate.Driver.FirebirdClientDriver</property>
	<property name="connection.connection_string">
	ServerType=1;
	User=sysdba;password=masterkey;Database=C:ruraldata.fdb;
	Pooling=false
	</property>
	<property name="show_sql">true</property>
	<property name="dialect">NHibernate.Dialect.FirebirdDialect</property>
	<property name="use_outer_join">true</property>
	<property name="query.substitutions">true 1, false 0, yes 'Y', no 'N'</property>

	<mapping assembly="RuralSolution.Entities" />

 </session-factory>
</hibernate-configuration>

En este caso es la exportación del schema de una base de datos Firebird embebida.

Esta herramienta me ha sido de mucha utilidad. Estoy desarrollando una aplicación en SQL Server 2005 y NHibernate, pero, me dí cuenta que la aplicación debía ser portable, de modo que tuve que migrar la base de datos a una portable, y como Firebird está pasando todos los test de NH, la elegí. Ya tenía los hbm, así que no requirió trabajo demás.

Importante para la generación con Firebird: en el directorio de salida entonces tendriamos que tener: fbembed.dll, FirebirdSql.Data.FirebirdClient.dll, y hibernate.cfg.xml.

Si quieren saber como trabajar con MyGeneration y Firebird embedded léanse este post de mi amigo Matias.

Comparación de Modelos de Redes Neuronales

Hoy expusimos en la Jociti 2006 (Jornadas de Ciencia, Tecnología e Innovación) en la UTN – Facultad Regional Resistencia – con Carlos Vicentin y Juan Carlos Insfrán un trabajo que realizamos en la Cátedra de Inteligencia Artificial en la primera mitad del año 2006 durante el cursado.

El Sistema es una simple implementación de 2 redes neuronales muy conocidas: Hopfield y BackPropagation.

Basicamente el sistema ayuda al soporte de decisiones sobre el otorgamiento de crédito de una entidad financiera.

Se le presenta un perfil de cliente y la red decide si el perfil es apto, no lo es, o es dudoso para el otorgamiento de un crédito.

Se necesita el Framework 2.0 de .Net para correrlo. Y está programado en C#.

Recursos:

Predicados y especificaciones

Inspirado en este post, decidí hacerme un ejemplito de predicados en .Net y así utilizar el patrón especificaciones.

Un predicado es un delegado que apunta a funciones que devuelven un valor booleano y aceptan un objeto genérico.

Vamos a ver una forma interesante de hacer filtrados en base a ciertos criterios de selección utilizando estos dichosos predicados de .Net.

Ciertamente podríamos implementar esto de la forma que siempre se hace, realizar un filtrado, iterando en una colección, y preguntando si tal elemento, se corresponde con el criterio elegido. Por ejemplo: recorrer la lista y vamos preguntando: este cliente…tiene correo gmail ?

Podría esbozarse un código como este:

 static public IList<Cliente> HasGmail(IList<Cliente> lista)
        {
            IList<Cliente> lista_resultado = new List<Cliente>();

            foreach(Cliente c in lista)
            {
                if(c.Email.Contains("@gmail.com"))
                {
                    lista_resultado.Add(c);
                }
            }
            return lista_resultado;
        }

Y se llamaría así:

IList<Cliente> usuariosDeGmail = ClienteFinder.HasGmail(lista);

Pero ahora bien, se podría objetizar el código, y hacerlo un poco más flexible, y hacer uso de predicados.

Podríamos reemplazarlos por esto:

IList<Cliente> usuariosDeGmail = 
                new ClienteFinder(lista).Find(EmailSpec.HasGmail);

Parecería estar más complicado, pero el concepto es sencillo y aplica el patrón de especificaciones, mediante predicatos genéricos.

Vamos al codigo de la entidad de negocio de la cual tenemos un listado y la queremos obtener por un criterio: la entidad Cliente.

public class Cliente
    {
        public Cliente()
        { }

        public Cliente(string nombre, string email)
        {
            this.nombre = nombre;
            this.email = email;
        }
        private string nombre;

        public string Nombre
        {
            get { return nombre; }
            set { nombre = value; }
        }

        private string email;

        public string Email
        {
            get { return email; }
            set { email = value; }
        }
    }

El código principal sería así:

 static void Main(string[] args)
        {
            CargarLista();

            //IList<Cliente> usuariosDeHotmail = new ClienteFinder(lista).Find(EmailSpec.HasHotmail);
            IList<Cliente> usuariosDeGmail = new ClienteFinder(lista).Find(EmailSpec.HasGmail);

            foreach (Cliente cliente in usuariosDeGmail)
            {
                Console.WriteLine("Email: {0}", cliente.Email);
            }

            Console.Read();
        }

Veamos el código de las especificaciones que se puede aplicar para obtener distintos criterios de filtrado:

public class EmailSpec
{
    public static Predicate<Cliente> HasHotmail
    {
        get{
            return delegate(Cliente cliente)
            {
                return cliente.Email.Contains("@hotmail.com");
            };

        }
    }

    public static Predicate<Cliente> HasGmail
    {
        get
        {
            return EmailSpec.MethodHasGmail;
        }
    }

    public static bool MethodHasGmail(Cliente cliente)
    {
        return cliente.Email.Contains("@gmail.com");
    }

    public static Predicate<Cliente> HasYahoo
    {
        get
        {
            return new Predicate<Cliente>(EmailSpec.MethodHasYahoo);
        }
    }

    public static bool MethodHasYahoo(Cliente cliente)
    {
        return cliente.Email.Contains("@yahoo.com");
    }

}

Aquí podemos ver, primeramente el uso de retorno de delegados usando Métodos anónimos (en HasHotmail), delegados con inferencia de tipos (en HasGmail) y la forma natural de usar un delegado (en HasYahoo). Estas son tres formas de hacer lo mismo, y yo recomiendo usar métodos anónimos que se vé en la propiedad HasHotmail y en este caso, el código se vuelve mucho más chico.

 public static Predicate<Cliente> HasHotmail
    {
        get{
            return delegate(Cliente cliente)
            {
                return cliente.Email.Contains("@hotmail.com");
            };

        }
    }

Vemos que la propiedad es de sólo lectura, y que también, retorna un predicado, que hablando mal, devolvería la función que se encargaría de la evaluación…y esa función…devolvería un booleano…true OR false, si cumple ó no. Muy prolijo no ? Sería lo mismo hacer:

  public static Predicate<Cliente> HasHotmail
    {
        get{
            return delegate(Cliente cliente)
            {
                if(cliente.Email.Contains("@hotmail.com"))
                    return true;
                else
                    return false;
            };

        }
    }

Ahora veamos el código de ClienteFinder:

public class ClienteFinder
{
    private IList<Cliente> _lista;

    public ClienteFinder(IList<Cliente> lista)
    {
        _lista = lista;
    }

    public IList<Cliente> Find(Predicate<Cliente> predicate)
    {
        List<Cliente> encontrados = new List<Cliente>();

        foreach (Cliente cliente in _lista)
        {
            if (predicate(cliente))
            {
                encontrados.Add(cliente);
            }
        }

        return encontrados;
    }

}

Lo importante acá es el constructor, que va a recibir la lista a ser filtrada. Y también el método Find, quien va a recibir el predicado correspondiente al criterio de selección. Es decir que Find puede recibir cualquiera de las tres especificaciones que preparamos: HasHotmail, HasGmail, HasYahoo, y realizar el filtrado en base a ellas.

Recursos:

Delegados, Eventos y Métodos anónimos

Introducción: Implementando una interfaz gráfica con windows forms, teniendo un MDI principal y un par de formularios hijos, vi la necesidad que en un momento dado, un formulario hijo realice algo que el padre tiene que interceptar, y tomar cartas en el asunto. En este caso necesitaba setear la propiedad Text del MDI, a partir de alguna acción de un formulario hijo; algo sencillo.

En C# y sin la posibilidad de usar algo análogo al namespace My, que posee la gente de VB.Net, tuve que recurrir a un Eventos y Delegados,…y ya que estamos…Métodos anónimos.

En VB.Net, estando en formulario hijo con el namespace my seria algo bastante tonto:

My.Forms.MDIMain.Text = nombredeltitulo

En C# me las arregle con un mecanismo que permita que el padre (MDI principal) se quede a la “escucha” de un evento, y así iniciar acciones, pero con información que le provee el hijo. Es decir:

  • Los metodos a ejecutar: son del MDI principal
  • Y la información necesaria -los parametros- : provienen del form hijo.

Ahora bien, como se podrían implementar estos tres conceptos juntos para resolver esto ?

Ahora pasemos a un ejemplo donde se vé una interacción entre los objetos:

using System;// Declaracion del delegado

public delegate void EventHandler(string str);

public class Program{

public static void Main()  {

TestHandler tb

= new TestHandler();

//Tres maneras de hacer lo mismo

//1- asignacion comun

        tb.Evento += new EventHandler(MetodoStringToUpper);

//2- asignacion por inferencia de tipos

        tb.Evento += MetodoStringToLower;

//3- asignacion con metodo anonimo

        tb.Evento += delegate(string str)                              {

string otrostring =   String.Format(

"Metodo Anonimo: {0}",str);                              Console.WriteLine(otrostring);

};

tb.Disparar();      Console.ReadLine();

}

public static void MetodoStringToUpper(string str)  {

Console.WriteLine(str.ToUpper());

}

public static void MetodoStringToLower(string str)  {

Console.WriteLine(str.ToLower());

}

}

class TestHandler{

//Evento de tipo EventHandler

    public event EventHandler Evento;

public void Disparar()  {

string mystring = "Dario.Net";

//Disparo el evento, con un string de parámetro

        Evento(mystring);  }

}

Primeramente declararemos un delegado: EventHandler, que podrá apuntar a metodos que contengan como argumentos el tipo string y que no retornen valores (void).

Luego en la clase TestHandler se declara un evento: Evento, del tipo EventHandler (nuestro delegado).

Y la funcion: Disparar(); se encargará de disparar el evento con un string como argumento: mystring=”Dario.Net”.

Vayamos al Main().

Instanciamos un objeto del tipo TestHandler, con el operador += y vamos agregando al evento, todos los métodos que queremos que se ejecuten, uno tras otro.

Luego invocamos a Disparar(). y se invocan, en este orden los tres métodos: MetodoStringToUpper,MetodoStringToLower y el querido metodo anónimo.

Cosas a notar:

El que dispara el evento es el TestHandler, y le pasa un parámetro: mystring.

Los métodos están en el Main, pero esos métodos se ejecutarán por el hijo…cosa loca no ?

El método anonimo: es como si fuera un metodo más, pero definido en el momento de su uso, e inline (buenísimo!)

Bien, ahora que vimos el ejemplo (y espero que lo hayan entendido), imaginemos algo: hagamos de cuenta que Program es el Main, y TestHandler es el Form1. Y podemos solucionar facilmente el problema que plantee al comienzo…y muchos más!

Para profundizar les recomiendo este artículo.

Saludos y dejen comentarios.

Reflexion + Generación de código = Modelo para AjGenesis

Estuve viendo AjGenesis, que es un generador de codigo escrito por Angel “Java” Lopez, la verdad muy útil. Se puede generar código para cualquier lenguaje que estén escritas las plantillas, y sino están escritas, las podés escribir con mucha facilidad, ya que vienen algunas plantillas de ejemplo como para ir conociendo el lenguaje AjGenesis. Para poder generar código se requiere, como todo generador de cógido: un modelo. Este modelo está escrito en XML.

Por ahora, AjGenesis, no cuenta con una interfaz amigable para un usuario final, pero funciona y genera de lo lindo. Para comenzar a usarlo se haría así:

#AjGenesis.Console.exe ModelsModel.xml Tasksgenerate.ajg
Donde Model.xml contiene los links a los otros archivos xml que contienen las descripciones de las entidades. Generate.ajg es el archivo de tareas que el generador vá a usar, vendría a ser como el archivo main para generar, él se encargará de buscar los templates. Por lo general el generate.ajg, tendrá un loop que iterará entre todas las entidades de negocio y así ir generando los artefactos en el lenguaje en que esten trabajando.

Ahora bien, como hago para obtener de forma automática el modelo?

En un proyecto que empecé con db4o, y el diseño de las business entities lo hacía con el Class Designer del Visual Studio 2005, se me ocurrió que el modelo de clases lo podía obtener del assembly donde están las clases de negocio. Es decir, tengo un proyecto que tiene adentro solo las clases de negocio, como podrían ser: Clientes, Factura, LineaFactura, etc.

Así nació GenerateFromAssembly, que es una herramienta muy sencilla, que via Reflexion, itera entre las clases de la dll, y obtiene sus propiedades con sus respectivos tipos de datos.

Actualmente la estoy usando en este proyecto, ya no con db4o, estoy usando NHibernate contra un Sql Server, pero la herramienta funciona igual, y genera el modelo como lo debe hacer.

Cabe destacar que la idea de la herramienta es: A partir del diseño de clases generar un assembly de .Net, y a partir de él, generar el modelo, y con el modelo y los templates de AjGenesis: obtengo mis artefactos, ya sean: Web Services, artefactos de capa de negocio, de capa de acceso a datos, o formularios web/windows.

Existe un issue: los tipos de datos genericos. Por ejemplo, si tengo IList<Cliente>, el CLR los trata como si fueran IList`[Cliente], es una cuestión de nombras, nada más. De modo que en el código final generado con AjGenesis obtendremos los tipos de datos con este último esquema. Se podría hacer otra herramienta, que al codigo final generado, lo masajee con algunas expresiones regulares y exprese los generic tipes segun el lenguaje .Net que estemos usando.

Expresiones Regulares en .Net

Intentando desarrollar un parser, ya que veremos que parseamos 🙂 es imposible no caer en este tema de expresiones regulares, a menos de que nos fabriquemos una rutina que caracter a caracter vaya leyendo, almacenando en un buffer y comprobando si es lo que estamos buscando, podría ser una opción si es algo sencillo, pero…hgggssss.

O podemos ser los programadores duros y construirnos nuestra propia maquina de estados y con ella procesar las expresiones.Para los que no saben, o alguno que haya cursado conmigo Sintaxis y Semantica del Lenguaje y no se acuerda de los que nos hablaba el profe Bernal cuando nos hacía hacer la máquina de estados de una expresión regular dada, como aabbcc 😛 y ver como corrían las letras de un estado al otro, pero bueno…supongo que ese fue el lado oscuro de las Regex 😀
Una definición semi-formal: Una expresión regular es una cadena que describe o “matchea” a un conjunto de cadenas, dado un conjunto de reglas de sintaxis. En sintesis, es una cadena que representa a un patron que se busca en un texto. Un ejemplo más palpable, que por cierto los usamos a menudo, cuando queremos copiar archivos a un directorio: #cp *.cs /home/dario. Estamos aplicando el concepto. Esto en castellano seria como copiar todos los archivos (*) que tengan la extensión ‘cs’ a el directorio /home/dario. Es simple.

 

Las expresiones regulares funcionan de una manera similar. Tienen un conjunto finito de simbolos para la representación de caracteres, numeros, y como tambien simbolos para indicar cardinalidad.

Vayamos a un ejemplo: todos sabemos que una dirección IP se asemeja a: 10.66.43.141. Es decir, [numero 1-3 digitos][punto][numero 1-3 digitos][punto][numero 1-3 digitos][punto][numero 1-3 digitos]

Que mejor ejemplo para aplicar expresiones regulares. Aunque las diferencias se den, dependiendo del lenguaje que estamos trabajando, las expresiones regulares no varían mucho de una implementación a la otra. Por lo general todas las implementaciones se parecen bastante a la manera que lo hace Perl, cuyo motor de expresiones regulares es muy potente, mejor dicho, cuando te nombran Perl, lo primero que se me viene a la cabeza es RegEx, que ha tenido su inspiración en sed.Si programás en .Net y querés usar RegExs, The Regulator es ideal para armar las cadenas, probarlas y ver si funcionan como queremos, esta herramienta está hecha en .Net (dicho sea de paso).

Ejemplo: si queremos evaluar una expresión matemática para extraer sus operadores y operandos, y despues convertirla a postfija para su evaluación, primero debemos masajearla un poco.

Entonces, si tenemos una expresion como:

2.34m+344/444*(variable.value+33.55m+2^234)/2

La podríamos evaluar con la siguiente expresión regular, que usa el concepto de grupos, pero no es dificil de entender:

(?(?[d]+)u002e(?[d]+)m)|
(?d+)|
(?u0028|u0029|u002a|u002b|u002d|u002f|u005e)|
u0022(?.+)u0022|
(?[a-zA-Z0-9u002du002e]+)

donde:

  • \d –> representa un decimal.
  • [ ] –> uno, cualquiera de los caracteres dentro de los corchetes.
  • (? ) –> se utiliza para hacer grupos.
  • \u00xx –> representa un caracter unicode.
  • . –> cualquier caracter, excepto el retorno de carro.
  • | –> exclusión.
  • a-z –> cualquier caracter entre a y z.



Patron Proxy en C#

Este es un ejemplo del Patron Proxy adaptado x mí, lo traté de hacer lo suficientemente claro para que no requiera mucha explicación, pero…basicamente es:Empleado deriva de IPersona.
Defino los metodos de Empleado (que son los metodos reales)
Despues defino el Proxy que tambien deriva de IPersona, de modo que aun no estando instanciado realmente en memoria, puedo ocupar los metodos, de manera que retraso la instanciación (que es una de las motivaciones de este patron).
Cuando deseo ocupar los metodos de Empleado, no lo trato directamente con él, sino con su delegado, es decir, el Proxy.


//
// Ejemplo Utilizando el patron Proxy
//
using System;

namespace sampleProxy3
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
  IPersona obj = new Proxy();

  obj.PonerNombre("dario"); //este es el momento en que se crea
                             verdaderamente el objeto pero ya puedo
                             ocupar sus metodos.
  obj.Imprimir();
  Console.ReadLine();
}
}

public interface IPersona
{
string ObtenerNombre ();
void PonerNombre (string nombre);
void Imprimir();
}

public class Empleado : IPersona
{
string Nombre;
public Empleado(string nombre)
{
  this.Nombre = nombre;
}
public Empleado(){}
#region Miembros de IPersona

public string ObtenerNombre()
{
  return this.Nombre;
}

public void PonerNombre(string nombre)
{
  this.Nombre = nombre;
}
public void Imprimir()
{
  Console.WriteLine("El nombre es: "+this.Nombre);
}
#endregion
}

public class Proxy : IPersona
{
Empleado emp;
public void PonerNombre(string nombre)
{
  if (emp == null)
  {
      emp = new Empleado();
  }
  emp.PonerNombre(nombre);
}

public void Imprimir()
{
  if (emp == null)
  {
      emp = new Empleado();
  }
  emp.Imprimir();
}

public string ObtenerNombre()
{
  if (emp == null)
  {
      emp = new Empleado();

  }
  return emp.ObtenerNombre();
}

}
}

Un ejemplo de esto más tangible, es lo que hace el Visual Studio cuando se Agrega una Referencia Web -un Web Service-. Como vamos a necesitar ocupar los metodos del Web Service que lo tenemos en otra parte del mundo, primero te genera un proxy para que vos puedas interactuar con los metodos de las clases originales, sin interactuar con el objeto instanciado. De modo que el Intellisense nos deja ver todo lo que tiene para ofrecernos el Web Service.


What ???!!!

Vean lo que es este trozo de codigo, y me parece que habla por si solo. Si… es lo que Uds piensan…es LINQ, Language INtegrated Query.. que te permite hacer consultas en pleno
C# y VB, y no solamente a bases de datos, podes hacerlo como muestra el primer ejemplo, a un simple arreglo.

using System;

using System.Query;

using Danielfe;

class Program

{

static void Main(string[] args)

{

string[] aBunchOfWords = {"One","Two", "Hello",

"World", "Four", "Five"};

var result =

from s in aBunchOfWords // query the string array

where s.Length == 5 // for all words with length = 5

select s; // and return the string

//PrintToConsole is an Extension method that prints the value

result.Print();

}

}

y para que lo vean como funcionaría con un SQL Server, va un ejemplo, y me parece que no hace falta explicarlo


using System;

using System.Query;

using Danielfe;

using System.Data.DLinq; //DLinq is LINQ for Databases

using nwind; //Custom namespace that is tool generated

class Program

{

static void Main(string[] args)

{

Northwind db = new Northwind("Data Source=(local);Initial Catalog=Northwind;Integrated Security=True");

TableCustomers> allCustomers = db.GetTableCustomers>();

var result =

from c in allCustomers

where c.ContactTitle.Length == 5

select c.ContactName;

result.Print();

}

}

Proyecto LINQ


Npgsql y .NET 2.0

Para los que quieren programar con Postgres SQL en .Net, les cuento que a este driver se lo puede Agregar al Toolbar y despues podemos arrastrar el componente hacia los winforms, es pura y exclusiva para programar en .Net. Esto esta muy bueno, y por sobre todo, lo mejor, es que funciona bien en el .Net 2.0.
Veremos si los muchachos sacan una version exclusiva para este nuevo .Net, pero por ahora…Funciona.

The npgsql Project