Mostrando entradas con la etiqueta Entity Framework. Mostrar todas las entradas
Mostrando entradas con la etiqueta Entity Framework. Mostrar todas las entradas

martes, 5 de julio de 2011

Entity Framework: Code First

Finalizando la serie de posts sobre los enfoques de Entity Framework, ahora intentaré explicar el enfoque Code First.

Puedes leer mis posts anteriores sobre Database First y Model First.

Este post es más bien conceptual, puede contener algunos detalles prácticos, pero no pretende mostrar ejemplos ni guías paso a paso. En futuros post intentaré dar ejemplos prácticos sobre cada uno de estos enfoques.

Code First

Este enfoque está disponible a partir de la versión ADO.NET Entity Framework 4.1 y nos permite crear un modelo de clases POCO (Plain Old CLR Object) a partir del cual podemos generar una base de datos y/o mapear esas clases a una base de datos existente.

Aquí no existe un archivo edmx con las definiciones XML del modelo conceptual, el modelo de datos y el mapeo entre ambos.

El modelo conceptual está definido por el conjunto de clases que hemos creado para representar a las entidades de nuestro modelo de dominio. La idea es que nuestras clases POCO sean mapeadas directamente a la base de datos.

La ventaja de usar clases POCO es que el modelo de clases es más limpio y las clases no tienen dependencia con EntityFramework (como sucede cuando las entidades heredan de EntityObject, implementan ciertas interfaces y contienen atributos).

 

¿Cómo se mapean las clases POCO a la base de datos?

De forma predeterminada, se utiliza un mapeo por convención en lugar de configuración. Es decir, si no especificamos el mapeo, se utilizan los nombres de las clases y sus propiedades para generar los nombres de las tablas y sus campos.

Este mapeo por convención puede ser suficiente en nuevos proyectos, donde estamos definiendo y diseñando el modelo conceptual y la base de datos desde cero.

 

¿Qué pasa si ya tengo una base de datos heredada?

Si el esquema de la base de datos heredada coincide con el modelo conceptual, entonces el mapeo por convención seguirá siendo suficiente y aplicable y todos felices! (Probabilidad: 0.0000001%)

La experiencia indica que si ya tenemos una base de datos heredada, es altamente probable que haya diferencias con el modelo conceptual utilizado en la aplicación. Entonces:

  • ¿Qué hacemos cuando el esquema de base de datos es diferente al modelo conceptual?
  • ¿Qué tal si quiero especificar restricciones, tipos de datos o validaciones a los campos?

Bien, podemos resolver estas cuestiones y otras tantas a través de:

  1. Data Annotations
  2. Mapping Fluent API

 

Data Annotations

Estas clases están disponibles en el namespace System.ComponentModel.DataAnnotations y permiten especificar restricciones o validaciones, especificar nombres de tablas o campos, etc.

image_thumb11

Hay dos artículos interesantes sobre Data Annotations que recomiendo (en inglés):

 

Mapping Fluent API

La segunda alternativa que Code First ofrece para definir el mapeo entre el modelo conceptual y la base de datos es mediante Fluent API. Esta API permite especificar la configuración de mapeo y otras configuraciones mediante código.

image_thumb3

Los siguientes enlaces contienen un artículo interesante con ejemplos sobre Code First Fluent API y un video paso a paso:

 

¿Y el contexto de datos de Code First?

De forma predeterminada, con el enfoque Database First y Model First disponemos de una clase ObjectContext del cual hereda el contexto de datos de nuestra aplicación.

Con el enfoque Code First tenemos un nuevo contexto llamado DbContext del cual debe heredar el contexto de datos de nuestra aplicación.

image_thumb5

En el contexto de datos, básicamente estamos definiendo cuáles son los conjuntos de entidades que serán mapeados a la base de datos.

Adicionalmente, podemos usar inicializadores de bases de datos, especificar configuraciones de mapeo o interceptar las operaciones de SaveChanges (entre otras).

DbContext no es más que una versión simplificada de ObjectContext, y también puede ser usando con los enfoques Database First y Model First.

 

¿Cómo o cuando se genera la base de datos?

Cuando se crea una instancia de DbContext, de forma predeterminada, se utiliza una estrategia de inicialización que crea a base de datos si no existe, opcionalmente se puede agregar código para insertar registros después de la creación.

Estas operaciones de inicialización pueden ser implementadas usando el método Database.SetInitializer. Éste método toma como parámetro una estrategia de inicialización de base de datos.

