Sie sind auf Seite 1von 18

Rompiendo el hielo con EF Code First

Hay mucha gente para la que el enfoque Code First de Entity Framework es natural e
intuitivo: crear un modelo a partir de clases POCO y delegar en Entity Framework para la
creacin automtica de una base de datos que pueda persistir el modelo. Sin embargo,
hay otra mucha gente (yo me incluyo entre ellos) que est acostumbrada a crear primero
la base de datos y despus crear el modelo (esta vez apoyado en el famoso fichero .edmx
y con el enfoque Database First).
Siendo as, me gustara que este post sirviera a escpticos de Code First que an estn
indecisos sobre si este enfoque es o no viable (como la estaba yo hace unas pocas
semanas).
Lo primero es lo primero, exactamente y en pocas palabras Qu es Code First?
Code First es un enfoque ms de Entity Framework (hay otros dos enfoques que son
Database First y Model First) que plantea lo siguiente: T crea clases POCO con tu lenguaje
favorito (C#, VB.NET, etc.) y crea relaciones entre las mismas, despus despreocpate que
ya ver yo como me las gasto para persistir tu modelo en una base de datos.
Lo importante es entender que con Code First, lo primero es el cdigo. En vez de
comenzar creando la base de datos y despus con ingeniera inversa generar las clases
POCO (como hacamos con Database First), con Code First primero creamos el modelo con
cdigo y despus se genera automticamente la base de datos. Lo cierto es que aunque el
espritu de Code First es el que he explicado, Code First tambin puede trabajar con base
de datos existentes, puedes ver ms informacin en Code First to an Existing Database.
Asumiendo para el resto del post que la base de datos se crear automticamente, Code
First nos reporta las siguientes ventajas:
En nuestro proyecto slo hablamos de cdigo, no hablamos ms de bases de datos
ni de T-SQL, al fin y al cabo, la base de datos ser simplemente un forma ms de persistir
nuestro modelo (la base de datos ser un medio, no el fin).
Parece que, definitivamente, eliminamos el desajuste de impedancia. Esto es que
nuestro proyecto ya no vive en dos mundos, el de la base de datos y el del cdigo, ya no
tenemos que saber T-SQL adems de C#, ya slo con nuestras clases POCO y Linq
podremos abordar cualquier proyecto.
Cierto es que estas ventajas tambin las conseguimos con Database First y Model First,
porque al fin y al cabo un modelo EDM en memoria es siempre el mismo con
independencia del enfoque del que provenga. Sin embargo, con Code First todo parece
ms fluido y, con un poco de suerte, slo abrirs la consola de administracin de Sql
Server para confirmar que tu modelo ha persistido y que no es todo una ilusin y tus datos
estn en el limbo.
Lo siguiente sera ver un ejemplo para poner cara y ojos a nuestro amigo Code First:
1. Crear un nuevo proyecto del tipo Aplicacin web de ASP.NET MVC 4 y llamarlo Tienda.
2. Agregar un nuevo proyecto de Biblioteca de clases a la solucin y llamarlo Models.
3. Agregar una referencia a Models desde Tienda.
4. Agregar al proyecto Models el paquete Entity Framework desde NuGet (asumimos que
el proyecto Tienda ya tendr instalado el paquete al seleccionar alguna de las plantillas de
ASP.NET MVC).
5. Agregar el siguiente cdigo al proyecto Models, con el que estamos definiendo nuestro
modelo slo a travs de cdigo y clases POCO ignorantes de la persistencia:
using System;
using System.Collections.Generic;
using System.Data.Entity;

namespace Models
{
public class TiendaContext : DbContext
{
public DbSet<Cliente> Clientes { get; set; }
public DbSet<Producto> Productos { get; set; }
public DbSet<Pedido> Pedidos { get; set; }
public DbSet<LineaPedido> LineasPedido { get; set; }
}

public class Cliente
{
// Comentar si ProxyCreationEnabled = true
public Cliente()
{
Pedidos = new HashSet<Pedido>();
}
public virtual int ClienteId { get; set; }
public virtual string Nombre { get; set; }
public virtual ICollection<Pedido> Pedidos { get; set; }
}

public class Producto
{
// Comentar si ProxyCreationEnabled = true
public Producto()
{
Lineas = new HashSet<LineaPedido>();
}
public virtual int ProductoId { get; set; }
public virtual string Descripcion { get; set; }
public virtual decimal Precio { get; set; }
public virtual ICollection<LineaPedido> Lineas { get; set; }
}

public class Pedido
{
// Comentar si ProxyCreationEnabled = true
public Pedido()
{
Lineas = new HashSet<LineaPedido>();
}
public virtual int PedidoId { get; set; }
public virtual DateTime FechaCreacion { get; set; }
public virtual int ClienteId { get; set; }
public virtual Cliente Cliente { get; set; }
public virtual ICollection<LineaPedido> Lineas { get; set; }
}

public class LineaPedido
{
public virtual int LineaPedidoId { get; set; }
public virtual int PedidoId { get; set; }
public virtual int ProductoId { get; set; }
public virtual int Unidades { get; set; }
public virtual Pedido Pedido { get; set; }
public virtual Producto Producto { get; set; }
}
}
Lo de ignorantes de la persistencia debera ir entrecomillado. Digo esto porque EF podra
crear proxys dinmicos en tiempo de ejecucin a partir de nuestras clases POCO para
soportar la carga diferida y el seguimiento de cambios. Los requisitos para la creacin
automtica de estos proxys los puedes consultar en el siguiente enlace Requisitos para
crear objetos proxy POCO (Entity Framework).
A grandes rasgos, para habilitar la creacin automtica de proxys de carga
diferidadebemos especificar virtual en todas las propiedades de navegacin y establecer a
true las propiedades LazyLoadingEnabled y ProxyCreationEnabled (por defecto estn
activadas).
Para los proxys de seguimiento de cambios, todas las propiedades de nuestra clase
deben ser virtual y las propiedades de navegacin de coleccin tienen que ser ICollection
(las cuales sern convertidas despus a EntityCollection<TEntity>). Adems tiene que valer
true la propiedad ProxyCreationEnabled (fjate que aqu no hay una propiedad especfica
para habilitar o deshabilitar la creacin de estos proxys como s la hay para los proxys de
carga diferida).
En lo relativo a inicializar las propiedades de navegacin de coleccin en el constructor de
la clase, sirve para cuando no generamos ningn tipo de proxy y queremos inicializar la
coleccin. Si este cdigo est presente y est activa la generacin de proxys de
seguimiento de cambios se producir una excepcin (no as con los proxys de carga
diferida).
En cuanto a si es o no conveniente la creacin automtica de proxys, hay todo un debate
abierto con gente a favor y gente en contra. Yo personalmente deshabilito la creacin de
cualquier tipo de proxy (con ProxyCreationEnabled igual a false).
6. Por ltimo, simplemente utilizar el modelo a travs de la clase de contexto y quedar
atnitos con la magia de Code First
using (var context = new TiendaContext())
{
var cliente = new Cliente() { Nombre = "Sergio" };
context.Clientes.Add(cliente);
context.SaveChanges();
}
Que ha generado automticamente la base de datos Models.TiendaContext en nuestra
instalacin de SQL Express con las siguientes tablas y relaciones:

Si ya conocas Code First no estars muy impresionado (lo entiendo, no te culpo), pero si
no el caso, me gustara ver la cara de asombro y perplejidad que se te ha quedado al ver
que una nuevo base de datos se ha creado automticamente con todo lo necesario para
persistir tu modelo y sin haber tirado ni una sola lnea de T-SQL No podrs negar que ha
sido alucinante?
A partir de aqu, al menos yo tuve muchas preguntas:
Por qu se cre la base de datos en SQL Express y por qu con ese nombre?
En qu momento se cre la base de datos?
Qu pasa si el modelo cambia?
Qu pasa si la estructura de base de datos no me convence, puedo cambiar algo?
Si vienes de Database First o es tu primera vez con Code First, estoy seguro que estas y
otras muchas preguntas te estarn asaltando en este preciso instante. Pues nada, vamos a
ello.
Lo primero es entender cmo funciona Code First.
El primer paso que lleva a cabo Code First es leer las clases accesibles en la clase de
contexto y crear un modelo en memoria. A continuacin infiere un esquema de base de
datos vlido para persistir el modelo. Para esta tarea, Code First se basa en convenciones,
por ejemplo si nuestra clase tiene una propiedad <NombreClase>Id, esta propiedad ser
elegida para ser la clave primaria en la base de datos. Si adems es numrica, crear el
campo clave como autonumrico, etc.
Qu cules son exactamente las convenciones de Code First para inferir el esquema de
base de datos? Pues son muchas, todas ellas en el espacio de nombres
System.Data.Entity.ModelConfiguration.Conventions, las puedes consultar aqu, pero a
grandes rasgos las ms relevantes que se han utilizado para crear nuestro modelo han sido
las siguientes:
El nombre de la base de datos ha sido <NombreContexto> plenamente cualificado,
es decir, Models.TiendaContext.
La base de datos se ha creado en la instancia de SQL Express.
El nombre de la tabla ser el nombre de la clase en plural. Como el servicio de
pluralizacin slo est disponible en ingls, tenemos nombres tan divertidos y ocurrentes
como Pedidoes. Lgicamente esto no es muy acertado y despus veremos cmo
solucionarlo.
La clave primaria de cada tabla ha sido <NombreClase>Id.
Las claves primarias son todas autonumricas.
A partir de las propiedades de navegacin y propiedades de clave externa que
encontr en el modelo, cre en base de datos las relaciones oportunas. Code First
reconoci las relaciones porque los nombres de las mismas siguieron las convenciones
predeterminadas. Por ejemplo:
o Clientes.Pedidos es una propiedad de navegacin de coleccin.
o Pedidos.Cliente es una propiedad de navegacin de referencia
o Pedidos.ClienteId es una propiedad de clave externa que ser utilizada para
llevar a cabo la relacin en base de datos entre Pedidos y Clientes.
Como podemos ver, la mayora de las convenciones de Code First parecen muy razonables
y pienso que merece la pena cumplirlas para trabajar lo menos posible. Sin embargo,
puede haber situaciones en las que alguna convencin no nos satisfaga (como por
ejemplo el nombre en plural de las tablas) o bien no podamos cumplirla por algn motivo
(imagina por ejemplo que el campo Pedidos.ClienteId tuviera que ser creado en cdigo
con el nombre Pedidos.ClienteAsociadoId).
En estos casos, lo que necesitamos es poder invalidar las convenciones predeterminadas
de Code First e instruirle de forma precisa sobre cmo queremos que se realicen ciertos
mapeos. Es decir, estamos tomando el control y decidiendo por nosotros mismos como
inferir el esquema. Adems de poder eliminar convenciones, lo ms habitual es realizar
ajustes a travs de alguna de estas vas:
DataAnnotations.
Fluent API.
Cuando elegir una u otra es un tema de profundo debate, pero a grandes rasgos:
DataAnnotations es ms sencillo de utilizar que Fluent API.
Con DataAnnotations hay ciertos escenarios que no podemos conseguir.
Con DataAnnotations ensuciamos en cierto modo nuestras clases POCO, tanto a
la vista del desarrollador como a efectos de interoperabilidad.
Con Fluent API tenemos ms control y adems es ms cool (vale, lo he dicho y no
me arrepiento).
Al respecto de los ajustes, la precedencia que aplica Code First es primero Fluent API,
despus DataAnnotations y por ltimo las convenciones predeterminadas.
Dicho esto, para ajustar nuestro modelo veremos ambas opciones y as despus podremos
decidir.
Los ajustes que realizaremos a modo de ejemplo sobre la entidad Pedido sern:
Elegir el nombre Pedidos para la tabla.
Crear el campo PedidoId como no autonumrico (queremos controlar el nmero
de pedido).
La propiedad de clave externa con Clientes tiene que llamarse ClienteAsociadoId en
nuestro modelo pero tiene que continuar llamndose ClienteId en la base de datos (est
claro que este requerimiento es un poco absurdo, pero nos servir para el ejemplo).
Para ajustar el modelo con DataAnnotations tendremos que agregar una referencia al
ensamblado System.ComponentModel.DataAnnotations en nuestro proyecto Models y
decorar la clase Pedido con los siguientes atributos:
[Table("Pedidos")]
public class Pedido
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int PedidoId { get; set; }
public DateTime FechaCreacion { get; set; }
[ForeignKey("Cliente")]
[Column("ClienteId")]
public int ClienteAsociadoId { get; set; }
public Cliente Cliente { get; set; }
public virtual List<LineaPedido> Lineas { get; set; }
}
Si esto mismo lo hiciramos con Fluent API, tendramos que agregar una referencia en
Models para el ensamblado System.Data.Entity y escribir los ajustes en el mtodo
OnModelCreating de nuestra clase de contexto:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Eliminar convencin de pluralizacin de nombres de tablas
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

