sábado, 1 de marzo de 2008

LinQ to Sql - Data acces label


LinQ to Sql (Arquitectura de capas):


En esta parte del artículo veremos como utilizar el objeto LinQ to sql, comprender como trabajaremos, es decir, como definiremos la estructura de nuestro proyecto, esto se hará utilizando bibliotecas de clases.

En esta fase veremos como interactuar con las entidades de la Base de Datos directamente con LinQ to Sql, es decir, la lógica transaccional la definiremos en nuestras clases que atenderán los objetos principales.
Para los que han desarrollado antes en .Net de buena forma, sabran que definir una Capa de Datos y una Capa de Negocios en bibliotecas de clase, permite mayor portabilidad, reuzabilidad y permite migrar de una versión a otra sin tener que afectar de gran forma los niveles de interfaces de usuario.


No voy a explicar este tema porque los buenos programadores no lo necesitan, solo me detendré a explicar el objeto LinQ to Sql y las cosas que haremos con este



En el caso de que no tengan VS2008 veremos a mediados de mes de Marzo como crear una capa de Datos y de negocio con Visual Studio 2005, pero el interez que tengo ahora es de definir una capa con LINQ to Sql.

El primer paso será habrir nuestro Visual Studio 2008 y seleccionar la opción crear proyecto nuevo, en este caso, crearemos un proyecto de bibliotecas de clase en C#. Pueden llamarlo como quieran, yo lo llamaré "CapaDato" y la solución la llamare WinApp.

Ya creado el proyecto, agregaré el objeto "LINQ TO Sql Classes" a la solución, yo lo he llamado dcPrueba.dbml dc asociado a "Data Clase", pero como vamos a trabajar con el objeto usuario, podrían llamarlo dcUsuario, pero deben tener que considerar que, deben cambiar las llamadas de nombre, ya que yo utilizaré dcPrueba.

Hasta ahora todo bien, veremos que se ha desplegado un diseñador del objeto LinQ to Sql Classes, que muestra dos zonas, una es para asociar las entidades que utilizaremos y que definirán las estructuras a utilizar en las capas que definiremos más adelante. En la zona que aparece a la derecha del diseñador del objeto LinQ to Sql Classes, se agregan los métodos de Bases de Datos (Procedimientos almacenados), que se utilizarán. Nosotros solo trabajaremos en este artículo con accesos directos y al final de este, veremos como consumir los procedimientos almacenados. La interfaz de diseño se puede observar a continuación:


El siguiente paso será asociar las dos entidades que hemos creado en la base de datos a la sección de creación de clases de objetos, para ello crearemos una conexión a la Base de Datos LINQ.




Luego arrastraremos las dos entidades a la sección derecha del diseñador del objeto LinQ to Sql Classes, quedando este como se muestra a continuación:




Si se observa, en el explorador de clases se mostrarán tres clases, dos asociadas a las estructuras de las entidades, que constituyen el tratamiento de datos para el objeto y, una que presenta los métodos de diseño y transacción con la Base de Datos. Si observamos desde el explorador de soluciones solo veremos un archivo, que es el que hemos creado en el diseñador del objeto LinQ to Sql Classes, pero dentro de este existe un archivo .designer.cs que presenta las estructuras de las clases que se observan dentro del explorador de clases, este archivo es dcPrueba.designer.cs o, para los que definieron dcUsuario sera dcUsuario.designer.cs

Creando un Data Access Label

Bueno hasta ahora tenemos construida la lógica de estructura y la lógica que soportará las transacciones en el diseñador del objeto LinQ to Sql Classes (todo en unos pocos pasos). Lo siguiente será definir las rutinas de acceso a datos, para ello agregaremos a la solución una clase que llamaremos dalUsuario.

En la clase dalUsuario (dal por data access label), definiremos los métodos de consulta, inserción, actualización y eliminación de registros, reutilizando, en este caso los métodos creados por el diseñador. Pero antes de hacer esto, debemos entender el comportamiento del objeto.