Entity Framework Code First incluye 3 inicializadores:

  1. CreateDatabaseIfNotExist
  2. DropCreateDatabaseAlways
  3. DropCreateDatabaseIfModelChanges

Podemos utilizar estos inicializadores de base de datos, cuando la aplicación se inicia. Por ejemplo, en una aplicación web podríamos usarlo en el evento Application_Start del archivo global.asax, en una aplicación de consola podríamos incluirlo en el método Main del programa principal.

image

Adicionalmente, podemos crear una clase que hereda de alguno de estos inicializadores y sobreescribir el método Seed para insertar registros después que la base de datos es creada.

image

Luego, usamos esta clase al iniciar la aplicación:

image

 

¿Cómo sería la inicialización de base de datos en un entorno de producción?

Bueno, es claro que en el entorno de desarrollo o pruebas, la inicialización de base de datos puede ser muy útil. Pero en un entorno de producción podría ser un problema.

Simplemente podemos “apagar” la inicialización de base de datos, pasando null como parámetro del método SetInitializer:

image

 

Conclusión

  • Code First es el nuevo enfoque soportado por Entity Framework. Permite mapear nuestras clases POCO directamente a la base de datos.
  • De forma predeterminada, el mapeo es por convención en lugar de configuración.
  • Configuración adicional de mapeo puede especificarse mediante Data Annotations o Fluent API.
  • DbContext API nos permite realizar las operaciones de acceso a datos.

Espero que esta serie de posts haya aclarado un poco más sobre las alternativas de trabajo con Entity Framework.

Dedicaré nuevos posts con ejemplos más prácticos de cada uno de estos enfoques, ventajas, desventajas, limitaciones, etc.

Saludos
~Gus

viernes, 1 de julio de 2011

Entity Framework: Model First

Continuando con la serie de posts sobre los enfoques de Entity Framework, ahora intentaré explicar el enfoque Model First.

Puedes leer sobre el enfoque Database First en mi post anterior.

Este post es más bien conceptual, puede contener algunos detalles prácticos, pero no pretende mostrar ejemplos ni guías paso a paso. En futuros post intentaré dar ejemplos prácticos sobre cada uno de estos enfoques.

Model First

La idea aquí es crear primero el modelo conceptual y a partir de este generar la base de datos.

image_thumb4

Usando Visual Studio podemos arrancar creando un archivo .edmx vacío y utilizamos el diseñador visual para crear nuestras entidades con sus propiedades y relaciones con otras entidades.

Cuando tenemos nuestro modelo conceptual completo, podemos generar los scripts de SQL para crear el esquema de base de datos.

image

¿Qué pasa si la base de datos cambia?

En realidad, la base de datos debe cambiar como consecuencia de un cambio en el modelo conceptual.

Ok ¿Qué pasa si el modelo conceptual cambia?

Habrá que actualizar el modelo conceptual de EntityFramework y generar los scripts SQL de creación de la base de datos.

Desafortunadamente, los scripts SQL generados a partir del modelo conceptual son solo scripts de creación. Es decir, no son scripts de cambios o actualización de nuestro esquema de base de datos. En consecuencia, deberíamos borrar la base de datos y volver a crearla. Obviamente, esto no será factible en un entorno de producción y deberemos elegir otras alternativas. Por ejemplo:

  1. Actualizar el esquema de datos a través de scripts escritos a manos o generados mediante una herramienta de comparación de esquema de datos. También podríamos alterar el esquema usando el Administrador de base de datos
  2. Luego deberíamos, actualizar el modelo conceptual de Entity Framework (edmx) y asegurarnos que el mapeo hacia la base de datos es correcto.
  3. Finalmente, actualizar el código existente en la aplicación.

Ojalá que en un futuro, el equipo de EF pueda incorporar un comparador de esquemas y generar scripts de actualización.

Ya tengo mi modelo conceptual creado, como sigo?

Bueno, a partir de ahora no difiere del enfoque Database First. Podemos escribir querys contra el modelo conceptual y realizar las operaciones de persistencia necesarias.

¿Las clases de entidades generadas con Model First son POCO?

De forma predeterminada NO. Las clases contienen metadata y heredan de clases dependientes de EntityFramework. Al igual que con Database First, disponemos de una clase que hereda de ObjectContext que nos permite interactuar con el modelo.

Sin embargo, es posible tener clases POCO agregando un ítem de generación de código llamado: ADO.NET POCO Entity Generator.

image

Esto elimina todo el código generado en el archivo .edmx.designer.cs y crea clases POCO basadas en plantillas T4.

image

