lunes, 19 de mayo de 2008

ADO Entity Framework , El inicio. (1ra Parte)

Introducción

En el desarrollo de las aplicaciones empresariales es muy frecuente combinar frameworks y plataformas de trabajo con gestores de bases de datos que por lo general constituyen almacenes de datos relacionales. Las capas medias de los grandes y medianos proyectos suelen implementar el paradigma de la programación orientada a objeto. Como es lógico la POO y la programación estructural predominante en los SGBD son incompatibles, a este fenómeno se le conoce como desajuste de impedancia.

El acceso a datos es hace ya más de una década una filosofía que poco ha cambiado, mucha lógica de negocio va a parar a lenguajes basados en SQL, como paquetes o procedimientos de código estructurado dentro de los gestores, provocando entre otras cosas, que un cambio de gestor de datos implique un versionamiento casi total de la aplicación. Pudiéramos pensar que sacando estos segmentos de códigos, transformarlos en SQL estándar y colocarlos como cadenas de consulta dentro de la capa de acceso a datos resolvería este problema y es en gran medida cierto, pero entonces colocamos junto con nuestro código java, c#, c++, etc. , cadenas opacas e intrascendentes a estos lenguajes y sus frameworks. Estas consultas devuelven por lo general datos sin tipos, a los cuales una vez obtenido, hay que realizarle determinadas trasformaciones que nada tiene que ver con el pedido lógico del negocio. A continuación un ejemplo de una clase de acceso a Datos con ADO.NET:

class DataAccess

{

static void GetNewOrders(DateTime date, int qty) {

using (SqlConnection con = new SqlConnection(Settings.Default.NWDB)) {

con.Open();

SqlCommand cmd = con.CreateCommand();

cmd.CommandText = @"

SELECT o.OrderDate, o.OrderID, SUM(d.Quantity) as Total

FROM Orders AS o

LEFT JOIN [Order Details] AS d ON o.OrderID = d.OrderID

WHERE o.OrderDate >= @date

GROUP BY o.OrderID

HAVING Total >= 1000”;

cmd.Parameters.AddWithValue("@date", date);

DbDataReader r = cmd.ExecuteReader();

while (r.Read()) {

Console.WriteLine("{0:d}:\t{1}:\t{2}", r["OrderDate"],

r["OrderID"], r["Total"]);

}

}

}

}

Mapeo Objeto Relacional

Para aliviar este problema de impedancia y ayudar a los desarrolladores a que cada vez más se ocupen del buen diseño de la aplicación de negocio que realicen, han surgido un grupo de frameworks conocidos como ORMs (Object Relational Mappings), mapeo objeto relacional.

Veamos la definición según Miguel Angel Morán [blog] :

“Al proceso de mapeo automatizado entre una entidad del paradigma orientado a objetos a una tabla de una base de datos relacional se le conoce como ORM o lo que es lo mismo Object Relational Mapping”

O sea con este tipo de framework, los desarrolladores pueden crear Entidades inteligentes capaces de persistir en gestores de BD, de forma automática. Este mapeo generalmente hace corresponder a una tabla de la BD una clase en el modelo de entidades. Las relaciones son modeladas de igual manera, incluyendo referencias en cada extremo de las entidades y su multiplicidad la indica el tipo de relación. Por ejemplo A y B se relacionan siendo A el extremo uno o cero y B el extremo mucho, generalmente el mapeo de estas tablas conlleva a que la entidad A tendrán un referencia a una colección de B, y cada entidad B tendrá una referencia a su extremo A. Esto no siempre se cumple como regla, la mayoría de los frameworks dan determinadas flexibilidades a los desarrolladores para ampliar las opciones de mapeo, incluyendo cosas como herencia y mapeo de varias entidades sobre una tabla y viceversa.

En este artículo estaremos viendo ADO.NET Entity Framework, que constituye uno de los pilares del acceso a datos de Microsoft.

ADO.NET Entity Framework

