Entity Framework Tutorial
Entity Framework Tutorial
Entity Framework Tutorial
DE UN ENTITY MODEL
Entity Framework es la evolución natural de ADO.NET hacia el tratamiento de los datos
almacenados en una base de datos relacional a través del paradigma objetual. Por lo
tanto, se trata, al igual que LINQ to SQL, de un mapper objeto-relacional que es capaz
de abstraer las tablas y columnas de una base de datos y tratarlas como objetos
u entidades y relaciones.
Una entidad, por tanto, no es más que un objeto persistente, que cumple las
condiciones de unicidad e identidad.
Lo primero que deberemos hacer una vez que hayamos creado nuestro proyecto (por
ejemplo, de consola) será añadir un nuevo Entity Data Model. Para ello haremos click
derecho sobre nuestro proyecto y seleccionaremos la opción Add > New Item…
Una vez añadido, se nos preguntará el modo de generación del modelo. Queremos
mapear automáticamente nuestra base de datos, por lo que seleccionaremos la
primera opción (Generate from database).
A continuación rellenaremos los datos de conexión para acceder a la base de datos.
El siguiente paso será seleccionar los elementos que queremos importar. Elegimos
aquellos que nos interese modelar, le asignamos un nombre al namespace y pulsamos
el botón Finish.
La opción Pluralize or singularize generated object names teóricamente se encarga de
asignar un nombre en plural a las colecciones y hacerlo singular en las referencias. Sin
embargo, según mi experiencia previa con la pluralización automática, es más
aconsejable realizarlo a mano (salvo que no tengamos tiempo y se trata de una base
de datos enorme).
El resultado será el siguiente: un modelo con sus relativas relaciones que mantiene
gran similitud con el modelo relacional.
Si abrimos el documento XML perteneciente al fichero .edmx, vemos que por un lado
generará información sobre la parte relacional y por otro, sobre la parte objetual,
realizando el mapeo objeto-relacional.
9
<!-- CSDL content -->
10
<EntityType Name="Cliente">
11
<Key>
12 <PropertyRef Name="IdCliente" />
13 </Key>
20
3 {
6
public int IdCliente { get; set; }
7
public string Nombre { get; set; }
8
public System.DateTime FechaNacimiento { get; set; }
9
12
13
Investigando el modelo
En este explorador se nos mostrará que por un lado tenemos el modelo objetual y por
otro, el relacional.
Siempre podemos añadir nuevos objetos desde la base de datos. Por ejemplo, si
queremos añadir una vista y varios procedimientos almacenados, haremos click
derecho sobre la parte relacional y seleccionaremos la opción Update Model from
Database…
En caso de que no se cumpla, recordemos que toda entidad debe tener una clave de
entidad. Sin ella, Entity Framework no será efectivo. El explorador del modelo también
nos permitirá realizar esta operación.
Renombramos a plural también los elementos del Contexto, para dejar claro que se
trata de colecciones
Si queremos accede a los detalles del mapeado, también es posible hacer click derecho
sobre la entidad a consultar y pulsar click derecho sobre ella, seleccionando a
continuación “Table Mapping”
Esto mostrará la información del mapeo, comparando columna y propiedad. Desde
esta ventana podemos realizar operaciones como cambiar el mapeo o añadir ciertas
condiciones.
Accediendo al modelo
1 // Instanciamos el contexto
select cliente;
6
7
// Recorremos los clientes
8
foreach (Cliente cliente in clientes)
9
{
10
Console.WriteLine(string.Format("ID: {0}\tNOMBRE: {1}\tAÑO NAC: {2}",
11 cliente.IdCliente, cliente.Nombre, cliente.FechaNacimiento.Year));
12
15 {
pedido.IdPedido, pedido.FechaPedido));
17
18
// Recorremos las líneas de pedido
19
foreach (LineaPedido linea in pedido.LineasPedido)
20
{
21
Console.WriteLine(string.Format("\t\tPRODUCTO: {0}\tCANTIDAD: {1}\tTO
22 linea.Producto.Descripcion, linea.Cantidad,
(linea.Producto.Precio*linea.Cantidad)));
23
}
24
}
25
Console.WriteLine(" -----------------------------------------\n");
26
}
27
28
Hecho esto, vemos que el acceso es correcto y que nuestro modelo funciona
correctamente.
ENTITY FRAMEWORK (II):
OBJECTCONTEXT Y ENTITY SQL
Una de las mayores ventajas de Entity Framework es que es una tecnología agnóstica
respecto a la base de datos que tiene por debajo. Con ADO.NET era necesario utilizar
clases específicas para cada base de datos (SqlCommand para SQLServer,
OracleCommand para Oracle, etc.). Sin embargo, Entity Framework no se casa con
nadie: hace uso de elementos genéricos (EntityConnection, EntityCommand, etc.) que
genera instrucciones en un lenguaje intermedio denominado Entity SQL, que es muy
similar al lenguaje SQL estándar.
En última instancia, el ADO.NET de toda la vida estará trabajando por debajo, pero
Entity Framework establece una capa de abstracción superior que evita la necesidad de
atarnos a una fuente de datos concreta. No obstante, es posible descender un poco en
el nivel de abstracción y, en lugar de hacer uso de LINQ to Entities tal y como hicimos
en el artículo anterior, lanzar directamente consultas sobre el ObjectContext usando
para ello Entity SQL.
Dependiendo de la versión de Entity Framework que se esté usando, esto se realizará
de un modo u otro, ya que la versión 4.1 introdujo como novedad la utilización
del DbContext por defecto, envolviendo el ObjectContext dentro de él.
// Instanciamos el DbContext
1 var dbContext = new testdbEntities();
2
3 // Extraemos el ObjectContext del DbContext (a partir de Entity
4 Framework 4.1)
var objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
5
6 // Ejecutamos una sentencia de Entity SQL que recupere todos los
7 clientes
8 string sqlQuery = "SELECT VALUE c FROM Clientes AS c";
9 var clientes = new ObjectQuery<Cliente>(sqlQuery, objectContext);
10
11 foreach (Cliente cliente in clientes)
{
12 Console.WriteLine(string.Format("ID: {0}\tNOMBRE: {1}\tAÑO NAC:
13 {2}",
14 cliente.IdCliente, cliente.Nombre,
15 cliente.FechaNacimiento.Year));
}
Pese a que LINQ to Entities es bastante parecido a LINQ to SQL, a la hora de trabajar
con ambas tecnologías es necesario conocer las diferencias más importantes a nivel
práctico (dejaremos los fundamentos teóricos a un lado). Ambas tecnologías tienden a
la convergencia, puesto que Microsoft está intentando coger lo mejor de cada una de
ellas y adaptarlo a ambos mundos. Sin embargo, siguen existiendo diferencias
importantes, especialmente en las primeras versiones de LINQ to Entities.
Las primeras versiones de Entity Framework contenían ciertas carencias que, con
posteriores actualizaciones, han sido solventadas y corregidas. Una de las carencias
más importantes se correspondería con la imposibilidad de las versiones anteriores a
4.1 de realizar una carga automática de los elementos referenciados por un objeto. Es
decir, si tenemos el siguiente código:
1 // Instanciamos el contexto
3
// Lanzamos una consulta
4
var clientes = from cliente in contexto.Clientes
5
select cliente;
6
7
// Recorremos los clientes
8
foreach (Cliente cliente in clientes)
9 {
10 Console.WriteLine(string.Format("ID: {0}\tNOMBRE: {1}\tAÑO NAC: {2}",
11 cliente.IdCliente, cliente.Nombre, cliente.FechaNacimiento.Year));
12
18
// Recorremos las líneas de pedido
19 foreach (LineaPedido linea in pedido.LineasPedido)
20 {
22 linea.Producto.Descripcion, linea.Cantidad,
(linea.Producto.Precio*linea.Cantidad)));
23
}
24
}
25 Console.WriteLine(" -----------------------------------------\n");
26 }
27
28
Las versiones anteriores a la 4.1 eran incapaces de iterar sobre los pedidos de un
cliente, ya que era necesario realizar una carga implícita de estos elementos antes de
utilizarlos. Esto se hacía mediante el método Load(), que debía ser invocado antes de
hacer uso del listado:
7 cliente.Pedidos.Load();
8
// Recorremos los pedidos
9
10 foreach (Pedido pedido in cliente.Pedidos)
11 {
// ...
Insert
1 // Instanciamos el DbContext
11 };
12
14 {
18
Cliente guillermoSanabria = new Cliente()
19
{
20 Nombre = "Guillermo Sanabria San Juan",
21 FechaNacimiento = new DateTime(1983, 11, 1)
22 };
23
25 dbContext.Clientes.Add(katiaRamos);
26
// MÉTODO 2: AddObject (genérico)
27
objectContext.AddObject("Clientes", arturoSaavedra);
28
29
// MÉTODO 3: AddClientes (específico del contexto) – Versiones anteriores a
30 la 4.1
31 objectContext.AddClientes(guillermoSanabria);
32
dbContext.SaveChanges();
34
35
// Comprobamos si todo es correcto
36
foreach (Cliente cliente in dbContext.Clientes)
37
{
38
Console.WriteLine(string.Format("ID: {0}\tNOMBRE: {1}\tAÑO NAC: {2}",
39 cliente.IdCliente, cliente.Nombre, cliente.FechaNacimiento.Year));
40 }
41
42
Hecho esto, comprobamos que todo sea correcto:
1 // Realizamos la consulta
3
// Creamos los registros asociados:
4
Pedido p = new Pedido()
5
{
6
Cliente = cliente,
7
FechaPedido = DateTime.Now,
8
};
9
13 Cantidad = 7
};
14
15
// Insertamos registros la línea de pedido, que ya tiene asociado el pedido.
16
// LINQ to Entities se encargará de realizar la inserción previa del pedido por
17 nosotros.
18 dbContext.LineasPedido.Add(lp);
19
20 // Guardamos los cambios
21 dbContext.SaveChanges();
22
Update
1
// Instanciamos el DbContext
2
var dbContext = new testdbEntities();
3
4 // Realizamos la consulta
5 var clientes = dbContext.Clientes.Where(cliente =>
cliente.Nombre.StartsWith("Katia"));
6
7
// Modificamos los objetos que consideremos oportunos
8
foreach (var cliente in clientes)
9
cliente.Nombre = cliente.Nombre.Replace("Katia", "Katerina");
10
16 {
El resultado:
Delete
1 // Instanciamos el DbContext
6
// Realizamos la consulta
7
var clienteEliminar = dbContext.Clientes.Where(cliente => cliente.IdCliente ==
8 8).First();
10 // Eliminamos el cliente
13
// Guardamos los cambios
14
dbContext.SaveChanges();
15
16
// Comprobamos si todo es correcto
17
foreach (Cliente cliente in dbContext.Clientes)
18 {
19 Console.WriteLine(string.Format("ID: {0}\tNOMBRE: {1}\tAÑO NAC: {2}",
20 cliente.IdCliente, cliente.Nombre, cliente.FechaNacimiento.Year));
21 }
22
Sin embargo, ¿qué ocurrirá si eliminamos un cliente que tenga asociado algún pedido,
como por ejemplo el cliente 9? Veamos:
Por desgracia, en este caso deberemos limitarnos a decir “así no lo hagas“. Entity
Framework no es capaz de manejar bien los borrados en cascada, por lo que
deberemos delegar las operaciones de borrado a la base de datos, bien a través de una
restricción de borrado en cascada, bien mediante la creación de un procedimiento
almacenado que se encargue de realizar este proceso (que podremos invocar también
desde nuestro DbContext.
Un ejemplo para esto sería crear dos procedimientos almacenados: uno que borre los
pedidos y sus líneas de pedido y otro que, además de borrar pedidos y líneas, borre
también los clientes. El primero de ellos será algo como lo siguiente:
1
create procedure sp_Pedido_DeleteCascade
2 @IdPedido int
3 as
4 begin
1
create procedure sp_Cliente_DeleteCascade
2
@IdCliente int
3 as
4 begin
12
declare @IdPedido int;
13
14
15 -- Borramos todos los pedidos junto a sus lineas de pedido, recorriendo
los
16
-- pedidos devueltos por el cursor.
17
open cur;
18 fetch next from cur into @IdPedido
19 while @@FETCH_STATUS = 0
20 begin
25
delete from Cliente where IdCliente = @IdCliente;
26
end;
// Instanciamos el DbContext
1
var dbContext = new testdbEntities();
2
3
// Realizamos la consulta
4
var clienteEliminar = dbContext.Clientes.Where(cliente => cliente.IdCliente ==
5 9).First();
6
// Ejecutamos el procedimiento almacenado pasando el ID (9) como parámetro.
7
dbContext.sp_Cliente_DeleteCascade(clienteEliminar.IdCliente);
8
9
// Guardamos los cambios
10
dbContext.SaveChanges();
11
{
14
Console.WriteLine(string.Format("ID: {0}\tNOMBRE: {1}\tAÑO NAC: {2}",
15
cliente.IdCliente, cliente.Nombre, cliente.FechaNacimiento.Year));
16
}
17
18
Es importante saber que Entity Framework tiene una política de todo o nada para hacer
uso de esta característica, es decir: si se decide utilizar un procedimiento almacenado
para realizar las eliminaciones, será obligatorio definir también los métodos de
inserción y actualización.
1
2 create procedure sp_Cliente_Insert
@Nombre nvarchar(256),
3
@FechaNacimiento date
4 as
5 begin
6 set nocount on;
7
8 -- Realizamos la inserción
9 insert into Cliente(Nombre, FechaNacimiento)
values(@Nombre, @FechaNacimiento);
10
11 -- Devolvemos el nuevo id insertado
12 select SCOPE_IDENTITY() as id;
13 end;
14
El procedimiento de actualización será similar: contará con todos los valores del
registro y realizará la comparación por el ID, tal y como se muestra a continuación:
Tras rellenar los tres elementos, se nos debería mostrar algo como lo siguiente,
realizando el mapeo entre los parámetros del procedimiento y las propiedades de
nuestros objetos:
Sólo nos queda un detalle: en la inserción queremos que el campo IdCliente se rellene
automáticamente en nuestro objeto tras la inserción. ¿Cómo lograr eso? Si recordamos,
el procedimiento ejecutaba la siguiente sentencia al finalizar la inserción:
// Instanciamos el DbContext
1 var dbContext = new testdbEntities();
2
3 // Realizamos la consulta
4 var clientes = dbContext.Clientes.Where(cliente =>
5 cliente.Nombre.StartsWith("Kat"));
6
// Modificamos los objetos que consideremos oportunos
7 foreach (var cliente in clientes)
8 cliente.Nombre = cliente.Nombre.Replace("Katia", "Katerina");
9
10 // Guardamos los cambios
dbContext.SaveChanges();
11
12 // Comprobamos si todo es correcto
13 foreach (Cliente cliente in dbContext.Clientes)
14 {
15 Console.WriteLine(string.Format("ID: {0}\tNOMBRE: {1}\tAÑO NAC: {2}",
cliente.IdCliente, cliente.Nombre, cliente.FechaNacimiento.Year));
16 }
17
18
19
Una vez que tenemos claro que un DbContext es el encargado de velar por la
integridad en el esquema objeto-relacional, ¿qué ocurriría si manejamos objetos que,
por alguna razón, escapan a su control? Nos referimos, por ejemplo, al envío de los
datos de un cliente a través de un formulario web y ponernos a la espera de que el
usuario los modifique en otra página distinta. Este escenario implicaría, con toda
seguridad, que el DbContext en el que se recuperó el objeto sea distinto al DbContext
que se encargará de modificarlo.
Volver a recuperar el objeto completo, copiar todos los datos del objeto
enviado por el usuario al objeto que acabamos de recuperar y guardar los
cambios.
Enlazar directamente el objeto al DbContext y decirle que actualice los
cambios.
Para la primera opción no es necesaria mucha explicación, puesto que se trata de una
modificación normal y corriente. Para la segunda opción habrá que trabajar un poco
más, pero se considera una opción más correcta. Además, el proceso dependerá de si
estamos haciendo uso de un ObjectContext (versión anterior a 4.1) o un DbContext
(versión posterior a ésta).
3 // Instanciamos el DbContext
{
5
// Recuperamos el cliente anterior
6
datosAntiguos = objectContext.Clientes.Where(cliente => cliente.IdCliente ==
7 3).First();
8 }
{
2
// Creamos un nuevo cliente con el MISMO ID que el anterior y
3
// cambiamos algunos datos
4
Cliente datosNuevos = new Cliente()
5
{
6 IdCliente = datosAntiguos.IdCliente,
7 Nombre = "Ana Maria Lopez Diaz",
8 FechaNacimiento = new DateTime(1947, 1, 15)
9 };
10
17 objectContext.SaveChanges();
}
18
19
El método Attach indica a nuestro context que “empiece a preocuparse” por el destino
del objeto que le hemos pasado como parámetro. Por lo tanto, cualquier cambio
realizado sobre este objeto será ahora repercutido sobre la fuente de datos al invocar
el método SaveChanges().
A partir de esta versión, este proceso se simplifica bastante. Bastará con realizar lo
siguiente:
5 {
17 dbContext.Clientes.Attach(datosNuevos);
18
// Cambiamos el estado a "Modificado" para que Entity Framework sepa que
19
// deben actualizarse los datos
20
dbContext.Entry(datosNuevos).State = System.Data.EntityState.Modified;
21
dbContext.SaveChanges();
22
}
23
24
Si nos fijamos bien, en la versión 4.1 nos hemos limitado a enlazar el nuevo objeto al
listado en lugar de al contexto, y hemos cambiado manualmente el estado de la
entidad para forzar su actualización. Al ejecutar SaveChanges(), el contexto
comprobará los estados de todas sus entidades y aplicará la operación
correspondiente a cada una. En nuestro caso, actualizando sus datos.
El método opuesto a Attach() es Detach() y, como su propio nombre indica, hace que el
contexto deje de trazar los cambios realizados sobre ese objeto, ignorándolo cuando
se ejecute un SaveChanges().
Sabiendo esto, podemos jugar un poco con las propiedades y realizar operaciones de
forma “alternativa”, tal y como hemos hecho con la actualización. Así, una forma de
insertar un nuevo cliente podría ser la siguiente:
1
Cliente guillermoSanabria = new Cliente()
2
{
3 Nombre = "Guillermo Sanabria San Juan",
4 FechaNacimiento = new DateTime(1983, 11, 1)
5 };
dbContext.Entry(guillermoSanabria).State = EntityState.Added;
7
8
// Guardamos los cambios
9
dbContext.SaveChanges();
10
ENTITY FRAMEWORK
(VI): WEBSERVICES
Hasta ahora hemos visto el funcionamiento de LINQ y Entity Framework. La siguiente
serie de artículos estarán orientados hacia los servicios web, por lo que haremos una
pequeña introducción aplicando los conocimientos que hemos obtenido hasta el
momento.
Para crear un nuevo servicio web, crearemos un nuevo proyecto web de tipo ASP.NET
Empty Web Application y le asociaremos un nuevo nombre. Los servicios web reciben
ese nombre porque operan sobre el protocolo HTTP, así que este será nuestro punto
de partida.
A continuación volveremos la vista atrás y, tal y como vimos en los artículos dedicados
a Entity Framework, añadiremos un nuevo modelo de datos a nuestro proyecto web.
Una vez añadido, el asistente nos preguntará el origen de los datos. Le responderemos
que nuestro deseo es generarlo a partir de la base de datos y pulsaremos Next >
Tal y como hicimos en ocasiones anteriores, seleccionaremos los objetos a modelar:
tablas, vistas, procedimientos almacenados…
Si todo ha ido bien, nuestro modelo debería generarse con los elementos
seleccionados.
A partir del Framework 3.5, Microsoft encapsuló la gestión de los servicios web en un
conjunto de bibliotecas denominadas Windows Communication Foundation. Nuestra
intención es crear un servicio de datos (veremos los tipos de webservices en
posteriores artículos), por lo que haremos click derecho sobre nuestro proyecto web y
añadiremos un nuevo elemento Web > WCF Data Service.
Al finalizar la generación, veremos que se han creado dos elementos: un fichero con
extensión .svc, que representa el servicio web en sí y un fichero .svc.cs que contendrá
el code behind o comportamiento, es decir, el código fuente que definirá las acciones
que realizará.
Si aún no hemos tocado nada, el cuerpo del fichero c# será similar al siguiente:
1 namespace EntityFrameworkService
{
2
public class GestionPedidosDataService : DataService< /* TODO: put your data s
3 */ >
4 {
{
7
// TODO: set rules to indicate which entity sets and service operatio
8 updatable, etc.
9 // Examples:
10 // config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRe
11 // config.SetServiceOperationAccessRule("MyServiceOperation", Service
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVe
12
}
13
}
14
}
15
Como podemos ver, existe una sección TODO dentro de los símbolos “<” y “>” que nos
indica que introduzcamos la clase de nuestra fuente de datos. Ésta no será otra que la
clase generada en el paso anterior y que dio lugar a nuestro Entity Data Model, es
decir, el DbContext. Por lo tanto, añadimos el nombre de la clase para que el servicio
sepa a qué conjunto de datos podrá tener acceso.
Configuración y permisos
Si, por el contrario (y que será con toda seguridad el escenario más común) deseamos
asignar más de un permiso a una entidad, los permisos se concatenarán con el
símbolo OR binario “|”. Por ejemplo, la siguiente configuración asigna permisos de
lectura a todas las entidades, y, además, permisos de inserción (WriteAppend) a las
entidades Pedido y LineaPedido:
4 {
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersio
5
6
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
7
config.SetEntitySetAccessRule("Pedido", EntitySetRights.AllRead |
8 EntitySetRights.WriteAppend);
9 config.SetEntitySetAccessRule("LineaPedido", EntitySetRights.AllRead |
EntitySetRights.WriteAppend);
10
}
11
}
12
Esto abrirá una ventana que mostrará un fichero XML con un aspecto parecido al
siguiente:
Como podemos ver, además de la cabecera, nos encontramos con un nodo service que
posee un nodo workspace que contiene un conjunto de nodos collection. Estos
elementos, como podremos adivinar nada más verlos, se corresponden a los
elementos que nuestro DbContext se encarga de mapear.
Uno de los nodos tiene un atributo denominado href cuyo valor es Cliente. Probemos,
por lo tanto, a navegar a esta ruta relativa indicando la siguiente dirección en nuestro
navegador (cambiando el puerto por el que nos asigne el navegador).
http://localhost:6040/GestionPedidosDataService.svc/Cliente
Es posible que realizar esta operación provoque que se muestre un mensaje como el
siguiente:
Probemos algo más. Sabemos que Entity Framework gestiona de forma interna una
clave primaria para cada entidad. También sabemos que el modelo objetual que
expone Entity Frameworkintegra colecciones de objetos con los que el objeto actual se
encuentra relacionado. Por lo tanto, probemos a comprobar los pedidos asociados al
cliente cuya clave es “3”. Basta con algo como lo siguiente:
http://localhost:6040/GestionPedidosDataService.svc/Cliente(3)/Pedidos
Consultas REST
En cuanto a los tipos de consultas que podemos realizar mediante REST, aquí se
muestran unos pocos ejemplos:
$value: recupera el valor solicitado, sin metadatos asociados (sin XML, en este
caso).
/Cliente(3)/Nombre/$value
$count: devuelve el total de registros de una entidad.
/Cliente/$count
$skip=n: ignora los primeros n elementos. En conjunción con top, sirve para
realizar paginaciones.
/Cliente?$top=1&$skip=1
Creando el cliente
Lo siguiente será añadir una referencia a nuestro servicio. WCF proporciona un servicio
de descubrimiento que, a partir de la dirección de un servicio web, extrae toda la
información disponible asociada a éste. Haremos click derecho sobre nuestro proyecto
cliente y seleccionaremos la opción Add Service Reference…
En la caja de dirección, insertaremos la URI del servicio web, incluyendo la extensión
svc. Una vez hecho esto, pulsaremos en Go, dejando que Visual Studio descubra qué
es lo que se encuentra al otro lado. Finalmente, le daremos un nombre al namespace,
que servirá para identificar los objetos que se encuentran al otro lado.
Con algo tan sencillo como esto habremos configurado nuestra aplicación para
comunicarse con el servicio web. Lo siguiente que haremos será crear una referencia
al DbContext remoto, para lo cual le tendremos que pasar la URI del servicio web.
Realizar una consulta será similar a lo que hacemos en local: realizamos una
consulta LINQ to Entities buscando el objeto a recuperar, teniendo en cuenta que
nuestro DbContext es limitado. Es importante darse cuenta de que no disponemos de
todas las operaciones que podemos realizar sobre un contexto local: nos tendremos
que limitar a operaciones más simples como condiciones, ordenaciones y
paginaciones. Deberemos olvidarnos de joins, funciones de agregación o similares.
Eso no significa que no podamos realizar esas operaciones, sino que tendrán un mayor
coste. Siempre es posible obtener la totalidad de registros de una tabla, transformarlos
en una lista y realizar las operaciones complejas en entorno local. Sin embargo, si este
tipo de operaciones son necesarias, será aconsejable hacer uso de otros recursos
como procedimientos almacenados para que nuestro rendimiento y escalabilidad no se
vean comprometidos.
Nuestro primer paso será, por lo tanto, crear una consulta y generar cuatro nuevos
objetos: un cliente, un pedido y dos líneas de pedido asociadas a dos productos
distintos que, previamente, existirán en la base de datos. Por ejemplo, una línea de
pedido contendrá tres lapiceros y la otra, siete bolígrafos.
5
6 // Creamos un nuevo cliente
{
8
Nombre = "Pedro Gonzalez Arnau",
9
FechaNacimiento = new DateTime(1988, 2, 2)
10
};
11
12
// Creamos un nuevo pedido y dos nuevas lineas de pedido
13 Pedido pedido = new Pedido()
14 {
15 FechaPedido = DateTime.Now
16 };
17
{
19
IdProducto = lapicero.IdProducto,
20
Cantidad = 3
21
};
22
23
LineaPedido lineaPedidoBoligrafos = new LineaPedido()
24 {
25 IdProducto = boligrafo.IdProducto,
26 Cantidad = 7
27 };
28
Inserción
2 dbContext.AddToCliente(nuevoCliente);
6
// Añadimos las relaciones entre pedido y lineas de pedido al contexto
7
dbContext.AddRelatedObject(pedido, "LineasPedido",
8 lineaPedidoBoligrafos);
El siguiente paso será invocar el método SaveChanges para comprometer los cambios.
Además, echaremos un vistazo dentro del valor que el servicio devuelve como
respuesta para comprobar qué es lo que ha ocurrido en el otro lado de la conexión.
3
// Mostramos los cambios
4
foreach (ChangeOperationResponse cambio in respuesta)
5
{
6
EntityDescriptor descriptor = (EntityDescriptor)cambio.Descriptor;
7 if (descriptor != null)
8 {
9 if (descriptor.Entity.GetType().IsAssignableFrom(typeof(Cliente)))
10 {
Console.WriteLine(string.Format("Cliente: {0}\t{1}\t{2}",
11
((Cliente)descriptor.Entity).IdCliente, ((Cliente)descriptor.Enti
12
}
13
else if (descriptor.Entity.GetType().IsAssignableFrom(typeof(Pedido)))
14
{
15 Console.WriteLine(string.Format("\tPedido: {0}\t{1}\t{2}",
16 ((Pedido)descriptor.Entity).IdPedido, ((Pedido)descriptor.Entity)
descriptor.State.ToString()));
17
}
18
else if (descriptor.Entity.GetType().IsAssignableFrom(typeof(LineaPedido))
19
{
20
Console.WriteLine(string.Format("\t\tLinea: {0}\t{1}\t{2}",
21 ((LineaPedido)descriptor.Entity).IdLineaPedido, ((LineaPedido)des
descriptor.State.ToString()));
22
}
23
24
}
25
}
26
27
Esto nos devolverá la siguiente información. Como podemos observar, los campos de
los identificadores, por ejemplo, ya habrán sido rellenados por Entity Framework.
A continuación mostraremos, mediante foreach anidados, una consulta de todos los
clientes con ID = 61 (el que acabamos de insertar) junto a sus pedidos y líneas de
pedido.
1
var clientes = dbContext.Cliente.Where(cliente => cliente.IdCliente == 61);
2
Console.WriteLine("\n\nTRAS LA INSERCION:");
3
foreach (var cliente in clientes)
4 {
5 Console.WriteLine(string.Format("\tID: {0}\tNOMBRE: {1}",
6 cliente.IdCliente, cliente.Nombre));
8 {
l.Productos.Descripcion, l.Cantidad));
14
}
15
}
16
}
17
Si ejecutamos este código veremos, asombrados, que únicamente se habrá recuperado
el ID y el Nombre del cliente, pero no se mostrará nada de información acerca de
pedidos o líneas de pedido. Esto se debe a que, por defecto, la petición de consulta
es lazy, y habrá que indicar específicamente que se quiere recuperar la información de
los listados asociados.
Para realizar esta operación, basta con indicar con el método LoadProperty el listado
del objeto que se quiere recuperar, siendo el primer parámetro el objeto cuyo listado
se quiere expandir y el segundo, una cadena de texto con el nombre del listado. Así,
nuestro código tendría el siguiente aspecto:
1 Console.WriteLine("\n\nTRAS LA INSERCION:");
3 {
4 dbContext.LoadProperty(cliente, "Pedidos");
12 {
13 dbContext.LoadProperty(l, "Productos");
Console.WriteLine(string.Format("\t\t\tPRODUCTO: {0}\tCANTIDAD:
14 {1}",
15 l.Productos.Descripcion, l.Cantidad));
16 }
17 }
18 }
19
Modificación
1
// MODIFICACION
2
3
dbContext = new GestionPedidosDataService.TestDbContext(serviceRoot);
4 clientes = dbContext.Cliente.Where(cliente => cliente.IdCliente == 61);
5
10 lineaPedidoModificar.Cantidad = 59;
11
dbContext.UpdateObject(clienteModificar);
12
dbContext.UpdateObject(lineaPedidoModificar);
13
dbContext.SaveChanges();
14
Por último, el proceso de eliminación, que será similar al de modificación, salvo que
invocaremos el método DeleteObject en lugar de UpdateObject. El proceso de
eliminación en cascada es parecido al visto en la sección correspondiente de Entity
Framework, por lo que nuevamente, suele aconsejarse utilizar procedimientos
almacenados para realizar este proceso.
1 // ELIMINACION
5
var lineaPedidoEliminar = (from linea in dbContext.LineaPedido
6
where linea.IdLineaPedido ==
7 lineaPedidoBoligrafos.IdLineaPedido
8 select linea).Single();
10 dbContext.DeleteObject(lineaPedidoEliminar);
11 dbContext.SaveChanges();
Tabla Carrera.
EF 5 Code First
AppEjemploEfCodeFirst.Web: Proyecto
Web ASP.NET MVC 4 (Razor).
AppEjemploEfCodeFirst.Data: Proyecto de
tipo Biblioteca de Clases. Contendrá el contexto a la
base de datos (BBDD).
AppEjemploEFCodeFirst.Data.Entities:
Proyecto de tipo Biblioteca de Clases. Contendrá las entidades, que darán lugar posteriormente
a las tablas de la BBDD.
2. El siguiente paso será agregar Entity Framework (EF), para ello utilizaremos
el Administrador de Paquetes NuGet. EF será agregado a dos de los tres proyectos:
AppEjemploEfCodeFirst.Web
AppEjemploEfCodeFirst.Data
5. Crear la primera entidad POCO (código primero), las entidades las creáremos en el proyecto
de entidades (AppEjemploEfCodeFirst.Data.Entities)
namespace AppEjemploEfCodeFirst.Data.Entities
namespace AppEjemploEfCodeFirst.Data
}
7. Ahora sólo queda hacer uso del contexto y las entidades, para ello escribiremos en
el Load de alguna página o en el Controller en el caso de usar MVC.
namespace AppEjemploEFCodeFist.Controllers
ViewBag.LstPersonas = ctx.Personas.ToList();
return View();
EF Code First Migrations, no es otra cosa que habilitar la capacidad de actualizar la base de
datos con los cambios realizados en nuestras entidades de código (POCO)… pero veamos un
ejemplo.
Sigamos con el mismo ejemplo del articulo anterior “EF 5 Code First (Entity Framework Code
First)”, en el que teníamos una entidad Persona:
namespace AppEjemploEfCodeFirst.Data.Entities
Imaginemos que deseamos agregar una nueva propiedad, por ejemplo …. Numero de
Documento, quedando así la nueva clase:
namespace AppEjemploEfCodeFirst.Data.Entities
Entity Framework te brinda además la posibilidad de que las migraciones se realicen de forma
automática, así nos ahorraríamos el tener que ir a la Consola del Administrador de paquetes y
escribir estos 2 comandos:
Add-Migration
Update-Database
public Configuration()
AutomaticMigrationsEnabled = true;
}
Después debemos registrar el inicializador MigrateDatabaseToLatestVersion. Para ello
escribimos el siguiente código en el Global.asax, método Application_Start (o cualquier otro
punto de entrada de nuestra aplicación);
Database.SetInitializer(new MigrateDatabaseToLatestVe
rsion<AppEjemploEfCodeFirstDbContext, Configuration>());
Y con esto todo listo, ya puedes hacer cualquier modificación en el código (clases POCO) y
después de ejecutar la aplicación, los cambios serán migrados a la base de datos.
En estos casos tendrás (al menos hasta la versión actual) que forzar la migración de forma
manual, para ello escribirás el siguiente comando en la consola:
Existen además algunos casos en los que no es suficiente, pongamos un ejemplo: Imagina que
tienes una entidad con una propiedad (public long Id { get; set; }) y decides cambiar el
tipo a entero (public int Id { get; set; }).
En el caso anterior no basta con forzar la actualización. En este caso, mi consejo es que quites
la entidad del modelo (o la propiedad) y fuerces la actualización (Update-Database -Force -
Verbose), con esto se eliminará la tabla de datos, vuelves a incluir la entidad y nuevamente
fuerzas la actualización para que cree la tabla con el nuevo tipo deseado.
Model First,
le da la posibilidad de diseñar toda la lógica del negocio, le permite
crear un modelo nuevo mediante Entity Framework Designer y después genera
un esquema de la base de datos a partir del modelo.
En esta ocasión voy a usar DataBase First pero antes se debe crear una base
de datos de prueba.
1
CREATE DATABASE PruebaEF
2
GO
3
USE PruebaEF
4
GO
5 CREATE TABLE [dbo].[Personal](
6 [Id] [varchar](6) NOT NULL,
) ON [PRIMARY]
15
16
GO
17
18
SET ANSI_PADDING OFF
19
GO
20
Para este ejemplo estoy usando Visual Studio 2013 y SQL 2014.
Una vez teniendo la base de datos creada pasamos a crear una solución
distribuida en una arquitectura de 3 capas, Presentación(Proyecto Windows
Forms), Dominio(Proyecto Class Library), Persistencia Datos(Proyecto Class
Library), se hace las referencia entre capas Persistencia en Dominio,
Persistencia y Dominio en Presentación, referencio Persistencia en Presentación
por que el mapeo a la infraestructura de la DB esta en esa capa solo por eso,
pero la lógica de la aplicación va en el Dominio.
Obtenemos el modelo.
1 //------------------------------------------------------------------------------
2 // <auto-generated>
//
4
// Los cambios manuales en este archivo pueden causar un comportamiento inesperad
5 aplicación.
6 // Los cambios manuales en este archivo se sobrescribirán si se regenera el códig
7 // </auto-generated>
8 //------------------------------------------------------------------------------
9
10 namespace Prueba.PersistenciaDatos.Modelo
11 {
using System;
12
using System.Collections.Generic;
13
14
public partial class Personal
15
{
16
public string Id { get; set; }
17 public string Nombre { get; set; }
18 public string Direccion { get; set; }
21 }
}
22
23
1 using Prueba.PersistenciaDatos.Modelo;
2 using System;
3 using System.Collections.Generic;
4 using System.Data.Entity;
using System.Linq;
5
6 using System.Linq.Expressions;
7 using System.Text;
using System.Threading.Tasks;
8
9
namespace Prueba.PersistenciaDatos
10
{
11
public class PersonalRepository
12
{
13
17 {
25
27 {
{
29
int codigo;
30
var ultimoId = Convert.ToInt32(context.Personal.Max(x => x.Id)) + Convert.ToInt
31
codigo = ultimoId;
32
return string.Format("{0:000000}", codigo);
33 }
34 }
35
{
39
int resultado = context.Personal.Where(x => x.Id == codigo).Count();
40
if (resultado == 0)
41
return false;
42
else
43 return true;
44 }
45 }
47 {
54 }
55
57 {
try
58
{
59
using(var context = new PruebaEFEntities())
60
{
61
context.Personal.Add(model);
62 context.SaveChanges();
63 }
64 }
66 {
69 }
70
public void Actualizar(Personal model)
71
{
72
try
73
{
74
using(var context = new PruebaEFEntities())
75 {
76 context.Entry(model).State = EntityState.Modified;
77 context.SaveChanges();
78 }
79 }
88 {
95 }
96
97 }
98 }
99
100
101
102
103
104
105
106
107
1 using System;
using System.Collections.Generic;
2
using System.Linq;
3
using System.Text;
4
using System.Threading.Tasks;
5
using Prueba.PersistenciaDatos;
6 using System.Linq.Expressions;
7
8 namespace Prueba.Dominio
9 {
11 {
14
PersonalRepository personal = new PersonalRepository();
15
16
public List<PersistenciaDatos.ModeloMinimo.PersonalMinimalModel> GetPersonal()
17
{
18 return personal.GetPersonal();
19 }
20
22 {
return personal.Buscar(codigo);
23
}
24
25
public void Guardar(PersistenciaDatos.Modelo.Personal model)
26
{
27
BusinessException.Clear();
28 if (string.IsNullOrEmpty(model.Nombre)) BusinessException.Add("Debe ingresar el
29
30 if(BusinessException.Count() == 0)
31 {
32 if(string.IsNullOrEmpty(model.Id))
33 model.Id = personal.GenerarCodigo();
34
try
35
{
36
if(personal.Existe(model.Id))
37
{
38
personal.Actualizar(model);
39 MensajeLogica = "Registro actualizado";
40 }
41 else
42 {
43 personal.Guardar(model);
50
51 }
52 }
53
public void Eliminar(string codigo)
54
{
55
personal.Eliminar(codigo);
56
}
57
58
}
59 }
60
61
62
63
64
65
using System;
1
using System.Collections.Generic;
2
using System.ComponentModel;
3
using System.Data;
4 using System.Drawing;
5 using System.Linq;
6 using System.Text;
7 using System.Threading.Tasks;
8 using System.Windows.Forms;
using Prueba.PersistenciaDatos;
9
using Prueba.Dominio;
10
11
namespace Prueba.WindowsUI
12
{
13
public partial class frmPersonal : Form
14 {
16
Dominio.Personal personal = new Dominio.Personal();
17
18
public frmPersonal()
19
{
20
InitializeComponent();
21
}
22
25 LoadDGVPersonal();
26
27 }
28
{
30
PersistenciaDatos.Modelo.Personal model = new PersistenciaDatos.Modelo.Personal
31
model.Id = lblCodigo.Text;
32
model.Nombre = txtNombre.Text;
33
model.Direccion = txtDireccion.Text;
34 model.Telefono = txtTelefono.Text;
35 model.lEstado = Convert.ToInt16(chkEstado.Checked ? "1" : "0");
36 personal.Guardar(model);
37 LoadDGVPersonal();
38 }
39
void LoadDGVPersonal()
40
{
41
List<PersistenciaDatos.ModeloMinimo.PersonalMinimalModel> list = personal.GetPe
42
dgvPersonal.AutoGenerateColumns = false;
43
dgvPersonal.DataSource = list;
44
45 }
46
47 void ObtenerId()
{
48
strCodigo = Convert.ToString(dgvPersonal.CurrentRow.Cells[0].Value);
49
}
50
51
void Buscar()
52
{
53 if(strCodigo != string.Empty)
54 {
56 lblCodigo.Text = model.Id;
57 txtNombre.Text = model.Nombre;
txtDireccion.Text = model.Direccion;
58
txtTelefono.Text = model.Telefono;
59
chkEstado.Checked = Convert.ToBoolean(model.lEstado);
60
}
61
}
62
63 void Eliminar()
64 {
65 ObtenerId();
67 Environment.NewLine,
70
if (MessageBox.Show(msg, "Personal", MessageBoxButtons.YesNo, MessageBoxIcon.Qu
71 System.Windows.Forms.DialogResult.Yes)
72 {
73 personal.Eliminar(strCodigo);
LoadDGVPersonal();
74
}
75
76 }
77
{
79
ObtenerId();
80
Buscar();
81
tabControl1.SelectedTab = tabPage2;
82
}
83
86 if(e.KeyCode == Keys.F7)
87 {
88 Eliminar();
}
89
}
90
91
}
92
}
93
94
95
96
97
98
99
100