Otra alternativa para generar clases POCO es eliminar la herramienta de generación de código EntityModelCodeGenerator asociada al archivo .edmx y generar las clases a mano.

image

image

Para finalizar este post, es interesante saber que estas técnicas para generar clases POCO, también son aplicables al enfoque Database First.

 

Conclusión

  • El enfoque Model First nos permite crear primero un modelo conceptual y generar luego el esquema de base de datos.
  • Al igual que Database First se basa en un archivo edmx para almacenar las definiciones XML del modelo conceptual, modelo de datos y mapeo entre ambos.
  • Las clases generadas automáticamente no son POCO, pero podemos generar clases POCO usando otras herramientas de generación de código.

El próximo post estará dedicado al enfoque más reciente soportado por Entity Framework: Code First.

Espero que sirva.
~Gus

jueves, 30 de junio de 2011

Entity Framework: Database First

En esta serie de post intentaré resumir los enfoques que hoy permite Entity Framework para crear nuestro modelo conceptual y resolver nuestras necesidades de persistencia.

Este post es más bien conceptual, puede contener algunos detalles prácticos, pero no pretende mostrar ejemplos ni guías paso a paso. En futuros post intentaré dar ejemplos prácticos sobre cada uno de estos enfoques.

Introducción

Actualmente con Entity Framework podemos elegir entre 3 enfoques diferentes para crear el modelo conceptual:

  1. Database First: El modelo conceptual se crea a partir de una base de datos existente.
  2. Model First: se crea el modelo conceptual y se genera la base de datos.
  3. Code First: nuevo a partir de EF 4.1. Un enfoque simplificado que permite mapear nuestras clases POCO a la base de datos usando convención, Data Annotations o Fluent API.

Este post estará dedicado al primer enfoque: Database First.

Database First

Este enfoque permite inferir un modelo de clases a partir del esquema de una base de datos existente.

image

Usando Visual Studio, podemos agregar un ítem de proyecto llamado Entity Data Model e iniciar un asistente para conectarnos a una base de datos, seleccionar los objetos que queremos incluir en el modelo (tablas, vistas, stored procedures) y generar un archivo .edmx que contiene una representación XML de:

  • El modelo conceptual (CSDL – Conceptual Schema Definition Language)
  • el modelo de datos (SSDL – Store Schema Definition Language)
  • y el mapeo entre ambos modelos (MSL – Mapping Specification Language)

El siguiente gráfico muestra un ejemplo del contenido XML de un archivo .edmx:

Es importante entender que el modelo conceptual (CSDL) no debe ser necesariamente igual al modelo de datos (SSDL), y por ello existe el modelo de mapeo (MSL).

A partir de ese archivo .edmx, se genera automáticamente (usando plantillas T4) un archivo .edmx.designer.cs (.vb en el caso de VB.NET) que contiene las clases que representan a las entidades del modelo conceptual.

Puede observarse que estas clases heredan de EntityObject y tienen varios atributos y propiedades dependientes de EntityFramework.

Típicamente, se crea una clase (entidad) por cada tabla de la base de datos, incluyendo propiedades que se mapean a campos y las relaciones entre las tablas son representadas mediante propiedades de navegación.

image

Además de las clases de entidades se genera también una clase que hereda de ObjectContext y representa el contexto de base de datos. A través de esta clase tendremos acceso a las entidades y colecciones del modelo y podremos realizar todas las operaciones de lectura y escritura de datos desde y hacia la base de datos subyacente.

En el siguiente ejemplo, BookstoreEntities es la clase generada por EF que hereda de ObjectContext:

image

Luego podemos usarla para acceder al modelo:

image

¿Puedo modificar o extender las clases de entidades generadas por EF?

Las clases generadas en el archivo .designer son re-generadas cada vez que se actualiza el modelo en el archivo .edmx. En consecuencia, no podemos modificar o agregar código a esas clases. La buena noticia es que son clases parciales y podemos extenderlas en otro archivo o bien podemos usar herencia.

¿Qué pasa si la base de datos cambia?

Habrá que actualizar el modelo conceptual. Esto puede lograrse fácilmente a través del designer del edmx:

image

Se inicia el asistente de actualización en el cual podemos agregar nuevas tablas, vistas o stored procedures, refrescar las tablas existentes o eliminar otras.

image

Una vez finalizado el asistente, el archivo edmx se actualiza y se re-generan las clases de entidades. Luego habrá que actualizar el código que hace referencia a las entidades modificadas (si corresponde).

