1.24.2011

Maestro Detalle con el Entity Framework 4.0

Quizás la tarea más común a la hora de desarrollar un software de negocios es el uso de maestros detalles en nuestra aplicación, es decir una entidad padre con varios hijos que la complementan. Esto conlleva a que se de una pregunta entre los desarrolladores con mucha frecuencia: ¿cómo funciona un maestro detalle con el Entity Framework? En este post vamos a solucionar este problema.

Foreign Keys en el Entity Framework 4.0

En el EF 4 se creo una nueva forma de manipular asociaciones a través de las llaves foráneas de las tablas asociadas. Esta forma de relación se le llama “Asociación de FK”. Esto permite incluir las columnas de la llave foránea como propiedades en las entidades con lo cual el manejo de las relaciones es mucho más sencillo a la hora de llevar a cabo operaciones del CRUD – Create – Retrieve – Update – Delete. Por ejemplo, a la hora de crear un producto podemos hacer la asociación entre las clases de la forma que se presenta a continuación.

using (var context = new Context()) 
{
Product p = new Product
{
ID = 1,
Name = "Papas Fritas Congeladas",
Category = context.Categories
.Single(c => c.Name == "Comida")
};
context.SaveChanges();
}

Ejemplo


Como de costumbre, vamos a ilustrar el concepto a través de un ejemplo desarrollado en .NET. En este caso vamos a tener una base de datos con el siguiente esquema.


image


Cabe resaltar que los campos Id de cada una de las tablas son AutoIdentity y que la relación del esquema se hace entre el campo OrdenId de la tabla detalle y el campo Id de la tabla Orden. Luego procedemos a crear un proyecto de Tipo Librería de Windows – dll y generamos el esquema se genera automáticamente para el EntityFramework.


image


Como podemos ver en la figura anterior aunque se genera la relación para navegar entre ambas clases, la propiedad IdOrden que relaciona la Orden con el detalle se genera para que podamos utilizarla vía código. Luego procedemos a crear la capa de lógica de negocios, en la cual solo vamos a crear una clase para el manejo de las órdenes y el método que me permite agregar una orden y sus respectivos detalles.

using DAL;

namespace BL
{
public class Orden_BL
{
public int AgregarOrden( Orden pOrden, List<Detalle> pDetalle )
{
foreach (Detalle d in pDetalle)
{
pOrden.Detalle.Add(d);
}

Common.ObtenerContexto().Ordenes.AddObject( pOrden );

return Common.ObtenerContexto().SaveChanges();

}
}
}

En este caso procedemos a recibir dos parámetros a la hora de ingresar la orden a la base de datos: La orden y la lista de detalles que le vamos a asociar a la orden. Seguidamente recorremos la lista de detalles y los vamos agregando uno a uno. Por último agregamos la nueva orden al contexto del esquema del entity framework y procedemos a salvarlo en la base de datos. El código del método ObtenerContexto es el siguiente:

using DAL;

namespace BL
{
public class Common
{
private static Ejemplos_BlogEntities _contexto;
public static Ejemplos_BlogEntities ObtenerContexto( )
{
if (_contexto == null)
_contexto = new Ejemplos_BlogEntities();
return _contexto;
}
}
}

Por último creamos el UI. Esta parte es un poco más simple ya que solamente tenemos que interactuar con la lista de detalles y la orden que vamos a crear. En primera instancia vamos a crear una variable del tipo Lista de detalle para almacenar todos los detalles que vamos creando:

private List<Detalle> _detalles = new List<Detalle>();

public List<Detalle> Detalles
{
get { return _detalles; }
set { _detalles = value; }
}

Seguidamente creamos un botón para agregar detalles a la orden que queremos crear y en este evento procedemos a agregar el nuevo detalle a la colección de detalles y a refrescar el Grid de detalles.