Es un framework desarrollado por Microsoft, aún en versión beta, que forma parte de la familia de ADO.NET para el acceso a datos. Permite, como Framework ORM, modelar los datos desde un nivel conceptual habilitando la separación de la capa de acceso a datos del esquema relacional de la base de datos. De esta forma los datos pueden ser accedidos como objetos de .NET y no como estructuras relacionales y normalizadas. Se alinea con el concepto moderno de implementación de aplicaciones de negocio (LOB) permitiendo al desarrollador acceder a un modelo conceptual. Crea una capa adicional a la aplicación, siendo esta la forma en que se logra la independencia entre los esquemas de BD y los modelos conceptuales. Esta independencia se traduce en la existencia, en la arquitectura del framework, de dos modelos, el de datos y el conceptual (EDM). De esta manera existe un proveedor (provaider) para cada modelo, encargados de realizar el mapeo entre las entidades conceptuales y los datos que mapean y viceversa.

Pero dejemos que los expertos en el tema, abunden más en los principales conceptos que rodean a ADO.NET Entity Framework.

Ver:

http://www.microsoft.com/spanish/msdn/articulos/archivo/041206/voices/

Next-Generation.mspx

http://www.microsoft.com/spanish/msdn/articulos/archivo/041206/voices/

adonetentity.mspx

Instalando componentes

Entrando en un ejemplo práctico del uso de ADO EF, con VS2008 (V9) y el .NET Framework 3.5 en nuestra PC, solo necesitamos instalar tres elementos para comenzar a trabajar.

Links:

http://www.microsoft.com/downloads/details.aspx?

FamilyId=15DB9989-1621-444D-9B18-D1A04A21B519&displaylang=en

http://www.microsoft.com/downloads/details.aspx?
familyid=CF99C752-1391-4BC3-BABC-86BC0B9E8E5A&displaylang=en

http://www.microsoft.com/downloads/details.aspx?

FamilyId=D8AE4404-8E05-41FC-94C8-C73D9E238F82&displaylang=en

Una vez descargados estos instaladores debemos seguir el orden que nos muestra la siguiente figura:

A grandes rasgos:

EFB3SetupX86.exe: Instala el framework ADO EF Beta 3.

VS90-KB945282.exe: Instala unas extensiones para VS 2008, necesario para instalar las herramientas del framework.

EFToolsSetupX86.exe: Instala las herramientas del framework en VS 2008.

Una vez instalados estos 3 elementos solo basta tener acceso a algún Servidor de SQL Server 2000, 2005 donde esté instalado el clásico esquema de BD: Northwind.

Descripción del ejemplo

En este primer ejemplo, trataremos de dar los primeros pasos en el uso del Framework y sus herramientas. Las tablas que mapearemos son:

· Categories

· Products

La relación entre ellas es de 1-* siendo Categories el extremo 1.

Lo que haremos en primera instancia será crear el modelo utilizando las herramientas instaladas en VS.


Pongámonos el overol y a trabajar:

  1. Abrir el VS 2008 y crear un proyecto de consola. Nombrémoslo Mapeo_Productos.
  2. Adicionar elemento al proyecto, click derecho Add->New Item, una vez aquí seleccionar ADO.NET Entity Data Model. Tal como se muestra en la siguiente figura:
3. Presionando el botón Add, comenzamos a configurar un conjunto de elementos en un Wizard que nos provee VS. La siguiente secuencia de imágenes nos indica que poner en cada caso:

Paso1: Seleccionamos generar el modelo desde un esquema de datos.

Paso 2: Seleccionamos la conexión a la BD NortWind y marcamos la casilla tal como muestra la imagen. Con ello garantizamos que la cadena de conexión se escriba en el config de la aplicación.


Paso 3: Seleccionar las tablas que deseamos mapear (Categories y Products) en este modelo así como nombrar el Namespace donde se generarán las clases .NET que mapean dichas tablas.

Paso 4: Pulsamos el botón Finish y listo, tenemos creado nuestro primer modelo.


4. El modelo generado se observa tal como la siguiente imagen:

En la parte inferior observe como cada atributo de la tabla Categories es mapeados por una propiedad de la clase Categories generada en el modelo.

1.55. Finalmente nuestro Solution Explorer debiera verse de la siguiente forma:

Están marcados los Assamblys que se incluyen automáticamente como referencias así como el modelo generado.

Llegados a este punto describamos un poco el código generado por las herramientas.

[assembly: global::System.Data.Objects.DataClasses.
EdmSchemaAttribute()]