modelBuilder.Entity<Pedido>().ToTable("Pedidos");
modelBuilder.Entity<Pedido>()
.Property(p => p.PedidoId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
modelBuilder.Entity<Pedido>()
.Property(p => p.ClienteAsociadoId)
.HasColumnName("ClienteId");
modelBuilder.Entity<Pedido>()
.HasRequired(p => p.Cliente)
.WithMany(p => p.Pedidos)
.HasForeignKey(p => p.ClienteAsociadoId);
}
A priori Fluent API parece mucho cdigo para tan poco requerimiento, adems parece algo
complicado pero, lo cierto es que, despus de entender cmo funciona y con algo de
prctica, nos daremos cuenta que no se llama API Fluida por nada, realmente fluir y nos
resultar sencillo realizar ajuste por esta va con poco esfuerzo.
En este punto, algo interesante es saber cmo podramos gestionar estos ajustes para no
ensuciar las clases (si hablamos de DataAnnotations) o no tener un mtodo
OnModelCreating de 1000 lneas (si hablamos de Fluent API).
Para el caso de DataAnnotations podramos utilizar las famosas clases buddy (clase colega)
para separar la clase POCO de los atributos.
[MetadataType(typeof(PedidoMetadata))]
public class Pedido
{
public int PedidoId { get; set; }
public DateTime FechaCreacion { get; set; }
public int ClienteAsociadoId { get; set; }
public Cliente Cliente { get; set; }
public virtual List<LineaPedido> Lineas { get; set; }
}