Conclusión

  • El enfoque Database First nos permite generar rápidamente nuestro modelo de clases y mantenerlo sincronizado cuando el modelo de datos cambia.
  • El archivo edmx contiene una definición XML del modelo conceptual (CSDL), el modelo de datos (SSDL) y el mapeo entre ambos.
  • Las clases generadas heredan de EntityObject y se utiliza una clase que hereda de ObjectContext para administrar el modelo y realizar las operaciones de persistencia.

En los próximos post continuaré con los otros dos enfoques de Entity Framework (Model First y Code First), y hablaremos sobre las opciones de generación de entidades POCO, etc.

Espero que sirva, saludos.
~Gus

domingo, 8 de marzo de 2009

ADO.NET Entity Framework: The version of SQL Server in use does not support datatype 'datetime2'

Trabajando con ADO.NET EF, en mi entorno de desarrollo tengo SQL Server 2008 y en producción está SQL Server 2005. Al publicar mi aplicación, en algunas páginas obtengo el siguiente error:

The version of SQL Server in use does not support datatype 'datetime2'

SQL Server 2008 dispone de un nuevo tipo de datos Datetime2 que agrega soporte mejorado de fechas y horas.

Por ejemplo, Datetime2 soporta fechas y horas desde 0001-01-01 00:00:00 hasta 9999-12-31 23:59:59.9999999 (nanosegundos de precisión), mientras que el conocido Datetime solo acepta rangos desde 1753-01-01 00:00:00 hasta 9999-12-31 23-59:59.999 (solo milisegundos de precisión). Pueden leer más sobre esto en el siguiente enlace (en inglés): DATETIME2 vs DATETIME in SQL Server 2008

Para resolver el problema de incompatibilidad usando ADO.NET Entity Framework hay que abrir el archivo .edmx con el editor de XML y cambiar el valor del atributo ProviderManifestToken a 2005.

<edmx:Runtime>
<edmx:StorageModels>
   <Schema Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2005">

Luego guardamos los cambios en el modelo y recompilamos la aplicación. Esto resolverá el error de versiones.

Algo importante a saber es que si posteriormente actualizamos el modelo en nuestro entorno de desarrollo el atributo ProviderManifestToken volverá a establecerse al valor 2008 y deberemos cambiarlo manualmente antes de publicar nuestra aplicación.

Quizás con una tarea postbuild podríamos hacer este cambio automáticamente. Si alguien se anima a crear una tarea como esta será bienvenida.

Espero que sirva.
Saludos, Gus

jueves, 29 de enero de 2009

ADO.NET Entity Framework: ¿Cómo actualizar un objeto desconectado?

Un escenario común es el siguiente:

"Al enviar un formulario web, creo una entidad Pedido con los datos proporcionados por el usuario y lo entrego a mi capa de negocios o servicios para que se valide y persista en el almacén de datos."

En este escenario la entidad Pedido enviada desde la página web (o enviada desde un Web Service), se encuentra en estado Deattached. Para que podamos persistir sus cambios necesitamos asociarla a un contexto y luego llamar al método SaveChanges().

Si la entidad aún no existe en el almacén de datos, debemos agregarla. Esto es muy simple, usando los métodos AddTo* generados en el modelo:

context.AddToOrders(order);
context.SaveChanges();

En cambio si la entidad ya existe en el almacén de datos, entonces debemos asociarla al contexto y guardar los cambios. Esta tarea parece ser simple y podríamos intentar resolverla de la siguiente manera:

context.AttachTo("Orders", order);
context.SaveChanges();

Sin embargo, esto no funciona, pues el objeto order se asocia exitosamente al contexto pero en el estado Unchanged. Luego el método SaveChanges() no persiste ningún cambio.

La forma de resolver este problema es recuperando la entidad original desde el almacén de datos, aplicar los cambios, y volver a guardarla.

Buscando en la web encontré diferentes enfoques para resolver este problema. Muchos utilizan Reflection para recorrer todas las propiedades y asignar las que han cambiado. La que más me ha interesado la encontré en el blog de Cesar de la Torre.

Se trata de un extension method llamado AttachUpdated() aplicado al ObjectContext. Me he tomado el atrevimiento de renombrar le método AttacheUpdated() por UpdateObject(), pues me pareció más consistente con otros métodos que ya existen en el ObjectContext como AddObject() y DeleteObject(). El código es el siguiente:

