[Añado al post las correcciones de Unai]
Aprovechando que estoy estudiando un poco sobre Entity Framework para incorporarlo a mi actual proyecto, he decidido ir compartiendo los apuntes que voy tomando de diferentes fuentes. Cualquier corrección, mejora o aportación será muy bien recibida.
Introducción
Desde la llegada de .NET Framework allá por el 2002, muchos de nosotros hemos usado ADO.NET en nuestras aplicaciones para acceder a la base de datos. El problema de usar ADO.NET, es que estas quedan fuertemente acopladas a la base de datos.
Los ORMs simplifican la interacción de nuestras aplicaciones con los datos, introduciendo una capa de abstracción entre el código de nuestra aplicación y el esquema de la base de datos, haciendo nuestro código menos acoplado y flexible, evitando el tener que preocuparnos por las sentencias SQL y trabajar con objetos fuertemente tipados evitando así errores de sintaxis.
Un ejemplo de ORM es Entity Framework que veremos en esta serie de posts.
En EntityFramework disponemos de 3 enfoques de desarrollo:
- Database first
- Model first
- Code first
De estos 3, los más usados son Model First y Code First.
Model First:
Haciendo uso del Entity Designer tool diseñas el modelo de dominio y el designer genera un script que crea la base de datos por tí, además de las entidades, los mapeos con las tablas de la base de datos y las relaciones. Se genera un fichero de extensión .edmx
Code First:
En esta caso no usaremos un archivo .edmx para diseñar nuestro modelo, sino que escribiremos nuestro dominio directamente con C# y Entity Framework escaneará nuestras clases y basandose en una serie de convenciones intentará mapear nuestro dominio con las tablas de una base de datos existente o creandola por nosotros. Por ejemplo, si tenemos en una entidad de dominio una propiedad que se llama Id, Entity Framework asumirá que es la clave primaria de la tabla.
Contexto y entidades
En Entity Framework el contexto (ObjectContext) es la puerta de enlace entre el modelo y el framework subyacente encargado de conectar con la base de datos y mapear las operaciones de los objectos con los comandos de base de datos.
EntityFramework provee 2 tipos de contextos:
DbContext es un warpper de ObjectContext que agiliza muchas de las acciones comunes que realizamos con ObjectContext.
El constructor de DbContext acepta como parámetro el nombre de la base de datos y conecta por defecto con SQLExpress o LocalDb. Si ambos están instalados, usa por defecto SQLExpress. Podemos usar otra base de datos pasando como parámetro el nombre del connection string que queremos usar en vez de el motor de base de datos por defecto. El connection string podemos almacenarlo en el archivo de configuración de nuestra aplicación (Web.Config o app.config):
Xml
<configuration>
<connectionStrings>
<add name=”CommerceDB” providerName=”System.Data.SqlServerCe.4.0”
connectionString=”Data Source=Commerce.sdf” />
</connectionStrings>
</configuration>
C#
DbContext context = new DbContext(“CommerceDB”);
CRUD operations
using (var context = new DbContext(“CommerceDB”))
{
DbSet<Orders> orders = context.Set<Orders>();
var order = orders.Find(“XXX-XX-XXXX”);
order.Dispatched = DateTime.Now;
context.SaveChanges();
}
En el código anterior lo que estamos haciendo es crear un contexto, acceder al conjunto de órdenes a través del método genérico Set<T> mediante el método Find buscamos por clave primaria la orden. Una vez que tenemos la entidad, modificamos su propiedad Dispatched y salvamos los cambios en base de datos.
Tip: Es muy importante mantener en nuestra aplicación un número concurrente de DbContext bajo. Cada DbContext abre una conexión con la base de datos y la mantiene abierta durante cierto tiempo. Un número concurrente elevado de conexiones abiertas puede provocar problemas de rendimeinto en nuestra aplicación. Cuando declaramos un DbConext es recomendable hacer uso de la sentencia using que se encargará de cerrar la conexión y cualquier cache en memoria de objetos que se hayan consultado recientemente serán también eliminados.
Unai -> No es cierto, EF nunca deja abierta una conexión.Siempre la abre y la cierra de forma automática después de cada operación. De hecho el trabajo con las conexiones ha sido una de las demandas que se han satisfecho en EF 6, ahora tu le puedes indicar que eres el Owner de una conexión para que no se encargue el de la gestión de abrir/cerrar. Hay una feature specification respecto a esto en entityframework.codeplex.com.
Normalmente lo que se suele hacer por norma general, es crear una clase que deriva de DbContext y expone propiedades de tipo DbSet<T> de cada entidad de nuestro dominio.
public class CommerceContext : DbContext
{
public CommerceContext() : base(“CommerceDB”) { }
public DbSet<Order> Orders { get; set; }
public DbSet<Customer> Customers { get; set; }
}
Cuando DbContext es incializado, detecta si la base de datos existe, sino existe, Entity Framework puede crearla en base a la información de nuestra clase derivada. Para crear la base de datos podemos usar la clase generica CreateDatabaseIfNotExists<T>
var initializer = new CreateDatabaseIfNotExists<CommerceContext>();
initializer.InitializeDatabase(new CommerceContext());
Change Tracking
Entity Framework change tracking soporta 2 modos:
- Active change tracking: Cada propiedad informa al contexto de que ha cambiado
- Pasive change tracking: Es el contexto el encargado de detectar cambios antes de salvar los cambios, comprandolo con un snapshot que tomó cuando se recuperó la entidad.
Cuando llamamos al método SaveChanges del contexto, este comprueba si el modo Active change tracking está habilitado. Si solo está habilitado el modo pasivo, el DbContext llama al método DetectChanges. Este enumera todas las entidades recuperadas por el contexto y compara cada propiedad con el valor original de cuando fue recuperada. Todos los cambios serán actualizados en la base de datos.
Para soportar el modo Active change tracking deberemos marcar todas las propiedades de nuestras entidades dominio como virtuales (virtual) y Entity Framework creará un proxy en tiempo de ejecución para informar de todos los cambios.
Unai -> Hay ciertas cosas que no son correctas tal y como las comentas, te recomiendo la lectura de la siguiente serie, te pongo la 3 entrega pero están los enlaces para las otras, de Arthurs Vickers uno de los devs de EF, blog.oneunicorn.com/…/secrets-of-detectchanges-part-3-switching-off-automatic-detectchanges. Por cierto, aquí el menda hizo un par de pull request para el AddRange y RemoveRange que eliminan la necesidad de desactivar el change tracking para estas operaciones sobre N elementos.
DataAnnotations
Por defecto Entity Framework mapea el nombre de nuestras entidades de dominio y propiedades con el nombre de las tablas y columnas. Si queremos cambiar este comportamiento por defecto podemos hacer uso de atributos como [Table] y [Column]:
Unai -> Ummm, en realidad utiliza un pluralizador para estos nombres, en EF 6 este pluralizador es publico y se puede enchufar ( otro pull request del menda, y no es por echarme flores 🙂 ) con tu DbConfiguration y el método SetPluralizationService. Yo tengo un pluralizador en castellano que espero publicar en poco tiempo en codeplex dentro del proyecto contrib por si te interesa…
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
[Table("Products")]
public class Product
{
public int Id { get; set; }
[Column("ProductName")]
public string Name { get; set; }
}
Podemos representar claves foráneas de 2 maneras diferentes:
- Desde la propiedad de clave foránea a la propiedad de entidad.
- Desde la propiedad de la entidad a la propiedad de clave foránea.
A continuación se muestra un ejemplo con ambos casos:
[ForeignKey("Customer")]
public Guid CustomerId { get; set; }
public Customer Customer { get; set; }
public Guid CustomerId { get; set; }
[ForeignKey("CustomerId")]
public Customer Customer { get; set; }
En este caso es una relación a uno, para una relación a muchos bastaría con modificar el tipo de la propiedad Customer a IColelction<Customer> o IEnumerable<Customer>.
Teniendo ambas propiedades ganamos en flexibilidad y performance, porque si necesitas recuperar la entidad entera puedes acceder a ella a tarvés de la propiedad Customer y también por motivos de rendimeinto, si solo necesitas la clave a través de la propiedad CustomerId.
FluentAPI
Si queremos que nuestras clases este en otra librería que nada tenga que ver con Entity Framework y no esten llenas de atributos, podemos hacer uso de Entity Framework Fluent API.
Hay 2 maneras de usar EntityFramework Fluent API:
- Sobreescribiendo el método OnModelCreating de nuestro DbContext
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().ToTable("Customers");
modelBuilder.Entity<Customer>().HasKey(c => c.Id);
modelBuilder.Entity<Customer>().Property(c =>
c.Name).HasColumnName("CustomerName");
}
- Heredando de la clase EntityTypeConfiguration.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new CustomerMapping());
}
public class CustomerMapping : EntityTypeConfiguration<Customer>
{
public CustomerMapping()
{
ToTable("Customers");
HasKey(t => t.ID);
Property(t => t.ID).HasColumnName("CustomerId");
Property(t => t.Name).HasColumnName("CustomerName");
}
}
Yo prefiero usar la segunda opción, así tenemos más clara todo la configuración de mapeos y evitamos tener un método con cientos de lineas configuracion de mapeos.
Unai -> Correcto, en EF 6 tenéis un AddFromAssembly ( pull request mio tb ) que es capaz de pillar de forma automática todos los EntityTypeConfiguration ( incluso con constructores no públicos para evitar tentaciones de usarlos directamente ) y así te evitas tener las n-mil líneas de Configuration.Add(…)
Y hasta aquí la introducción a Entity Framework. En el siguiente post veremos las consultas.