[Table("Pedidos")]
public class PedidoMetadata
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int PedidoId { get; set; }
[ForeignKey("Cliente")]
[Column("ClienteId")]
public int ClienteAsociadoId { get; set; }
}
En este ejemplo podemos ver:
Se ha indicado la clase buddy con el atributo MetadataType, en la clase Pedido.
En la clase PedidoMetadata (llamada as por convencin pero podra haberse
llamado de cualquier otra forma) hemos indicado los DataAnnotations slo para las
propiedades que queremos ajustar.
Con esto hemos conseguido separar la definicin de nuestra clase de las DataAnnotations
para Code First.
Si queremos lograr esta misma separacin de conceptos a travs de Fluent API, tendremos
que crear una configuracin para nuestra clase Pedido en una nueva clase que herede de
EntityTypeConfiguration y registrarla durante el evento OnModelCreating.
Primero la clase de configuracin:
public class PedidoConfiguration : EntityTypeConfiguration<Pedido>
{
public PedidoConfiguration()
{
ToTable("Pedidos");
Property(p => p.PedidoId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
Property(p => p.ClienteAsociadoId)
.HasColumnName("ClienteId");
HasRequired(p => p.Cliente)
.WithMany(p => p.Pedidos)
.HasForeignKey(p => p.ClienteAsociadoId);
}
}
Despus registrarla en el evento de creacin del modelo:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new PedidoConfiguration());
}
Llegados a este punto, ya sabemos cmo se infiere el modelo y cmo podemos ajustarlo,
as que ha llegado el momento de saber cundo, cmo y dnde se crea la base de datos.
Por defecto, Code First intenta crear la base de datos en el primer acceso a la misma (en
nuestro ejemplo anterior ocurri cuando intentamos agregar un cliente a
TiendaContext.Clientes). Adems la intenta crear en la instancia de SQL Express
(.\SQLEXPRESS) o en su defecto en LocalDb ((localdb)\v11.0), motor de base de datos que
viene incluido con VS 2012. Respecto al nombre de la base de datos elegir el nombre del
contexto plenamente cualificado (Models.TiendaContext). Cmo lgicamente casi nunca
nos servir esta localizacin de base de datos, comencemos por ver cmo elegir donde
crear la base de datos.
La forma ms sencilla de elegir donde crear la base de datos es crear una cadena de
conexin en nuestro fichero Web.config con el nombre del contexto.
<connectionStrings>
<add name="TiendaContext"
providerName="System.Data.SqlClient"
connectionString="Data Source=(local)\sqlexpress;Initial Catalog=Tienda;Integrated
Security=SSPI;MultipleActiveResultSets=True;Application Name=Tienda" />
</connectionStrings>
De la cadena de conexin podemos comentar lo siguiente:
El atributo name puede ser el nombre de la clase de contexto o el nombre
plenamente cualificado de la clase de contexto, es decir, valdra tanto TiendaContext como
Models.TiendaContext.
No olvidar agregar el parmetro MultipleActieResultSets=True (necesario para el
correcto funcionamiento de Entity Framework).
Agregar un nombre de aplicacin si no quieres despus sorpresas con
TransationScope. Puedes ver ms informacin en el post Buscando al culpable de Pedro
Hurtado.
Claramente, para mi esta forma es la preferida porque despus y con lastransformaciones
de ficheros Web.config podemos cambiar la cadena de conexin segn la configuracin
activa (Debug o Release).
Otra forma de especificar donde conectar Code First es a travs del constructor de
DbContext.
public class TiendaContext : DbContext
{
public TiendaContext()
{
}
public TiendaContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{
}
}