Si creamos un objeto, en donde rescatamos los detalles de factura y, tenemos definidos en el objeto la estructura de clases de detalle de facturas y de factura. Al buscar el detalle de esta, LinQ to Sql retornará también la cabecera de la factura asociada a los detalles, según la estructura definida, por lo cual, si queremos solo los datos específicos de una estructura deberemos filtrar estos.

Existen dos formas de retornar la información:

  • Recorriendo lo rescatado y creando una lista de tipo de clase o,
  • Devolviendo directamente el resultado cargado a una variable según una estructura creada.

Bueno en este ejemplo haremos la primera, pero les pondré el código y les daré las indicaciones para realizarlo de la segunda forma:

Recorriendo lo rescatado con LinQ to SQL y creando una lista

Lo primero que debemos hacer es crear dentro del archivo dalUsuario.cs, una nuava clase pero fuera del espacio de definición de la clase dalUsuario, es decir en el namespace la siguiente definición de estructura de clase:


public class clsUsuario {
private int _usuCodigo;
private string _usuLogin;
private string _usuClave;
private char _usuEstado;
public int Codigo{
get { return _usuCodigo; }
set { _usuCodigo = value; }
}
public string Login {
get { return _usuLogin; }
set { _usuLogin = value; }
}
public string Password {
get { return _usuClave; }
set { _usuClave = value; }
}
public char Estado {
get { return _usuEstado; }
set { _usuEstado = value; }
}
}

Esta estructura será utilizada tanto para crear la lista a retornar, como para crear el objeto de búsqueda de la segunda opción mencionada "Devolviendo directamente el resultado cargado a una variable según una estructura creada".

Bueno, como dijimos, LinQ to SQL permite realizar transacciones desde dos perspectivas, por consultas desde LINQ to sql y las entidades o desde procedimientos almacenados que retornarán la información. Para cualquiera de los dos casos deben existir estructuras de clases que capturen la información, en el caso de que se desea desplegar la información.

Para utilizar un procedimiento que realice inserción, modificacion o eliminacion sobre una entidad de una tabla, se debe habilitar en el administrador de propiedades del objeto (Veremos esto más adelante).

Bueno, ahora a picar el código: Como sabemos, la creación de capas debe de ser de forma ascendente, por lo cual picaremos el código de la capa de acceso a datos (Data access label) y luego nos centraremos en la capa de negocios.

Función que capturará la información de usuarios y creará la lista de los mismos:

Primero, en el espacio de clase dalUsuario crearemos una variable de clase que llamaremos dc de la siguiente forma:

dcPruebaDataContext dc = new dcPruebaDataContext();

Con esto tendremos acceso a los objetos y métodos transaccionales que se han creado para realizar las intervenciones en las entidades de la base de datos, para consultar la información de usuario y retornarla, debemos definir la siguiente función.

// metodo que rescata la informacion de todos los usuarios registrados
public List<clsUsuario> funUsuarioObtener() {
// creamos una estructura que contendr la informaci¢n de los todos los registros
List<clsUsuario> lstUsuario = new List<clsUsuario> { };
try {
var usu = from u in dc.lnqUsuarios
orderby u.usuClave, u.usuLogin, u.usuClave, u.usuEstado ascending
select u;
// realizamos la asignaci¢n de datos
if (usu.Count() > 0){
// cargamos la informaci¢n a retornar
foreach (lnqUsuario u in usu){
clsUsuario cls = new clsUsuario();
cls.Codigo = u.usuCodigo;
cls.Login = u.usuLogin;
cls.Password = u.usuClave;
cls.Estado = u.usuEstado;
lstUsuario.Add(cls);
cls = null;
}
}
}
catch (Exception ex){
throw ex;
}
return lstUsuario;
}


Comentemos el código: Como se observa, la función retornará una lista de estructura de la definición de clase que hemos creado, cuando vamos a buscar la información, lo primero que haremos será utilizar la siguiente sentencia para rescatar esta:

var usu = from u in dc.lnqUsuarios
orderby u.usuClave, u.usuLogin, u.usuClave, u.usuEstado ascending
select u;


Lo que hacemos es seleccionar la información de la entidad lnqUsuarios utilizando la variable de objeto dc, ahora pueden observar que estoy ordenando la información para desplegarla. Luego en "u" se carga la inmformación y la asignamos a la variable usu, que es la que utiliza la declarativa var (nuevo tipo de dato definido para linq).
Puede parecerles que este tipo de dato es muy parecido a la declarativa de javascript, el cual permite definir una variable de cualquier tipo y utilizarlo, no se si microsoft ha tomado la misma idea.
Bueno si quisieramos rescatar directamente la información en la variable var usu y no recorrer el resultado para crear una lista, lo que debemos hacer es lo siguiente:

var usu = from u in dc.lnqUsuarios

orderby u.usuClave, u.usuLogin, u.usuClave, u.usuEstado ascending
select new clsUsuario{
Codigo = u.usuCodigo,
Login = u.usuLogin,
Password = u.usuClave,
Estado = u.usuEstado
};

O bien

IEnumerable<clsUsuario> usu = from u in dc.lnqUsuarios
orderby u.usuClave, u.usuLogin, u.usuClave, u.usuEstado ascending
select new clsUsuario{
Codigo = u.usuCodigo,
Login = u.usuLogin,
Password = u.usuClave,
Estado = u.usuEstado
};



Esto automáticamente nos cargará la información en la variable, pero al retornar la información en la función, debemos asegurarnos que ésta este definida como var o como IEnumerable<clsUsuario> respectivamente, es decir:

public var funUsuarioObtener() {}

public IEnumerable<clsUsuario>funUsuarioObtener() {}


El problema de esto o la consideración que se debe tomar es que debes tener la referencia a System.Linq en todas las capas para tratar la variable retornada.
Como mi intención es que aprendan lo más posible se los pongo desarrollado para que ustedes decidan que opción les conviene.
El siguiente paso será realizar el método que me permitirá rescatar la información de un usuario en específico, la cual la presento a continuación:

// metodo que rescata la informacion de un usuario en especifico
public lnqUsuario funUsuarioEspecificoObtener(string usuLogin, string usuClave){
lnqUsuario usuario = null;
try{
var usu = from u in dc.lnqUsuarios
where (u.usuLogin == usuLogin && u.usuClave == usuClave)
select u;
if (usu.Count() > 0)
usuario = usu.First();
}
catch (Exception ex) {
throw ex;
}
return usuario;
}

Ahora si observan, rescatamos la información asociando condiciones en la clausula where, como se observa en el código anterior. Esta información, nos retornará un registro de usuario y, como dijimos, dentro de la estructura de clase lnqUsuario se agrega la estructura y la información del empleado. (Prueben esto, ya lo verán).
Ahora crearemos la función de insertar un nuevo usuario en la tabla de usuarios, para esto crearemos un procedimiento, ya que no necesitamos retornar valor, solo necesitamos controlar y elevar los errores que se presenten, en el caso de que no exista error, la operación se considera como exitosa:

// metodo encargada de crear un nuevo usuario utilizando la definici¢n creada en csharp con linq to sql class
public void prcUsuarioInsertar(int empCodigo, string usuLogin, string usuClave) {
try {
lnqUsuario usuario = new lnqUsuario();
usuario.empCodigo = empCodigo;
usuario.usuLogin = usuLogin;
usuario.usuClave = usuClave;
usuario.usuEstado = 'A';
dc.lnqUsuarios.InsertOnSubmit(usuario);
dc.SubmitChanges();
}
catch (Exception ex) {
throw ex;
}
}

Comentemos el código: Lo primero es reconocer que debemos validar la no existencia de los datos enviados a esta función, para ello lo ideal es consultar la función de búsqueda del usuario específico y verificar que el resultado de la función sea null para hacer cualquier acción de inserción.