public static void UpdateObject(this ObjectContext context, string entitySetName, EntityObject entity)
{
    var key = context.CreateEntityKey(entitySetName, entity);

    object originalEntity;
    if (context.TryGetObjectByKey(key, out originalEntity))
    {
        context.ApplyPropertyChanges(entitySetName, entity);
        context.ApplyReferencePropertyChanges(entity, (IEntityWithRelationships)originalEntity);
    }
    else
    {
        throw new ObjectNotFoundException();
    }
}

Este método primero crea la EntityKey asociada al objeto usando el método CreateEntityKey() y luego intenta recuperar el objeto desde el almacén de datos usando el método TryGetObjectByKey(). A continuación aplica los cambios usando el método ApplyPropertyChanges() y luego llama al método ApplyReferencePropertyChanges() que se encarga de aplicar los cambios a las entidades relacionadas. Este último método se trata de otro extension method:

private static void ApplyReferencePropertyChanges(this ObjectContext context, IEntityWithRelationships newEntity, IEntityWithRelationships oldEntity)
{
    foreach (var relatedEnd in oldEntity.RelationshipManager.GetAllRelatedEnds())
    {
        var oldRef = relatedEnd as EntityReference;

        if (oldRef != null)
        {
            var newRef = newEntity.RelationshipManager.GetRelatedEnd(oldRef.RelationshipName, oldRef.TargetRoleName) as EntityReference;
            if (newRef != null) oldRef.EntityKey = newRef.EntityKey;
        }
    }
}

Aquí no solo se resuelve el problema de aplicar los cambios a las propiedades del objeto en cuestión sino también los cambios en sus objetos relacionados.

Luego de crear estos extensions methods, el código final para actualizar una entidad desconectada quedaría como:

context.UpdateObject("Orders", order); //Extension method
context.SaveChanges();

He probado esto y funcionó para mí. Espero que también les sea de utilidad.

Saludos, Gus

miércoles, 28 de enero de 2009

ADO.NET Entity Framework: Nuevos proveedores de datos

En el blog del equipo de ADO.NET Entity Framework se anuncia que están disponibles nuevos proveedores para ADO.NET EF, incluyendo versiones Beta.

Pueden leer más y descargar estos providers desde el siguiente link:
http://msdn.microsoft.com/en-us/data/dd363565.aspx

image

Saludos, Gus

ADO.NET Entity Framework: ¿Cómo cambiar la ForeignKey de un objeto?

Tengo un formulario de edición de un Producto, y entre muchos otros datos, se debe seleccionar, por ejemplo, la Categoría desde un DropDownList. Al guardar el producto, se debe establecer el ID de la categoría asociada.

La intuición nos llevaría a escribir el siguiente código:

product.Category.Id = Convert.ToInt32(CategoryDropDownList.SelectedValue);

Desafortunadamente, esto provocará un error, porque la propiedad Category es null, o bien porque no se puede cambiar la propiedad Id.

Para resolver el problema podemos escribir lo siguiente:

var categoryId = Convert.ToInt32(CategoryDropDownList.SelectedValue);
product.CategoryReference.EntityKey = new EntityKey("ContosoEntities.Categories", "Id", categoryId);

En lugar de la propiedad Category estamos usando la propiedad CategoryReference y estamos asignando una nueva EntityKey. Esto permitirá que cuando ejecutemos el método SaveChanges, los cambios de la categoría asociada al producto se persistan en el almacén de datos.

Particularmente esta forma de resolver el problema me parece poco intuitiva y elegante. Algo tan simple como cambiar una clave externa implica escribir todo ese código. Sin embargo, hasta ahora, parece ser la única forma de resolver el problema.

Si nos interesa mejorar un poco nuestro código, y escribir algo más simple, podríamos crear una clase Partial de Product, y agregar una propiedad CategoryId:

public int CategoryId
{
   get
   {
       return Convert.ToInt32(CategoryReference.EntityKey.EntityKeyValues[0].Value);
   }

   set
   {
       var qualifiedEntitySet = "ContosoEntities.Categories";
       CategoryReference.EntityKey = new EntityKey(qualifiedEntitySet, "Id", value);
   }
}

Esta propiedad adicional, permitirá escribir algo como:

product.CategoryId = Convert.ToInt32(CategoryDropDownList.SelectedValue);

Muy similar a los que habíamos intentado al principio!

Antes de terminar, alguien podrá decir:

"Usando LINQ TO SQL, estas propiedades se generan automáticamente cuando se crea el modelo."

Es cierto! Pueden dirigir sus comentarios y sugerencias al Blog del Equipo de ADO.NET Entity Framework, y quizás tengamos suerte para la siguiente versión.

Hasta la próxima! Espero que sirva.
Saludos, Gus