Ahora seran vlidas todas estas combinaciones:
// Por defecto, cadena de conexin con el nombre TiendaContext
var context = new TiendaContext();

// Cadena de conexin con el nombre Tienda
// Sino existe crear una base de datos con el nombre Tienda
var context2 = new TiendaContext("Tienda");

// Cadena de conexin con el nombre Tienda
// Sino existe fallar
var context2 = new TiendaContext("Name=Tienda");

// Cadena de conexin explcita
var connectionString =
@"Data Source=(local)\sqlexpress;"+
"Initial Catalog=Tienda;"+
"Integrated Security=SSPI;"+
"MultipleActiveResultSets=True;"+
"Application Name=Tienda";
var context3 = new TiendaContext(connectionString);
Ahora que ya sabemos especificar la localizacin exacta de la base de datos, todava nos
queda pendiente controlar el momento de la creacin de la misma. Por defecto, Code First
intentar crearla la primera vez que la necesite segn la API de DbContext. En cualquier
caso siempre podemos explcitamente decidir cundo crearla con el mtodo Initialize:
using (var context = new TiendaContext())
{
context.Database.Initialize(false);
}
El objeto Database, adems de Initialize nos suministra algunos otros mtodos muy tiles:
CreateIfNotExists
CompatibleWithModel
Delete
Create
ExecuteSqlCommand
Especialmente interesante es el mtodo CompatibleWithModel. Este mtodo nos informa
si el modelo actual es o no compatible con el esquema de la base de datos. Que devuelva
true o false depende del valor asignado al parmetro throwIfNoMetada y de la existencia o
no de la tabla __MigrationHistory. Esta tabla es creada por Code First cuando se crea
automticamente la base de datos o cuando se habilita Code First Migrations (ver el
siguiente post que indica como crear esta tabla para una base de datos en la que no
existe http://thedatafarm.com/blog/data-access/using-ef-migrations-with-an-existing-
database/). Si quieres saber que guarda exactamente esta tabla, te recomienda la lectura
de los siguientes posts Desmitificando Code Fisrt(1/2) y Desmitificando Code First(2/2) V
4.3.
CompatibleWithModel tiene las siguientes combinaciones:
throwIfNoMetada Existe
__MigrationHistory
Resultado
false No true, no hay forma de comparar el modelo
con el esquema y entonces se asume que
es compatible.
false S true o false en funcin de si el modelo es o
no compatible con el esquema.
true No Se lanzar una excepcin porque no se han
podido comparar modelo y esquema.
true S true o false en funcin de si el modelo es o
no compatible con el esquema.