bueno en este caso podremos ver que la variable del objeto Data Context dcPrueba que hemos definido, presenta un método llamado "InsertOnSubmit", que se ha creado para cada una de las estructuras de clases de entidades mapeadas, a la cual se le debe pasar un objeto del tipo de la misma clase para realizar la inserción.
Esta acción no se llevará a cabo hasta que no se ejecute el método SubmitChanges del objeto Data Context creado.
Ahora definiremos el método encargado de realizar la modificación de datos de los usuarios, esto se debe hacer de la siguiente forma:

// metodo interno en csharp que permite realizar la modificaci¢n de datos de un registro
public void prcUsuarioModificar(string usuLogin, string usuClave, string usuNewLogin, string usuNewClave) {
try{
// realizamos la b£squeda del usuario para asignar a la variable
var usu = from u in dc.lnqUsuarios
where (u.usuLogin == usuLogin && u.usuClave== usuClave)
select u;
// verificamos que el usuario exista
if (usu.Count() > 0) {
//si existe entones asignamos un objeto para realizar la modificaci¢n
lnqUsuario usuario = dc.lnqUsuarios.Single(u => u.usuCodigo == usu.First().usuCodigo);
// modificamos los datos del registro
usuario.usuLogin = usuNewLogin;
usuario.usuClave = usuNewClave;
// guardamos los cambios
dc.SubmitChanges();
}
}
catch (Exception ex) {
throw ex;
}
}

Comentemos el código:
Si observan, ahora he utilizado una clausula que nos permite rescatar un registro en especifico que cumpla con los criterios definidos, podría también haver usado el método de consulta de usuarios que retornará en una variable de estructura de clase la información del usuario, pero para tener un espectro mayor de posibilidades les muestro este tipo de búsqueda
Bueno, lo que nos queda es eliminar un registro, para ello, como en el método insert o update, tendremos tambien un método creado en en esquema de la estructura de clase que mapea la entidad llamado DeleteOnSubmit, al cual hay que pasarle una estructura del tipo de clase que define la acción con la información del registro que se desea eliminar en la entidad mapeada.
Esto se hace de la siguiente forma:

// metodo encargado de realizar
// la eliminación de un registro
public void prcUsuarioEliminar(string usuLogin, string usuClave) {
try {
// realizamos la b£squeda del usuario
// para asignar a la variable
var usu = from u in dc.lnqUsuarios
where (u.usuLogin == usuLogin && u.usuClave== usuClave)
select u;
// verificamos que el usuario exista
if (usu.Count() > 0) {
// creamos un nuevo objeto con el usuario seleccionado
lnqUsuario usuario = usu.First();
// realizamos la eliminaci¢n del registro a espera de submit
dc.lnqUsuarios.DeleteOnSubmit(usuario);
dc.SubmitChanges();
}
}
catch (Exception ex) {
throw ex;
}
}


Comente el código: En esta acción podemos ver que primero se realiza una búsqueda del usuario deseado según criterios de selección, que permite retornar la información a una estructura de clase, en este caso he realizado una consulta común y he utilizado el método First, para que vean que podemos movernos dentro del resultado de la búsqueda. Este método retorna la información en una estructura de la clase de usuarios, luego realizamos las acciones de eliminación. como se muestra en las ultimas líneas.
NOTA: LINQ también permite transacciones, no las he puesto en este artículo ya que las acciones se realizaban en una sola entidad de una Base de Datos, pero si no están trabajando con SP y desean tener un comportamiento transaccional controlado, esto se puede manejar en la lógica de programación. Como buenos desarrolladores que son, debe tenerse en consideración, los begin transaction y commit transaction que se definen en los SP o las transacciones las manejan en el programa o en los SP.

1 comentario:

Anónimo dijo...

Genial!, me ayudo muchisimo como base para mis proyectos que tenia que mostrar en la universidad, necesitaba un ejemplo de linq en mi proyecto que estaba con capas, me recomendaron que use linq para filtrar datos pero despues de una larga investigacion y despejando tantas lagunas mentales sobre este tema logre hacer mi trabajo. Genial!