Para iniciar hablemos un poco acerca del Api DbContext:
DbContext representa nuestro modelo conceptual, a través de él podemos acceder a nuestras entidades para consultarlas o realizar operaciones transaccionales sobre ellas, DbContext representa la combinación de dos patrones de diseño; unidad de trabajo y repositorio que permiten lograr leer los datos y agrupar los cambios que posteriormente serán escritos en la fuente de datos como una unidad. Para mayor información les comparto este link, y adicional estas fuentes sobre Unidad de trabajo y repositorio por si quieren profundizar en el tema. Adicional el ejemplo de implementación con C#.
Ahora sí, entremos en materia. Para nuestros ejemplos nos vamos a basar en un modelo muy sencillo el cual elaboramos en un ejemplo anterior Creando nuestra base de datos, basados en nuestro contexto es muy simple pero no es útil para efectos de nuestro ejemplo. Cómo vemos tenemos una entidad Producto y una Entidad categoría, las cuales podemos relacionar, aquí les dejo algunos ejemplos de cómo crear relaciones en CodeFirst Configurar una relación uno a uno en Code First, Configurando una relación de uno a muchos en Code First y Configurando una relación de muchos a muchos en Code First.
Realizando diferentes consultas a través de linq y lambda expresions:
Para realizar consultas en nuestra base de datos podemos usar linq o expresiones lambda, usando nuestro contexto y las entidades en las que deseemos realizar la consulta, para realizar consultas linq tenemos una sintaxis bien definida como si estuviéramos realizando una consulta a través de Transact Sql, por ejemplo una consulta básica consta de tres partes, from, where y select, la diferencia es que solo hasta el final especificamos que deseamos seleccionar, por ejemplo para seleccionar todos los productos cuyo código sea 1:
using (var productosContext = new ProductosContext()) { var productos = from c in productosContext.Productos where c.Codigo == 1 select c; }
Podemos filtrar por cualquier campo, y seleccionar el que queramos, y de igual forma podemos hacer consultas en varias tablas a través de sus relaciones, y retornar tipos anónimos para soportar todos los campos como por ejemplo:
var productos = from c in productosContext.Productos from e in c.Categorias select new { codigo = c.Codigo, nombre = c.Nombre, categoria = e.Nombre };
Cabe aclarar que para estas consultas podemos usar múltiples operadores que nos ayuden a realizar nuestras consultas cómo por ejemplo funciones de agregado, filtro, agrupamiento, ordenamiento, combinación, entre otros. Cómo en este artículo no pretendemos profundizar mucho en las consultas linq, aquí les dejo algunos ejemplos y les recomiendo estudiar bastante el tema ya que con un buen dominio de él podremos sacarle mejor partido a las consultas en nuestra base de datos.
(Obtener el mayor código de los productos de la categoría Lácteos)
var productos = (from c in productosContext.Productos from e in c.Categorias where e.Nombre.Contains("Lacteos") select c).Max(f => f.Codigo);
Adicional aquí pueden encontrar todos los operadores y funciones:
Ejemplos consultas linq y operadores.
Y para realizar la primera consulta a través de lambda sería:
var producto = productosContext.Productos.FirstOrDefault(c => c.Codigo == 1);
como podemos ver mucho más sencilla y en una sola línea de código. Lambda nos permite hacer mucho más simples las consultas y ahorrar bastantes líneas de código, aquí les comparto algunos ejemplos: expresiones lambda.
En conclusión sea cual sea el método de consulta que usemos, lo que hará internamente Entity Framework es convertir estas instrucciones en sentencias Transact Sql (A no ser que tengamos mapeada nuestra entidad a un procedimiento almacenado, lo cual veremos más adelante) y ejecutarlas en nuestro motor de base de datos, el cual nos da una respuesta y EF de nuevo la convertirá en términos de nuestra entidad.
Realizando operaciones de inserción, actualización y eliminación:
Para realizar operaciones de inserción debemos crear una nueva instancia de nuestra entidad representada en la base de datos, es igual a lo que estamos acostumbrados, totalmente objetual, sólo creamos la instancia y establecemos las propiedades que queremos persistir y luego hacemos un add sobre el la entidad, con esto agregamos el registro, pero aun debemos hacer el commit para que se asiente la instrucción y esto lo hacemos a través del método SaveChanges() de nuestro contexto, esto se requiere para cualquier transacción y nos ofrece la ventaja de hacer múltiples operaciones sobre una sola transacción.
using (var productosContext = new ProductosContext()) { var producto = new Producto { Codigo = 7, Nombre = "Yogurt" }; productosContext.Productos.Add(producto); productosContext.SaveChanges(); }
Por otro lado para actualizar y eliminar cómo es obvio antes debemos realizar una consulta, para que EF se entere de que registro deseamos tratar, entonces podemos hacer una consulta especifica como vimos anteriormente, y posteriormente actualizar alguna propiedad de la entidad o simplemente eliminarla, veamos un ejemplo de cómo hacerlo.
using (var productosContext = new ProductosContext()) { var producto = productosContext.Productos.First(c => c.Codigo == 7); producto.Nombre = "Yogurt dietético"; productosContext.SaveChanges(); }
Y para eliminar
using (var productosContext = new ProductosContext()) { var producto = productosContext.Productos.First(c => c.Codigo == 7); productosContext.Productos.Remove(producto); productosContext.SaveChanges(); }
Trabajando con datos relacionales: (consulta y transacción)
Una de las grandes ventajas de un ORM es que podemos trabajar con nuestros datos relacionales sin ningún problema, es decir, podemos en una consulta traer por demanda o por defecto (tema que veremos a continuación) datos de otras tablas relacionadas, o también podemos crear una entidad con entidades anidadas que representen otras tablas y guardar en una sola transacción, ya que Entity Framework se encarga de almacenar en cada una de las tablas, enviándole tan sólo la entidad principal, veamos cómo funciona esto que acabo de mencionar:
Consultando información relacionada:
A través de la cláusula Include, podemos indicar a Entity Framework que nos retorne la información de otras entidades relacionadas, este comportamiento está activado por defecto en Entity Framework y es lo que conocemos como Eager Loading. Veamos cómo lo podemos hacer:
Almacenando información relacionada:
Para almacenar información relacionada, tenemos una gran ventaja y es que podemos guardar todo en una sola transacción sin tener que construir entidad por entidad, y hacer inserción para cada una, es decir podemos crear una sola entidad con anidamiento y listo, observemos como se hace:
using (var prodcutosContext = new ProdcutosContext()) { var producto = new Producto { Codigo = 22, Nombre = "cereal", Categorias = new Collection<Categoria> { new Categoria { Nombre = "Cereales", Descripcion = "Productos de cereal" } } }; prodcutosContext.Productos.Add(producto); prodcutosContext.SaveChanges(); }
Como podemos ver creamos un nuevo producto y a la vez creamos una nueva categoría, Entity Framework se encarga de guardar en las respectivas tablas, incluyendo la tabla de la relación.
Eager Load vs Lazy Load:
Como mencioné anteriormente a través de Entity Framework podemos obtener fácilmente datos de otras entidades relacionadas, pero siempre traer datos de entidades relacionadas a una determinada entidad sería bastante costoso, aun sabiendo que no siempre requeriremos del uso de esto. Es por esto que Entity Framework nos ofrece la posibilidad de trabajar de dos formas con respecto a esto, una de ellas es Eager load que nos permite cargar todas las entidades relacionadas que indiquemos, esto suele ser costoso ya que hacemos carga de todo y más aun si se trata de un IEnumerable ya que por cada ítem cargaríamos sus entidades relacionadas, por lo tanto se debe ser cuidadoso al usar este tipo de carga, como todo tiene escenarios donde aplica perfecto y otros dónde no es buena idea usarlo. Y por otro lado tenemos Lazy Load, que nos permite una carga por demanda, es decir solo se consultan datos relacionados en el momento en que se requieran, por ejemplo si hacemos alusión a una entidad relacionada en ese momento se hará la consulta y no una vez se consulte la principal como pasa en Eager Load. Entonces tenemos estas dos opciones y cómo ya vimos anteriormente a través de la cláusula Include, podemos indicar a Entity Framework que nos retorne los datos de las entidades relacionadas, ahora para evitar que EF siempre retorne por defecto la información de todas las entidades relacionadas debe establecer la siguiente configuración en nuestro contexto:
public ProdcutosContext() : base("name=ProdcutosContext") { this.Configuration.LazyLoadingEnabled = false; }
Recordemos que no se trata de que una opción sea mejor que la otra, si no de saber usar la opción adecuada según el escenario en el que nos encontremos, por ejemplo si sabemos siempre que debemos mostrar todos los ítemes de una lista al igual que sus sub ítemes entonces será una buena opción usar Eager loading, para obtener en una sola consulta toda la información requerida, pero si hay un escenario dónde sabemos que debemos mostrar solo sub ítemes por demanda, es decir solo si se llega a seleccionar el ítem principal, entonces es buena idea usar Lazy loading.
Bueno amigos eso es todo de este ejemplo de cómo interactuar con nuestro contexto en Entity Framework, espero les sea de utilidad y de interés.
Saludos, y buena suerte!