Ejercicio: Interacción con datos
En el ejercicio anterior, ha creado clases de entidad y un contexto de base de datos. Después, ha usado migraciones de EF Core para crear el esquema de la base de datos.
En este ejercicio, completará la implementación de PizzaService
. El servicio usa EF Core para realizar operaciones CRUD en la base de datos.
Codificación de las operaciones CRUD
Para finalizar la implementación de PizzaService
, complete los pasos siguientes en Services\PizzaService.cs:
Realice los siguientes cambios como se muestra en el ejemplo:
- Agregue una directiva
using ContosoPizza.Data;
. - Agregue una directiva
using Microsoft.EntityFrameworkCore;
. - Agregue un campo de nivel de clase para
PizzaContext
antes del constructor. - Cambie la firma del método del constructor para aceptar un parámetro
PizzaContext
. - Cambie el código del método del constructor para asignar el parámetro al campo.
using ContosoPizza.Models; using ContosoPizza.Data; using Microsoft.EntityFrameworkCore; namespace ContosoPizza.Services; public class PizzaService { private readonly PizzaContext _context; public PizzaService(PizzaContext context) { _context = context; } /// ... /// CRUD operations removed for brevity /// ... }
La llamada de método
AddSqlite
que agregó anteriormente a Program.cs registróPizzaContext
para la inserción de dependencias. Cuando se crea la instancia dePizzaService
, se inserta un elementoPizzaContext
en el constructor.- Agregue una directiva
Reemplace el método
GetAll
con el código siguiente:public IEnumerable<Pizza> GetAll() { return _context.Pizzas .AsNoTracking() .ToList(); }
En el código anterior:
- La colección
Pizzas
contiene todas las filas de la tabla de pizzas. - El método de extensión
AsNoTracking
indica a EF Core que deshabilite el seguimiento de cambios. Puesto que esta operación es de solo lectura,AsNoTracking
puede optimizar el rendimiento. - Todas las pizzas se devuelven con
ToList
.
- La colección
Reemplace el método
GetById
con el código siguiente:public Pizza? GetById(int id) { return _context.Pizzas .Include(p => p.Toppings) .Include(p => p.Sauce) .AsNoTracking() .SingleOrDefault(p => p.Id == id); }
En el código anterior:
- El método de extensión
Include
adopta una expresión lambda para especificar que las propiedades de navegaciónToppings
ySauce
se van a incluir en el resultado mediante la carga diligente. Sin esta expresión, EF Core devuelvenull
para esas propiedades. - El método
SingleOrDefault
devuelve una pizza que coincide con la expresión lambda.- Si no hay registros coincidentes, se devuelve
null
. - Si coinciden varios registros, se produce una excepción.
- La expresión lambda describe los registros en los que la propiedad
Id
es igual al parámetroid
.
- Si no hay registros coincidentes, se devuelve
- El método de extensión
Reemplace el método
Create
con el código siguiente:public Pizza Create(Pizza newPizza) { _context.Pizzas.Add(newPizza); _context.SaveChanges(); return newPizza; }
En el código anterior:
- Se supone que
newPizza
es un objeto válido. EF Core no realiza la validación de datos, por lo que el código de usuario o el entorno de ejecución de ASP.NET Core debe controlar cualquier validación. - El método
Add
agrega la entidadnewPizza
al gráfico de objetos de EF Core. - El método
SaveChanges
indica a EF Core que conserve los cambios del objeto en la base de datos.
- Se supone que
Reemplace el método
AddTopping
con el código siguiente:public void AddTopping(int pizzaId, int toppingId) { var pizzaToUpdate = _context.Pizzas.Find(pizzaId); var toppingToAdd = _context.Toppings.Find(toppingId); if (pizzaToUpdate is null || toppingToAdd is null) { throw new InvalidOperationException("Pizza or topping does not exist"); } if(pizzaToUpdate.Toppings is null) { pizzaToUpdate.Toppings = new List<Topping>(); } pizzaToUpdate.Toppings.Add(toppingToAdd); _context.SaveChanges(); }
En el código anterior:
- Las referencias a los objetos
Pizza
yTopping
existentes se crean medianteFind
. - El objeto
Topping
se agrega a la colecciónPizza.Toppings
con el método.Add
. Si no existe, se crea una colección. - El método
SaveChanges
indica a EF Core que conserve los cambios del objeto en la base de datos.
- Las referencias a los objetos
Reemplace el método
UpdateSauce
con el código siguiente:public void UpdateSauce(int pizzaId, int sauceId) { var pizzaToUpdate = _context.Pizzas.Find(pizzaId); var sauceToUpdate = _context.Sauces.Find(sauceId); if (pizzaToUpdate is null || sauceToUpdate is null) { throw new InvalidOperationException("Pizza or sauce does not exist"); } pizzaToUpdate.Sauce = sauceToUpdate; _context.SaveChanges(); }
En el código anterior:
- Las referencias a los objetos
Pizza
ySauce
existentes se crean medianteFind
.Find
es un método optimizado para consultar registros por su clave principal.Find
busca primero en el gráfico de entidades locales antes de consultar la base de datos. - La propiedad
Pizza.Sauce
se establece en el objetoSauce
. - No es necesaria una llamada de método
Update
porque EF Core detecta que se establece la propiedadSauce
enPizza
. - El método
SaveChanges
indica a EF Core que conserve los cambios del objeto en la base de datos.
- Las referencias a los objetos
Reemplace el método
DeleteById
con el código siguiente:public void DeleteById(int id) { var pizzaToDelete = _context.Pizzas.Find(id); if (pizzaToDelete is not null) { _context.Pizzas.Remove(pizzaToDelete); _context.SaveChanges(); } }
En el código anterior:
- El método
Find
recupera una pizza por la clave principal (que, en este caso, esId
). - El método
Remove
elimina la entidadpizzaToDelete
del gráfico de objetos de EF Core. - El método
SaveChanges
indica a EF Core que conserve los cambios del objeto en la base de datos.
- El método
Guarde todos los cambios y ejecute
dotnet build
. Corrija los errores que se produzcan.
Inicializar la base de datos
Ha codificado las operaciones CRUD para PizzaService
, pero es más fácil probar la operación de lectura si la base de datos contiene buenos datos. Decide modificar la aplicación para inicializar la base de datos en el momento en que arranca.
Advertencia
Este código de inicialización de la base de datos no tiene en cuenta las condiciones de carrera, por lo que debe tener cuidado al usarlo en un entorno distribuido sin mitigar los cambios.
En la carpeta Data, agregue un archivo nuevo llamado DbInitializer.cs.
Agregue el siguiente código a Data\DbInitializer.cs:
using ContosoPizza.Models; namespace ContosoPizza.Data { public static class DbInitializer { public static void Initialize(PizzaContext context) { if (context.Pizzas.Any() && context.Toppings.Any() && context.Sauces.Any()) { return; // DB has been seeded } var pepperoniTopping = new Topping { Name = "Pepperoni", Calories = 130 }; var sausageTopping = new Topping { Name = "Sausage", Calories = 100 }; var hamTopping = new Topping { Name = "Ham", Calories = 70 }; var chickenTopping = new Topping { Name = "Chicken", Calories = 50 }; var pineappleTopping = new Topping { Name = "Pineapple", Calories = 75 }; var tomatoSauce = new Sauce { Name = "Tomato", IsVegan = true }; var alfredoSauce = new Sauce { Name = "Alfredo", IsVegan = false }; var pizzas = new Pizza[] { new Pizza { Name = "Meat Lovers", Sauce = tomatoSauce, Toppings = new List<Topping> { pepperoniTopping, sausageTopping, hamTopping, chickenTopping } }, new Pizza { Name = "Hawaiian", Sauce = tomatoSauce, Toppings = new List<Topping> { pineappleTopping, hamTopping } }, new Pizza { Name="Alfredo Chicken", Sauce = alfredoSauce, Toppings = new List<Topping> { chickenTopping } } }; context.Pizzas.AddRange(pizzas); context.SaveChanges(); } } }
En el código anterior:
- La clase
DbInitializer
y el métodoInitialize
se definen comostatic
. Initialize
acepta un objetoPizzaContext
como parámetro.- Si no hay registros en ninguna de las tres tablas, se crean los objetos
Pizza
,Sauce
yTopping
. - Los objetos
Pizza
y sus propiedades de navegaciónSauce
yTopping
se agregan al gráfico de objetos medianteAddRange
. - Los cambios del gráfico de objetos se confirman en la base de datos mediante
SaveChanges
.
- La clase
La clase DbInitializer
está lista para inicializar la base de datos, pero se le debe llamar desde Program.cs. En los pasos siguientes se crea un método de extensión para IHost
que llame a DbInitializer.Initialize
:
En la carpeta Data, agregue un archivo nuevo llamado Extensions.cs.
Agregue el siguiente código a Data\Extensions.cs:
namespace ContosoPizza.Data; public static class Extensions { public static void CreateDbIfNotExists(this IHost host) { { using (var scope = host.Services.CreateScope()) { var services = scope.ServiceProvider; var context = services.GetRequiredService<PizzaContext>(); context.Database.EnsureCreated(); DbInitializer.Initialize(context); } } } }
En el código anterior:
El método
CreateDbIfNotExists
se define como una extensión deIHost
.Se crea una referencia al servicio
PizzaContext
.EnsureCreated garantiza que la base de datos existe.
Importante
Si no existe ninguna base de datos,
EnsureCreated
crea una. La nueva base de datos no está configurada para migraciones, así que use este método con precaución.Se llama al método
DbIntializer.Initialize
. El objetoPizzaContext
se pasa como un parámetro.
Por último, en Program.cs, reemplace el comentario
// Add the CreateDbIfNotExists method call
por el código siguiente para llamar al nuevo método de extensión:app.CreateDbIfNotExists();
Este código llama al método de extensión que definió anteriormente cada vez que se ejecuta la aplicación.
Guarde todos los cambios y ejecute
dotnet build
.
Escribió todo el código que necesita para realizar operaciones CRUD básicas y inicializar la base de datos al iniciarse. En el siguiente ejercicio, pruebe esas operaciones en la aplicación.