Sea como fuere, la pregunta que surge a continuacin es Qu pasa si el modelo cambia?
Es decir, si el esquema de base de datos no es compatible con el modelo Qu ocurrir?
Pues la respuesta a estas preguntas son las distintas estrategias de inicializacin de Code
First.
Por defecto, Code First incorpora las siguientes estrategias de inicializacin:
CreateDatabaseIfNotExists
DropCreateDatabaseIfModelChanges
DropCreateDatabaseAlways
Con CreateDatabaseIfNotExists, si la base de datos no existe se crea, y si existe y el modelo
no es compatible se lanza un error (aunque puedes no tener __MigrationHistory y entonces
devolver siempre que la base de datos y el modelo son compatibles).
Con DropCreateDatabaseIfModelChanges la diferencia est en que si el modelo no es
compatible con la base de datos, la misma se eliminara y se volver a crear de nuevo (aqu
es obligado tener __MigrationHistory porque si no lanzar una excepcin).
Por ltimo, con DropCreateDatabaseAlways siempre se elimina y se crea la base de datos,
sin importar si es o no compatible con el modelo (lgicamente aqu tampoco importa si
tienes o no __MigrationHistory, simplemente no se consulta).
Por defecto, la estrategia activa es CreateDatabaseIfNotExists (menos mal!). Esta estrategia
es la nica que nos garantiza que nuestra aplicacin en produccin no eliminar vilmente
nuestra base de datos en cada ejecucin o si el modelo cambia. Por el contrario, si el
modelo no es compatible la inicializacin fallar y nuestra aplicacin quedar inaccesible.
Como resolver este escenario lo veremos ms adelante.
Respecto a la estrategia DropCreateDatabaseIfModelChanges, es normalmente utilizada en
el entorno de desarrollo donde los datos son prescindibles y queremos agilidad a la hora
de programar nuestra aplicacin sin importar si ha habido o no cambios en el modelo.
Por ltimo, con DropCreateDatabaseAlways estamos yendo un paso ms all y podra
resultar til si realizamos pruebas unitarias que tengan acceso a la base de datos y
queremos asegurarnos de disponer siempre de una base de datos vaca en cada ejecucin.
El cmo establecer un inicializador es muy sencillo:
Database.SetInitializer(new CreateDatabaseIfNotExists<TiendaContext>());
using (var context = new TiendaContext())
{
// hacer algo...
}
Llegado el caso tambin podemos no establecer ningn inicializador. Esto puede
resultarnos til si estamos trabajando contra una base de datos existente y no queremos
que Code First lleve a cabo ninguna estrategia de inicializacin.
Database.SetInitializer<TiendaContext>(null);
Como era de suponer, podemos crear nuestra propia estrategia de inicializacin
personalizada para Code First. Para nuestro ejemplo, crearemos un inicializador con las
siguientes caractersticas:
Si el modelo cambia, se recrear la base de datos.
Tambin se recrear la base de datos si se encuentra un setting DropIsRequired
con el valor true en nuestro Web.config.
Por otro lado, tambin debera ser posible ejecutar sentencias sql personalizadas
despus de haber creado la base de datos.
El cdigo de inicializador, a continuacin:
public class DropCreateIfModelChangesOrDropIsRequired<TContext>
: IDatabaseInitializer<TContext>
where TContext : DbContext
{
public IEnumerable<string> Sentences { get; set; }

public void InitializeDatabase(TContext context)
{
var created = false;
if (!context.Database.Exists())
{
context.Database.Create();
created = true;
}
else
{
var dropIsRequired = false;
if (ConfigurationManager.AppSettings["DropIsRequired"] != null)
{
var result = false;
if (bool.TryParse(ConfigurationManager.AppSettings["DropIsRequired"],out res
ult))
{
dropIsRequired = result;
}
}
if (dropIsRequired || !context.Database.CompatibleWithModel(false))
{
context.Database.Delete();
context.Database.Create();
created = true;
}
}
if (created && Sentences != null)
{
foreach (var sentence in Sentences)
{
context.Database.ExecuteSqlCommand(sentence);
}
}
}
}