[assembly: global::System.Data.Objects.DataClasses.
EdmRelationshipAttribute(

"NorthwindModel","FK_Products_Categories", "Categories", global::System.Data.Metadata.Edm.RelationshipMultiplicity.
ZeroOrOne,
typeof(NorthwindModel.Categories), "Products",
global::System.Data.Metadata.Edm.RelationshipMultiplicity.Many,
typeof(NorthwindModel.Products))]

// Original file name:

// Generation date: 18/05/2008 20:37:17

namespace NorthwindModel

{

///

/// There are no comments for NorthwindEntities in the schema.

///

public partial class NorthwindEntities:
global::System.Data.Objects.ObjectContext

{

///

/// Initializes a new NorthwindEntities object using the connection
string found in the 'NorthwindEntities' section of the application
configuration file.

///

public NorthwindEntities() :

base("name=NorthwindEntities", "NorthwindEntities")

{

}



}

Observamos en la cabecera de este código el atributo [...EdmSchemaAttribute] que indica que la próxima línea constituye un atributo del modelo generado. Esta línea siguiente es la definición de la relación de navegación entre la clase [Categories] y la clase [Products], indicando de qué tipo es (ZeroOrOne) y que propiedad la encierra en cada extremo. Describe para el framework quien es el extremo uno y quien es el extremo muchos.

Un poco más abajo, dentro del namespace NorthwindModel, se generó la clase NorthwindEntities, que hereda de la clase ObjectContext. Esta es la clase encargada de mantener la conexión e información de los metadatos necesaria para leer, escribir, actualizar o borrar entidades en la BD. O sea constituye la llave o entrada, entre el modelo conceptual y la BD. Además referencia internamente a la clase ObjectStateManager, encargada del seguimiento de las entidades en cuanto a cambios y resolución de identidades, de ella estaremos comentando en próximas publicaciones.

[global::System.Data.Objects.DataClasses.EdmEntityTypeAttribute(Namespace
Name="NorthwindModel", Name="Categories")]

[global::System.Runtime.Serialization.DataContractAttribute()]

[global::System.Serializable()]

public partial class Categories : global::System.Data.Objects.DataClasses.EntityObject

{

///

/// Create a new Categories object.

///

///
Initial value of CategoryID.

///
Initial value of CategoryName.

public static Categories CreateCategories(int categoryID, string categoryName)

{

Categories categories = new Categories();

categories.CategoryID = categoryID;

categories.CategoryName = categoryName;

return categories;

}

///

/// There are no comments for Property CategoryID in the schema.

///

[global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute

(EntityKeyProperty=true, IsNullable=false)]

[global::System.Runtime.Serialization.DataMemberAttribute()]

public int CategoryID

{

get

{

return this._CategoryID;

}

set

{

this.OnCategoryIDChanging(value);

this.ReportPropertyChanging("CategoryID");

this._CategoryID = global::System.Data.Objects.DataClasses.
StructuralObject.SetValidValue(value);

this.ReportPropertyChanged("CategoryID");

this.OnCategoryIDChanged();

}

}



}

En esto otro segmento de código generado se muestra la clase Categories, marcada como tipo de entidad del modelo (EdmEntityTypeAttribute), como clase Serializable y como DataContractAttribute, este último tiene que ver mucho con la relación que existe de forma natural entre las entidades generadas y el modelo de programación WCF, pero esto es un tema que estaremos viendo a fondo muy pronto por aquí. Se muestra además la propiedad CategoryID, que mapea la llave primaria de la tabla Categories, nótese que entre un conjunto de operaciones que se realizan en el set de la property , se realiza una llamada el método SetValidValue,quien se encarga de asignar un ID (Auto-Numérico) válido a la entidad.

Llegados a este punto, pongamos al framework en acción. Lo que trataremos de realizar ahora será una consulta a los datos existentes en la tabla. Antes que nada dejemos a un lado las viejas prácticas de acceso a datos y preparémonos para algo realmente sorprendente: Linq to Entities.

Para consultar los datos del modelo existen tres posibles formas:

  • EntityClient
  • Servicios del objeto
  • Linq to Entities.


En este artículo veremos dos de ellas. Comenzamos por encuestar al modelo mediante servicios de Objeto, el código para obtener el nombre de los productos cuya categoría es: “
Beverages” es el siguiente:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Data.EntityClient;

using System.Data.Common;

using System.Data;

using NorthwindModel;

using System.Data.Objects;

namespace Mapeo_Productos

{

class Program

{

static void Main(string[] args)

{

string Category = "Beverages";

NorthwindEntities model = new NorthwindEntities();

ObjectQuery query = model.CreateQuery(

"SELECT VALUE p FROM Products AS p INNER JOIN Categories AS c on p.Categories.CategoryID = c.CategoryID where c.CategoryName = @Category",

new ObjectParameter("Category", Category));

foreach (Products c in query)

Console.WriteLine(c.ProductName );

Console.ReadKey();

}

}

}


Note que la consulta está basada en SQL y para poder ejecutarla no en necesario crear conexiones ni otro tipo de configuración inicial, solo crear el objeto de contexto
NorthwindEntities, crear un ObjectQuery<Products>, al cual debemos pasar la cadena SQL, pero teniendo en cuenta que estamos consultando nuestro modelo y no la base de datos, de ahí que la condición de INNER JOIN sea: p.Categories.CategoryID = c.CategoryID y no: p.CategoryID = c.CategoryID, como normalmente se realiza sobre la BD. Accedemos a las categorías de un producto mediante la property Categories de la entidad Products. El resultado debiera ser algo parecido a lo que muestra la siguiente imagen.

Hasta este momento podemos no notar grandes ventajas para los desarrolladores, pero observemos el siguiente código que devuelve los mismos resultados que el anterior:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Data.EntityClient;

using System.Data.Common;

using System.Data;

using NorthwindModel;

using System.Data.Objects;

namespace Mapeo_Productos
{
class Program
{
static void Main(string[] args)
{
NorthwindEntities model = new NorthwindEntities();

List<Products > query = (from p in model.Products

where p.Categories.CategoryName == "Beverages"

select p).ToList();

foreach(Products c in query)

Console.WriteLine(c.ProductName);

Console.ReadKey();
}
}
}

Esta maravilla se conoce como Linq To Entities, ese poderoso lenguaje de consulta incluido en el .NET framework 3.5, cuenta con soporte para ADO EF lo cual hace las delicias de muchos desarrolladores. Pero bueno dejemos el comercial detrás y expliquemos un poco el código. Como en los Servicios de Objeto, lo primero es crear una instancia de nuestro Modelo, para luego encuestarlo haciendo uso de Linq, en este caso, tomando model.Products como Source y navegando desde los productos hacia las categorías, gracias a la property Categories, obtenemos todos los productos de nuestra BD que cumplen la condición expuesta. Este resultado lo casteamos a una lista de productos (List<Products>) para mostrar en pantalla los resultados. Esto pudiera parecer incómodo pues tiene mucha similitud con SQl, probemos entonces algo como esto:

class Program

{

static void Main(string[] args)

{

NorthwindEntities model = new NorthwindEntities();

List<
Products > query = model.Products.Where(p => p.Categories.CategoryName == "Beverages").ToList();

foreach(Products c in query)

Console.WriteLine(c.ProductName);

Console.ReadKey();

}

}

}

De igual forma obtenemos los mismos resultados.

Hasta aquí este primer acercamiento a ADO EF. Espero que le haya sido productivo y cuando tenga que crear capas de acceso a datos para sus proyectos, piense en ADO EF como una opción real que potenciará su productividad. En próximos artículos estaré comentando acerca de cómo realizar las operaciones CRUD sobre las entidades de este modelo. Nos vamos, nos vemos.

2 comentarios:

Anónimo dijo...

Muy buen articulo Brayan.. pero los problemas empiezan muy pronto...

tengo un windows XP SP2 con VS2008 SP1 y al intentar instalar el fichero EFB3SetupX86.exe recibo el siguiente error.....
""Product: ADO.NET Entity Framework 1.0 (Pre-Release Version) -- ADO.NET Entity Framework 1.0 (Pre-Release Version) can only be installed if Microsoft .NET Framework 3.5 is installed.""

Te aseguro que tengo el framework 3.5 instalado... ¿alguna solucion?

Ing. Brayan Alonso Fernández dijo...

Bueno disculpa la demora, he estado viajando un poco. Bueno si tienes instalado el NET 3.5 SP1 ya no necesitas instalar el beta 3 de ADO EF, solo necesitarias el SP1 del VS 9 (2008) para poder tener las tools del ADO EF, espero que esto te ayude, suerte y gracias por leer mi blog.