using System.Diagnostics; using Microsoft.EntityFrameworkCore; namespace Api; public static class Seeder { public const int Customers = 1_000; public const int Products = 500; public const int Orders = 20_000; public const int LinesPerOrder = 5; public const int Days = 30; public static readonly DateTime FirstDay = new(2026, 9, 27, 0, 0, 0, DateTimeKind.Utc); public static async Task RunAsync(string connectionString) { var options = new DbContextOptionsBuilder().UseNpgsql(connectionString).Options; await using var db = new AppDbContext(options); await db.Database.EnsureDeletedAsync(); await db.Database.EnsureCreatedAsync(); db.ChangeTracker.AutoDetectChangesEnabled = false; var stopwatch = Stopwatch.StartNew(); var random = new Random(220); string[] countries = ["RS", "DE", "US", "GB", "NL", "FR", "PL", "ES"]; string[] statuses = ["Pending", "Paid", "Shipped", "Delivered"]; string[] methods = ["Card", "PayPal", "Bank"]; var customers = Enumerable.Range(1, Customers) .Select(i => new Customer { Name = $"Customer {i}", Email = $"customer{i}@example.com", Country = countries[random.Next(countries.Length)] }) .ToList(); var products = Enumerable.Range(1, Products) .Select(i => new Product { Sku = $"SKU-{i:D5}", Name = $"Product {i}", Price = Math.Round((decimal)(random.NextDouble() * 190 + 10), 2) }) .ToList(); var carriers = new[] { "DHL", "UPS", "FedEx", "GLS", "DPD", "PostNL", "USPS", "Royal Mail" } .Select(name => new Carrier { Name = name }) .ToList(); db.AddRange(carriers); db.AddRange(customers); db.AddRange(products); await db.SaveChangesAsync(); db.ChangeTracker.Clear(); // The extra references draw from their own generator, so the orders, lines and payments // are the same rows as before the references were added. var extra = new Random(221); string[] cities = ["Belgrade", "Berlin", "Austin", "London", "Utrecht", "Lyon", "Gdansk", "Valencia"]; Address NewAddress() => new() { Street = $"{extra.Next(1, 400)} Street {extra.Next(1, 900)}", City = cities[extra.Next(cities.Length)], PostalCode = extra.Next(10_000, 99_999).ToString(), Country = countries[extra.Next(countries.Length)] }; const int chunk = 2_000; for (var start = 0; start < Orders; start += chunk) { var orders = new List(chunk); for (var i = 0; i < chunk; i++) { var customer = customers[random.Next(customers.Count)]; var orderedAt = FirstDay.AddSeconds(random.NextInt64(Days * 86_400L)); var order = new Order { CustomerId = customer.Id, OrderedAt = orderedAt, Status = statuses[random.Next(statuses.Length)] }; // Every order ships somewhere, half are billed to a second address, // and a pending order has no carrier yet. order.ShippingAddress = NewAddress(); order.BillingAddress = extra.Next(2) == 0 ? NewAddress() : null; order.CarrierId = order.Status == "Pending" ? null : carriers[extra.Next(carriers.Count)].Id; for (var l = 0; l < LinesPerOrder; l++) { var product = products[random.Next(products.Count)]; order.Lines.Add(new OrderLine { ProductId = product.Id, Quantity = random.Next(1, 5), UnitPrice = product.Price }); } var total = order.Lines.Sum(x => x.Quantity * x.UnitPrice); var paymentCount = random.Next(1, 3); for (var p = 0; p < paymentCount; p++) { order.Payments.Add(new Payment { Amount = Math.Round(total / paymentCount, 2), PaidAt = orderedAt.AddMinutes(random.Next(1, 600)), Method = methods[random.Next(methods.Length)] }); } orders.Add(order); } db.AddRange(orders); await db.SaveChangesAsync(); db.ChangeTracker.Clear(); } await db.Database.ExecuteSqlRawAsync("VACUUM ANALYZE"); Console.WriteLine( $"Seeded {Customers} customers, {Products} products, {Orders} orders, " + $"{Orders * LinesPerOrder} lines in {stopwatch.Elapsed.TotalSeconds:F1} s."); } }