Y el cdigo necesario para la inicializacin:
var initializer = new DropCreateIfModelChangesOrDropIsRequired<TiendaContext>();
initializer.Sentences = new List<string>()
{
"ALTER DATABASE CURRENT SET RECOVERY SIMPLE"
};
Database.SetInitializer(initializer);
Otro punto interesante es saber cmo podemos agregar datos iniciales a nuestra base de
datos. En los inicializadores que trae Code First de serie, podemos sobreescribir el mtodo
virtual Seed para centralizar la inicializacin de datos despus de que la estrategia de
inicializacin haya concluido. Por ejemplo, para CreateDatabaseIfNotExists
public class CreateDatabaseIfNotExistsWithSeedData :CreateDatabaseIfNotExists<Tienda
Context>
{
protected override void Seed(TiendaContext context)
{
context.Clientes.Add(new Cliente() { Nombre = "Sergio" });
}
}
Si hemos optado por crear un inicializador personalizado, no podremos sobreescribir este
mtodo (bsicamente porque no existe) pero nada impide que lo implementemos y lo
utilicemos igualmente. Por ejemplo, a nuestra anterior clase
DropCreateIfModelChangesOrDropIsRequired le agregaremos el mtodo Seed y una
llamada a context.SaveChanges si la base de datos fue creada correctamente:
public class DropCreateIfModelChangesOrDropIsRequired<TContext>
: IDatabaseInitializer<TContext>
where TContext : DbContext
{
public IEnumerable<string> Sentences { get; set; }

public void InitializeDatabase(TContext context)
{
var created = false;
if (!context.Database.Exists())
{
context.Database.Create();
created = true;
}
else
{
var dropIsRequired = false;
if (ConfigurationManager.AppSettings["DropIsRequired"] != null)
{
var result = false;
if (bool.TryParse(ConfigurationManager.AppSettings["DropIsRequired"], outresult
))
{
dropIsRequired = result;
}
}
if (dropIsRequired || !context.Database.CompatibleWithModel(true))
{
context.Database.Delete();
context.Database.Create();
created = true;
}
}
if (created)
{
if (Sentences != null)
{
foreach (var sentence in Sentences)
{
context.Database.ExecuteSqlCommand(sentence);
}
}
Seed(context);
context.SaveChanges();

}
}

protected virtual void Seed(TContext context)
{
}
}
Ahora crearemos una nueva clase que herede de
DropCreateIfModelChangesOrDropIsRequired y que fijar el parmetro genrico al tipo
TiendaContext. Adems tambin aprovecharemos para incluir en el constructor por
defecto nuestras sentencias personalizadas SQL:
public class DropCreateIfModelChangesOrDropIsRequiredTiendaContext :DropCreateIfM
odelChangesOrDropIsRequired<TiendaContext>
{
protected override void Seed(TiendaContext context)
{
context.Clientes.Add(new Cliente() { Nombre = "Cliente por defecto" });
}
public DropCreateIfModelChangesOrDropIsRequiredTiendaContext()
{
Sentences = new List<string>() {
"ALTER DATABASE CURRENT SET RECOVERY SIMPLE" };
}
}
Ahora nuestra inicializacin pasara a ser la siguiente:
var initializer = new DropCreateIfModelChangesOrDropIsRequiredTiendaContext();
Database.SetInitializer(initializer);
Con esto habramos logrado crear una nueva estrategia de inicializacin personalizada de
Code First y adems implementar la inicializacin de datos y sentencias personalizadas de
SQL. Bien! Ha costado pero hemos llegado.
Ya para terminar (que te prometo que yo tambin quiero terminar ya este post), mi ltima
pregunta fue saber cmo poda cambiar de estrategia de inicializacin segn la
configuracin activa (algo parecido a lo que hicimos con las cadenas de conexin segn
estbamos en Debug o Release). Es decir, imagina que en Debug queremos el inicializador
DropCreateIfModelChangesOrDropIsRequiredTiendaContext y en Release
queremos CreateDatabaseIfNotExists (ms que nada por no quedarnos con cara de bobos
cuando publiquemos, haya un cambio en el modelo y nuestra base de datos desaparezca
no quiero ni imaginarlo!). Pues bien, ya existe una solucin muy elegante y que viene de
serie con Code First y es la de elegir la estrategia de inicializacin a travs del fichero de
configuracin (de nuevo nuestro recurrente fichero Web.config).
Para seleccionar la estrategia adecuada a travs del Web.config tendremos que agregar un
setting con la clave DatabaseInitializerForType. Esto y junto a la transformacin de ficheros
de configuracin, nos permitirn cambiar de estrategia sin tocar ni una sola lnea de
cdigo.
Lo primero sera no establecer a travs de cdigo ningn inicializador (esto era obvio, pero
hoy estoy muy verbose y no quiero dar nada por sentado) en cualquier caso parece que
lo establecido en el Web.config prevalece sobre cualquier inicializador establecido por
cdigo.
Despus y para nuestra configuracin de Debug (fichero Web.config) escribiramos lo
siguiente donde value ser el nombre nuestra clase o tambin el valor Disabled si
queremos simplemente deshabilitar el inicializador (igual que hacamos antes por cdigo
con null).
<appSettings>
<add key="DatabaseInitializerForType Models.TiendaContext, Models"
value="Models.DropCreateIfModelChangesOrDropIsRequiredTiendaContext,
Models" />
</appSettings>
Por ltimo, en el fichero Web.Release.config (el fichero de trasformacin para Release)
haramos el siguiente cambio:
<appSettings>
<add key="DatabaseInitializerForType Models.TiendaContext, Models"
value="System.Data.Entity.CreateDatabaseIfNotExists`1 [[Models.TiendaContext,
Models]], EntityFramework"
xdt:Transform="SetAttributes"
xdt:Locator="Match(key)"/>
</appSettings>
Como apunte final, lo nico que nos ha quedado por ver en relacin al planteamiento
inicial del post es cmo gestionar en un entorno de produccin los cambios del modelo. Si
piensas que con poner a nivel manualmente la base de datos para que coincida con el
modelo es suficiente pues ser o no cierto en funcin de cmo se llame al mtodo
CompatibleWithModel. Recuerda que comparar el modelo con el esquema se realiza a
travs del contenido de la tabla __MigrationHistory, no leyendo la estructura de tablas de
la base de datos. Es por ello que evolucionar la base de datos para que coincida con el
modelo tiene que realizarse a travs de Code First Migrations pero eso ser en otro post.
Un saludo!

Das könnte Ihnen auch gefallen