CoffeShop cz. 2 – Migracje i Seedowanie
Kontynuuje prace nad projektem, żeby tyle nie musieć mockować podepnę pod wszystko baze przy użyciu EF Core 🙂
Stworzyłem 3 encje które całkowicie będą wystarczyły do naszego projektu.
Są to: Order, OrderDetail oraz Product.
namespace Domain.Entities { public class Order { public Order() { OrderDetails = new HashSet<OrderDetail>(); } public int Id { get; set; } public ICollection<OrderDetail> OrderDetails { get; set; } public DateTime OrderPlaced { get; set; } public DateTime? OrderCompleted { get; set; } public OrderStatus Status { get; set; } } public class OrderDetail { public int Id { get; set; } public int Quantity { get; set; } public decimal UnitPrice { get; set; } public int UnitTimeToPrepare { get; set; } public string AdditionalInfo { get; set; } public int ProductId { get; set; } public int OrderId { get; set; } public Product Product { get; set; } public Order Order { get; set; } } public class Product { public Product() { OrderDetails = new HashSet<OrderDetail>(); } public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } public int TimeToPrepare { get; set; } public ICollection<OrderDetail> OrderDetails { get; set; } } }
Encja Order posiada pole OrderStatus której jest enumem posiadający 3 stany:
NotStarted, InProgress, Completed.
Dodane zostały także pola wskazujące na czas oczekiwania na zamówienie oraz napiwek przy zamówieniu powyżej 5 kaw wynoszący +10% do ceny zamówienia.
Dzięki zastosowaniu ICollection<OrderDetail> w klasach Order i Product oraz dodaniu pól Product product i Order order wraz z ich kluczami otrzymaliśmy relacje One-To-Many. 🙂
Teraz tworzymy Interfejs IContext i implementujemy go. Posłuży on do Dependency Injection poprzez konstruktor.
namespace Domain.Interfaces { public interface IContext { DbSet<Product> Products { get; set; } DbSet<Order> Orders { get; set; } DbSet<OrderDetail> OrderDetails { get; set; } Task<int> SaveChangesAsync(); } }
namespace Infrastructure { public class Context : DbContext, IContext { public DbSet<Product> Products { get; set; } public DbSet<Order> Orders { get; set; } public DbSet<OrderDetail> OrderDetails { get; set; } public Context(DbContextOptions<Context> options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { // builder.ApplyConfigurationsFromAssembly(typeof(Context).Assembly); builder.Entity<OrderDetail>() .HasOne(o => o.Product) .WithMany(p => p.OrderDetails) .OnDelete(DeleteBehavior.Cascade); } public async Task<int> SaveChangesAsync() { return this.SaveChanges(); } } }
Następnie dodajemy DI dla IContext oraz ConnectionString który zczytywany jest z appsettings.json w warstwie Prezentacji.
namespace Infrastructure { public static class DependencyInjection { public static void AddInfrastructure(this IServiceCollection services, IConfiguration configuration) { services.AddDbContext<Context>(opt => opt.UseSqlServer(configuration.GetConnectionString("Context"))); services.AddScoped<IContext, Context>(); } } }
"ConnectionStrings": { "Context": "Server=(localdb)\\mssqllocaldb;Database=CoffeShop;Trusted_Connection=True;MultipleActiveResultSets=true", },
Tworzymy migracje wpisując w okno PMC: Add-Migration Initial
Jeśli wyskakuje błąd trzeba zrestartować Visual Studio, żeby widziało komendę. Dodatkowo w warstwie aplikacji trzeba doinstalować Microsoft.EntityFrameworkCore.Design o co „prosi” sam PMC.
Jeśli stworzenie Migracji się powiodło wpisujemy w PMC: Update-Database
Dodam teraz opcje seedowania bazy.
Można by to zrobić za pomocą ModelBuildera ale zrobie poprostu endpoint który będziemy mogli seedować bazę danych jeśli nie posiada ona jeszcze elementów.
Najpierw dodajemy klase do seedowania:
namespace Application.System.Command { public class SampleDataSeeder : IRequest { public class SampleDataSeederHandler : IRequestHandler<SampleDataSeeder> { private readonly IContext _context; public SampleDataSeederHandler(IContext context) { _context = context; } private readonly Dictionary<int, Product> Products = new Dictionary<int, Product>(); public async Task<Unit> Handle(SampleDataSeeder request, CancellationToken cancellationToken) { if (_context.Orders.Any()) { return Unit.Value; } await SeedProducts(); await SeedOrders(); return Unit.Value; } private async Task SeedProducts() { if (_context.Products.Any() || Products.Any()) { return; } Products.Add(1, new Product { Name = "Cappuccino S", Price = 5.99m, TimeToPrepare = 3 }); Products.Add(2, new Product { Name = "Cappuccino M", Price = 8.99m, TimeToPrepare = 4 }); Products.Add(3, new Product { Name = "Cappuccino L", Price = 9.99m, TimeToPrepare = 4 }); Products.Add(4, new Product { Name = "Espresso", Price = 4.99m, TimeToPrepare = 2 }); Products.Add(5, new Product { Name = "Espresso Doppio", Price = 5.99m, TimeToPrepare = 4 }); Products.Add(6, new Product { Name = "Americano S", Price = 4.99m, TimeToPrepare = 2 }); Products.Add(7, new Product { Name = "Americano M", Price = 6.99m, TimeToPrepare = 3 }); Products.Add(8, new Product { Name = "Americano L", Price = 8.99m, TimeToPrepare = 4 }); Products.Add(9, new Product { Name = "Latte S", Price = 6.99m, TimeToPrepare = 4 }); Products.Add(10, new Product { Name = "Latte M", Price = 8.59m, TimeToPrepare = 4 }); Products.Add(11, new Product { Name = "Latte L", Price = 9.99m, TimeToPrepare = 5 }); foreach (var product in Products.Values) { _context.Products.Add(product); } await _context.SaveChangesAsync(); } private async Task SeedOrders() { var orders = new List<Order>() { new Order { OrderPlaced = DateTime.UtcNow, OrderCompleted = DateTime.UtcNow.AddMinutes(7), Status = OrderStatus.Completed }.AddOrderDetails( new OrderDetail { Product = Products[1], Quantity = 2, }, new OrderDetail { Product = Products[2], Quantity = 1, }), new Order { OrderPlaced = DateTime.UtcNow, OrderCompleted = DateTime.UtcNow.AddMinutes(2), Status = OrderStatus.Completed }.AddOrderDetails( new OrderDetail { Product = Products[3], Quantity = 1, }), new Order { OrderPlaced = DateTime.UtcNow, OrderCompleted = DateTime.UtcNow.AddMinutes(9), Status = OrderStatus.Completed, }.AddOrderDetails( new OrderDetail { Product = Products[4], Quantity = 1, }, new OrderDetail { Product = Products[5], Quantity = 3, }), new Order { OrderPlaced = DateTime.UtcNow, OrderCompleted = DateTime.UtcNow.AddMinutes(6), Status = OrderStatus.Completed, }.AddOrderDetails( new OrderDetail { Product = Products[6], Quantity = 2, }), new Order { OrderPlaced = DateTime.UtcNow, OrderCompleted = DateTime.UtcNow.AddMinutes(5), Status = OrderStatus.Completed, }.AddOrderDetails( new OrderDetail { Product = Products[7], Quantity = 3, }), }; foreach (var order in orders) { foreach (var detail in order.OrderDetails) { detail.UnitPrice = detail.Product.Price * detail.Quantity; detail.UnitTimeToPrepare = detail.Product.TimeToPrepare * detail.Quantity; } } _context.Orders.AddRange(orders); await _context.SaveChangesAsync(); } } } }
Oraz endpoint:
namespace Presentation.Controllers { public class SystemController : BaseController { [HttpGet] public async Task<Unit> SeedDatabase() { return await Mediator.Send(new SampleDataSeeder()); } } }
Włączamy nasz projekt, przechodzimy do Swaggera i strzelamy w /api/system.
Teraz jeśli używamy Visual Studio 2019 możemy w łatwy sposób podejrzeć zawartość naszej bazy.
Można wpisać lub użyć skrótu klawiszowego.
Otwieramy naszą baze i sprawdzamy czy wszystko się zasiało jak miało 🤔
Wszystko się udało 😀
Cały kod zamieszczam na github: https://github.com/MichaelStett/CoffeShop
Dziękuje za Twoją uwagę i do następnego 😀