private void btnAgregar_Click( object sender, EventArgs e )
{
Detalle _detalle = new Detalle();
_detalle.Producto = txtProducto.Text;
_detalle.Cantidad = Convert.ToDecimal( txtCantidad.Text );

Detalles.Add( _detalle );

dgvProductos.DataSource = null;
dgvProductos.DataSource = Detalles;
}

Nótese que hasta el momento no existe ningún tipo de interacción con la clase Orden. Esta se lleva a cabo cuando el usuario del sistema decide guardar la orden; es en este momento en donde se procede a crear una instancia de la orden y a invocar el método de la lógica de negocios que hace la inserción de la orden con sus respectivos detalles.

private void btnTerminar_Click( object sender, EventArgs e )
{
Orden _orden = new Orden();
_orden.Cliente = txtCliente.Text;
_orden.Fecha = DateTime.Now;
_orden.Numero = txtNumeroDeOrden.Text;

Orden_BL _ordenBL = new Orden_BL();
_ordenBL.AgregarOrden( _orden, Detalles );
}


La pantalla que estamos utilizando – advirtiendo que yo por lo general hago pantalla muy desagradables – es la siguiente:


image


Si revisamos la tabla de órdenes vemos que la consulta nos retorna el siguiente resultado:


image


Vale la pena recalcar el hecho de que el Id autogenerado de la orden es 1. Ahora veamos la consulta a la tabla de detalles:


image


Nótese el hecho de que la relación de llave foránea con la orden se hizo de forma automática ya que en ninguna parte del código procedimos a asignar directamente el Id de la llave foránea, simplemente asignamos cada uno de los detalles a la lista de detalles de la orden en la capa de negocios.


Es importante de hacer notar a los que nos leen que no se utiliza ningun esquema de transaccionabilidad en este modelo, ya que no es necesario hacer rollback o commit de la operación en algún momento dado porque el esquema lo estamos asociando en memoria y antes de enviarlo a la base de datos, por lo que al final es una sola operación la que se ejecuta contra la base de datos.


Technorati Tags: ,

15 comentarios:

Anónimo dijo...

Luis, estuve mucho tiempo buscando como hacer para poder grabar datos de una entidad hija, sin tener previamente guardada la entidad padre. Tu articulo me ha sido de gran ayuda. Saludos !

Luis D. Rojas dijo...

Gracias por tu comentario y por tomarte el tiempo para leer mi blog :)

Anónimo dijo...

Luis, te hago una consulta. Supongamos que pueda buscar los detalles asociados a una orden, y los muestre de la misma manera que en el ejemplo (en la grilla). Y un boton modificar, para modificar ese registro. Como seria el funcionamiento? Deberia cargar los pedidos ya existentes en la lista de detalle ? o solo debo cargar los pedidos nuevos? Y como haria para actualizar un registro? Gracias !

JEDI dijo...

compañero buena tarde fijate q me toco un proyecto q no puedo terminar estoy trabajando con una base de datos postgresql y visual studio 2010 pero me marca un erro en visual studio y no me deja colilarlo como puedo resolver el error 175:El proveedor de almacenamiento especificado no se encuentra en la configuracion o no es válido.
como puedo quitarselo gracias

Nico dijo...
Este comentario ha sido eliminado por el autor.
Nico dijo...

Hola Diego, estuve revisando tu post y me pareció muy interesante. Quería consultarte como harías para eliminar en ese mismo escenario, ya que estuve probando y no he podido. Desde ya muchas gracias..

Sebastian dijo...

Un espectaculo lo tuyo....pase bastante tiempo sin encontrar un ejemplo Padre-Hijo facil y completo como el tuyo! Muchas gracias!!!! Y segui asi!!

dhuerta dijo...

Estoy usando los segmentos de código como guía, pero al implementarlo en mi proyecto, solamente me guarda el último registro este el código:


public bool _agregarTraspaso(EntTrapasos _entTraspasos)
{
try
{
DBNastiaColorPOSEntities _DBNastia = new DBNastiaColorPOSEntities();
Traspasos _traspasos = new Traspasos();
TraspasoDetalle _traspasosDet = new TraspasoDetalle();

_traspasos.idSucursal = _entTraspasos.idSucursal;
_traspasos.idTraspaso = _entTraspasos.idTraspaso;
_traspasos.idUsuario = _entTraspasos.idUsuario;
_traspasos.fechaSolicitud = _entTraspasos.fecha;
_traspasos.noArticulos = _entTraspasos.noArticulos;
_traspasos.fecha = _entTraspasos.fecha;
_traspasos.noLineas = _entTraspasos.noLineas;
_traspasos.fechaAplicado = _entTraspasos.fechaAplicado;
_traspasos.subtotal = _entTraspasos.subtotal;
_traspasos.impteIVA = _entTraspasos.impteIVA;
_traspasos.total = _entTraspasos.total;

foreach (var p in this.listaTraspaso)
{
_traspasosDet.idSucursal = p.idSucursal;
_traspasosDet.idTraspaso = p.idTraspaso;
_traspasosDet.idProducto = p.idProducto;
_traspasosDet.precioCosto = p.precioCosto;
_traspasosDet.precioVenta = p.precioVenta;
_traspasosDet.cantidad = p.cantidad;
_traspasosDet.importeIVA = p.importeIVA;
_traspasosDet.subTotal = p.subTotal;
_traspasosDet.total = p.total;

this.detalleTraspaso.Add(_traspasosDet);
}


foreach (TraspasoDetalle _trasp in detalleTraspaso)
{
_traspasos.TraspasoDetalle.Add(_trasp);
}

_DBNastia.Traspasos.AddObject(_traspasos);
_DBNastia.SaveChanges();

return true;
}
catch (Exception x)
{
throw new Exception(x.Message);
}
}

Qué sera que estoy haciendo mal?

Anónimo dijo...

podrias porfavor continuar este ejemplo y hacer la parte para actualizar registro no encuentro como seris util para todos

ksoto dijo...

Amigo Buenas Noches tengo una inquietud en donde se declara "Ejemplos_BlogEntities _Contexto" me sale un error por que no encuentra ninguna definición, ya he dado vueltas y nada me gustaría que me ayudaras esta muy bueno el tuto pero me falta poder ejecutar

Unknown dijo...

Para Duarte (solamente me guarda el último registro este el código),
me paso lo mismo que a vos y estuve todo el día buscando que podía ser, finalmente me di cuenta que hay que instanciar Traspasos _traspasos = new Traspasos(); dentro del foreach porque sino actualiza siempre sobre el mismo aunque hagas el add, lo toma como un update. seguramente ya lo habias resuelto, pero le puede ser de ayuda a otro que como yo se encuentra con este problema ahora. Saludos,
Diego

Anónimo dijo...

Luis:

Gracias por el artículo. Entiendo lo del tema de no preocuparnos por la transacción para este escenario. Sin embargo; como se trabaja en EF cuando yo tengo que asegurarme que se graba en varias tablas?. Es decir; X ejemplo cuando grabo una factura (cab-detalle) y además necesito grabar el movimiento en almacen (otra tabla). Considerando que si no se puede grabar en las 3 o surge un error no se grabe nada en ninguna tabla.

Shanito dijo...

compañero buena tarde fijate q me toco un proyecto q no puedo terminar estoy trabajando con una base de datos Oracle y visual studio 2013 pero me marca un error en visual studio y no me deja correrlo como puedo resolver el error :El proveedor de almacenamiento especificado no se encuentra en la configuracion o no es válido.
estoy trabajando con wcf

Anónimo dijo...

Hola Luis el ejemplo se ve interesante sin embargo seria bueno que colgases el fuente del mismo para poder probarlo sin errores, se que a muchos les esta pasando lo mismo.

IcebergDelphi dijo...

Hola amigo tendras el codigo fuente? ya que sigo tu ejemplo, pero al querer hacer el foreach antes de guardar, pos nunca llega a pasar hasta el SaveChanges();
No me da ningun error, pero eso es lo que pasa... saludos.