Vamos por partes:
> Porque es tan necesario utilizar la libreria NHibernate para hacer aplicaciones de gestión de datos?
> Con los dataset datareader datatable, etc que trae el mismo net, no seré capaz de hacer
una aplicación decente?
"necesario", estrictamente necesario no hay nada. ¿Podrías hacer una aplicacion decente usando solo lenguaje ensamblador? por supuesto que sí, si te tomas el tiempo y el esfuerzo necesario.
Las herramientas y abstracciones sirven justamente para reducir el tiempo y el esfuerzo necesario para realizar un trabajo determinado.
¿Por qué no es viable construir un software de negocios para una empresa utilizando solo lenguaje ensamblador? Pues porque para hacerlo necesitarías miles de horas de trabajo y un equipo de decenas de personas, y no es viable económicamente, ni tampoco razonable.
Cuanto más alto es el umbral de abstracción de un desarrollador, y de las herramientas que utiliza, mayor es la productividad y menos esfuerzo requiere para construir software. Es conocido el caso de Yan Cui, que utilizando el paradigma funcional y el lenguaje F#, reemplazó a un equipo entero de 5 programadores java. Es decir, una sola persona (con las herramientas correctas y el conocimiento) haciendo el trabajo que antes hacían 5.
Esto mismo pasa cuando comparas .NET con VB6. Como te dije antes, el umbral de abstracción de .NET es enormemente superior al de VB6.
Hagamos el ejercicio mental de comparar tu ejemplo de código con cómo lo haria yo en .NET usando NHibernate (u otro ORM, como Entity Framework, el código es muy similar).
A simple vista te puedo nombrar estas ventajas de este código respecto de tu ejemplo:
1 - utiliza un modelo estático, que es verificado por el compilador en tiempo de compilación. Es decir, qué sucede si mañana eliminamos la columna "Codigo" de la tabla cliente?
- En tu ejemplo, tendríamos que revisar todo el sistema completo buscando lugares donde se utilice la columna, y puede ser que haya muchos falsos positivos. Si haces una búsqueda de texto por "Codigo", pero resulta que todas las tablas de tu base tienen una columna con ese nombre, vas a tener que revisar todo el proyecto y eso te va a llevar muchísimo tiempo.
- En mi ejemplo, hay una clase Cliente declarada en el modelo, y esa clase tiene properties que representan las columnas de la tabla cliente, con sus respectivos tipos de datos. Si yo decidiera borrar la columna, simplemente borraría la property correspondiente de la clase, y el compilador me daría exactamente una lista de todos los lugares del proyecto entero donde se usa esa property, para corregir. Como el compilador sabe que la clase Cliente y Producto NO son iguales, no confundiría Cliente.Codigo con Producto.Codigo, con lo cual no me daria falsos positivos.
Lo mismo sucede con el campo "Nombre". O sea como es un string, es responsabilidad del desarrollador verificar que este escrito correctamente, y además "recordar" cuales son los campos válidos de esa tabla. No tenés intellisense, no tenés ninguna herramienta del IDE para ayudarte, con lo cual tenes que estar "adivinando" todo todo el tiempo. Vos podrás decir "ah, yo me acuerdo", pero no estás considerando que el software se construye con equipos de personas, y lo que "vos te acordas", otra persona puede no saberlo o no acordarse. En lugar de obligar a todo el equipo a memorizar detalles irrelevantes, lo que hacemos es usar herramientas más modernas y dejar que el equipo se concentre en diseñar y construir lógica de negocio.
2 - No estoy utilizando strings en ningun lado. Los strings no los verifica el compilador, con lo cual es muy facil que cometas un typo, como escribir SLEECT en lugar de SELECT, o escribir mal el nombre de una columna. La única forma de comprobar esto, es ejecutar el programa y probar, lo cual es una pérdida de tiempo. Además, en particular tu código es vulnerable a ataques de inyección SQL, mientras que el mío no.
3 - En tu ejemplo, los tipos de datos están implícitos y hay que adivinarlos y recordarlos. Por ejemplo, el WHERE Codigo = '' asume que código es VARCHAR. Qué sucedería si dentro de 3 meses se decide cambiar esa columna a INT? Otra vez lo mismo que en el punto #1, te verías obligado a recorrer todo el código buscando los lugares donde hacer las correcciones.
4 - Tu código no se puede abstraer, ni reutilizar. Solo sirve para consultar la tabla Clientes y solo para filtrar por el campo Codigo. Si yo quisiera reutilizarlo para otras tablas/columnas, tendría 2 opciones:
A - copy/paste. Esto es muy poco mantenible, ya que si luego hubiera un error o surgiera la necesidad de introducir una mejora, habría que modificar el código tantas veces como se haya copiado y pegado. Si tenes 100 tablas, y 100 funciones copy/pasteadas que solo varian en el nombre de la tabla, para introducir una mejora en todos los casos tendrías que modificar 100 funciones diferentes.
B - pasar el nombre de la tabla/columna como parametro de tipo "string". Esto se conoce como
String Typing, y es un abuso del uso de strings donde en realidad se deberían usar otros tipos de datos. Es decir, si yo tengo un parametro de tipo string tranquilamente podria recibir cualquier valor de string, incluyendo nombres que no correspondan a tablas reales de la base de datos, o nombres de tablas que en realidad no tienen el campo Codigo para la consulta. De nuevo, como es un simple string el compilador no tiene idea de si tu codigo es correcto o no. Queda en cada desarrollador la responsabilidad de verificar esto. Esto se conoce como "compilador humano", ya que en realidad estás haciendo vos mismo la tarea del compilador.
En mi ejemplo, si yo cambio el tipo <Cliente> por otro, el codigo dejaría de compilar si paso un tipo inválido (que no sea una entidad de la base de datos, por ejemplo), o si paso una entidad que no tenga el campo "Codigo". El compilador se encarga de verificar que mi codigo sea correcto, para que no tenga que hacerlo yo mismo.
Asimismo, si quisiera generalizar ese codigo simplemente tendria que reemplazar el tipo concreto <Cliente> por un tipo <T> y usar Generics de .NET para poder reutilizar mi codigo infinitamente con cualquier entidad, presente o futura.
5 - Otro aspecto en el que me resulta insuficiente la abstracción es que en tu ejemplo, para relacionar tablas, hay que conocer los foreign keys de cada una, recordar los nombres. No hay intellisense, no hay nada que te ayude. De nuevo, no se trata de vos te los sabes o no ahora mismo, pensá qué pasaría si una persona tiene que encargarse de hacer mantenimiento a tu código. Esa persona bien podrías ser vos mismo dentro de unos meses.
Usando un ORM, en cambio, podes hacer consultas como esta:
Esa consulta involucra 3 tablas: Cuotas, PlanPagos, y Operacion. Fijate que en ningún lado necesité referenciar los foreign keys, sino que me concentro en la lógica de negocio. Además en esta consulta podes ver otra ventaja: el uso de Enums. La columna Estado de las 3 tablas es de tipo INT, pero solo hay un puñado de valores válidos para cada caso. El ORM me asegura que le estoy pasando un valor valido para cada caso, y el compilador sabe que Operacion.Estado es diferente de Cuota.Estado, con lo cual no me va a permitir confundirme uno con otro. Y lo que te decía antes de los tipos de los parámetros de la consulta: el compilador sabe que Cuota.Anio y Cuota.Mes son de tipo INT, con lo cual no me va permitir hacer algo como (x.Anio > "holacomoestas"), eso va a ser un error de compilación.
6 - Grafo de objetos: asi como dentro de la consulta yo pude hacer cuota.Operacion.PlanPagos y resolver las relaciones entre las tablas, fuera de la consulta tambien lo puedo hacer. Esto se conoce como "lazy load", y significa que si yo tengo una Cuota, al acceder a la propiedad Operacion el ORM se encarga de ir a la base de datos y buscar el registro, sin que yo tenga que hacer queries adicionales. Lo mismo ocurre en el Save() o en el Update()
Podria seguir, pero son las 2:40 AM y tengo sueño.