diff --git a/CS/PivotTableBindToData/Data/PivotRow.cs b/CS/PivotTableBindToData/Data/PivotRow.cs deleted file mode 100644 index 34e6e5a..0000000 --- a/CS/PivotTableBindToData/Data/PivotRow.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace PivotTableBindToData.Data { - public class PivotRow { - public string CategoryName { get; set; } - public string ProductName { get; set; } - public decimal? UnitPrice { get; set; } - public DateTime? OrderDate { get; set; } - public string ShipCountry { get; set; } - public string ShipCity { get; set; } - } -} diff --git a/CS/PivotTableBindToData/Data/WeatherForecast.cs b/CS/PivotTableBindToData/Data/WeatherForecast.cs deleted file mode 100644 index e6e6cec..0000000 --- a/CS/PivotTableBindToData/Data/WeatherForecast.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace PivotTableBindToData.Data -{ - public class WeatherForecast - { - public DateTime Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary { get; set; } - } -} \ No newline at end of file diff --git a/CS/PivotTableBindToData/Data/WeatherForecastService.cs b/CS/PivotTableBindToData/Data/WeatherForecastService.cs deleted file mode 100644 index 7a07b89..0000000 --- a/CS/PivotTableBindToData/Data/WeatherForecastService.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace PivotTableBindToData.Data -{ - public class WeatherForecastService - { - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - public Task GetForecastAsync(DateTime startDate) - { - var rng = new Random(); - return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = startDate.AddDays(index), - TemperatureC = rng.Next(-20, 55), - Summary = Summaries[rng.Next(Summaries.Length)] - }).ToArray()); - } - } -} \ No newline at end of file diff --git a/CS/PivotTableBindToData/Northwind.db b/CS/PivotTableBindToData/Northwind.db deleted file mode 100644 index ef5dea8..0000000 Binary files a/CS/PivotTableBindToData/Northwind.db and /dev/null differ diff --git a/CS/PivotTableBindToData/Northwind/Category.cs b/CS/PivotTableBindToData/Northwind/Category.cs deleted file mode 100644 index 1e99e91..0000000 --- a/CS/PivotTableBindToData/Northwind/Category.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; - -#nullable disable - -namespace PivotTableBindToData.Northwind { - public partial class Category { - public Category() { - Products = new HashSet(); - } - - public int CategoryId { get; set; } - public string CategoryName { get; set; } - public string Description { get; set; } - public byte[] Picture { get; set; } - - public virtual ICollection Products { get; set; } - } -} diff --git a/CS/PivotTableBindToData/Northwind/Customer.cs b/CS/PivotTableBindToData/Northwind/Customer.cs deleted file mode 100644 index 774cf0d..0000000 --- a/CS/PivotTableBindToData/Northwind/Customer.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; - -#nullable disable - -namespace PivotTableBindToData.Northwind { - public partial class Customer { - public Customer() { - Orders = new HashSet(); - } - - public string CustomerId { get; set; } - public string CompanyName { get; set; } - public string ContactName { get; set; } - public string ContactTitle { get; set; } - public string Address { get; set; } - public string City { get; set; } - public string Region { get; set; } - public string PostalCode { get; set; } - public string Country { get; set; } - public string Phone { get; set; } - public string Fax { get; set; } - - public virtual ICollection Orders { get; set; } - } -} diff --git a/CS/PivotTableBindToData/Northwind/Employee.cs b/CS/PivotTableBindToData/Northwind/Employee.cs deleted file mode 100644 index 75ae9b2..0000000 --- a/CS/PivotTableBindToData/Northwind/Employee.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; - -namespace PivotTableBindToData.Northwind { - public partial class Employee { - public int EmployeeId { get; set; } - [Required] - public string LastName { get; set; } - [Required] - public string FirstName { get; set; } - [Required] - public string Title { get; set; } - public string TitleOfCourtesy { get; set; } - public Nullable BirthDate { get; set; } - [Required] - [Range(typeof(DateTime), "1/1/1992", "1/1/2000", - ErrorMessage = "HireDate must be between {1:d} and {2:d}")] - public Nullable HireDate { get; set; } - public string Address { get; set; } - public string City { get; set; } - public string Region { get; set; } - public string PostalCode { get; set; } - public string Country { get; set; } - public string HomePhone { get; set; } - public string Extension { get; set; } - public byte[] Photo { get; set; } - public string Notes { get; set; } - public Nullable ReportsTo { get; set; } - public string PhotoPath { get; set; } - public virtual ICollection Orders { get; set; } - public string Text => $"{FirstName} {LastName} ({Title})"; - public string ImageFileName => $"Employees/{EmployeeId}.jpg"; - } -} diff --git a/CS/PivotTableBindToData/Northwind/Invoice.cs b/CS/PivotTableBindToData/Northwind/Invoice.cs deleted file mode 100644 index 1a2e335..0000000 --- a/CS/PivotTableBindToData/Northwind/Invoice.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -#nullable disable - -namespace PivotTableBindToData.Northwind { - public partial class Invoice { - public string ShipName { get; set; } - public string ShipAddress { get; set; } - public string ShipCity { get; set; } - public string ShipRegion { get; set; } - public string ShipPostalCode { get; set; } - public string ShipCountry { get; set; } - public string CustomerId { get; set; } - public string CustomerName { get; set; } - public string Address { get; set; } - public string City { get; set; } - public string Region { get; set; } - public string PostalCode { get; set; } - public string Country { get; set; } - public string Salesperson { get; set; } - public int OrderId { get; set; } - - - [Required] - [Range(typeof(DateTime), "1/1/1996", "1/1/1999", - ErrorMessage = "Order Date must be between {1:d} and {2:d}")] - public DateTime? OrderDate { get; set; } - - - public DateTime? RequiredDate { get; set; } - public DateTime? ShippedDate { get; set; } - public string ShipperName { get; set; } - public int ProductId { get; set; } - [Required] - - public string ProductName { get; set; } - - public decimal UnitPrice { get; set; } - public short Quantity { get; set; } - public float Discount { get; set; } - public decimal? ExtendedPrice { get; set; } - public decimal? Freight { get; set; } - } -} diff --git a/CS/PivotTableBindToData/Northwind/NorthwindContext.cs b/CS/PivotTableBindToData/Northwind/NorthwindContext.cs deleted file mode 100644 index 1421fd7..0000000 --- a/CS/PivotTableBindToData/Northwind/NorthwindContext.cs +++ /dev/null @@ -1,223 +0,0 @@ -using Microsoft.EntityFrameworkCore; -#nullable disable - -namespace PivotTableBindToData.Northwind { - public partial class NorthwindContext : DbContext { - - public NorthwindContext(DbContextOptions options) - : base(options) { - } - - public virtual DbSet Categories { get; set; } - public virtual DbSet Customers { get; set; } - public virtual DbSet Invoices { get; set; } - public virtual DbSet Orders { get; set; } - public virtual DbSet OrderDetails { get; set; } - public virtual DbSet Products { get; set; } - public virtual DbSet Shippers { get; set; } - public virtual DbSet Suppliers { get; set; } - public virtual DbSet Employees { get; set; } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.HasAnnotation("Relational:Collation", "SQL_Latin1_General_CP1_CI_AS"); - modelBuilder.Entity(entity => { - entity.HasIndex(e => e.CategoryName, "CategoryName"); - entity.Property(e => e.CategoryId).HasColumnName("CategoryID"); - entity.Property(e => e.CategoryName) - .IsRequired() - .HasMaxLength(15); - entity.Property(e => e.Description).HasColumnType("ntext"); - entity.Property(e => e.Picture).HasColumnType("image"); - }); - modelBuilder.Entity(entity => { - entity.HasIndex(e => e.City, "City"); - entity.HasIndex(e => e.CompanyName, "CompanyName"); - entity.HasIndex(e => e.PostalCode, "PostalCode"); - entity.HasIndex(e => e.Region, "Region"); - entity.Property(e => e.CustomerId) - .HasMaxLength(5) - .HasColumnName("CustomerID") - .IsFixedLength(true); - entity.Property(e => e.Address).HasMaxLength(60); - entity.Property(e => e.City).HasMaxLength(15); - entity.Property(e => e.CompanyName) - .IsRequired() - .HasMaxLength(40); - entity.Property(e => e.ContactName).HasMaxLength(30); - entity.Property(e => e.ContactTitle).HasMaxLength(30); - entity.Property(e => e.Country).HasMaxLength(15); - entity.Property(e => e.Fax).HasMaxLength(24); - entity.Property(e => e.Phone).HasMaxLength(24); - entity.Property(e => e.PostalCode).HasMaxLength(10); - entity.Property(e => e.Region).HasMaxLength(15); - }); - modelBuilder.Entity(entity => { - entity.HasNoKey(); - entity.ToView("Invoices"); - entity.Property(e => e.Address).HasMaxLength(60); - entity.Property(e => e.City).HasMaxLength(15); - entity.Property(e => e.Country).HasMaxLength(15); - entity.Property(e => e.CustomerId) - .HasMaxLength(5) - .HasColumnName("CustomerID") - .IsFixedLength(true); - entity.Property(e => e.CustomerName) - .IsRequired() - .HasMaxLength(40); - entity.Property(e => e.ExtendedPrice).HasColumnType("money"); - entity.Property(e => e.Freight).HasColumnType("money"); - entity.Property(e => e.OrderDate).HasColumnType("datetime"); - entity.Property(e => e.OrderId).HasColumnName("OrderID"); - entity.Property(e => e.PostalCode).HasMaxLength(10); - entity.Property(e => e.ProductId).HasColumnName("ProductID"); - entity.Property(e => e.ProductName) - .IsRequired() - .HasMaxLength(40); - entity.Property(e => e.Region).HasMaxLength(15); - entity.Property(e => e.RequiredDate).HasColumnType("datetime"); - entity.Property(e => e.Salesperson) - .IsRequired() - .HasMaxLength(31); - entity.Property(e => e.ShipAddress).HasMaxLength(60); - entity.Property(e => e.ShipCity).HasMaxLength(15); - entity.Property(e => e.ShipCountry).HasMaxLength(15); - entity.Property(e => e.ShipName).HasMaxLength(40); - entity.Property(e => e.ShipPostalCode).HasMaxLength(10); - entity.Property(e => e.ShipRegion).HasMaxLength(15); - entity.Property(e => e.ShippedDate).HasColumnType("datetime"); - entity.Property(e => e.ShipperName) - .IsRequired() - .HasMaxLength(40); - entity.Property(e => e.UnitPrice).HasColumnType("money"); - }); - modelBuilder.Entity(entity => { - entity.HasIndex(e => e.CustomerId, "CustomerID"); - entity.HasIndex(e => e.CustomerId, "CustomersOrders"); - entity.HasIndex(e => e.EmployeeId, "EmployeeID"); - entity.HasIndex(e => e.EmployeeId, "EmployeesOrders"); - entity.HasIndex(e => e.OrderDate, "OrderDate"); - entity.HasIndex(e => e.ShipPostalCode, "ShipPostalCode"); - entity.HasIndex(e => e.ShippedDate, "ShippedDate"); - entity.HasIndex(e => e.ShipVia, "ShippersOrders"); - entity.Property(e => e.OrderId).HasColumnName("OrderID"); - entity.Property(e => e.CustomerId) - .HasMaxLength(5) - .HasColumnName("CustomerID") - .IsFixedLength(true); - entity.Property(e => e.EmployeeId).HasColumnName("EmployeeID"); - entity.Property(e => e.Freight) - .HasColumnType("money") - .HasDefaultValueSql("((0))"); - entity.Property(e => e.OrderDate).HasColumnType("datetime"); - entity.Property(e => e.RequiredDate).HasColumnType("datetime"); - entity.Property(e => e.ShipAddress).HasMaxLength(60); - entity.Property(e => e.ShipCity).HasMaxLength(15); - entity.Property(e => e.ShipCountry).HasMaxLength(15); - entity.Property(e => e.ShipName).HasMaxLength(40); - entity.Property(e => e.ShipPostalCode).HasMaxLength(10); - entity.Property(e => e.ShipRegion).HasMaxLength(15); - entity.Property(e => e.ShippedDate).HasColumnType("datetime"); - entity.HasOne(d => d.Customer) - .WithMany(p => p.Orders) - .HasForeignKey(d => d.CustomerId) - .HasConstraintName("FK_Orders_Customers"); - entity.HasOne(d => d.ShipViaNavigation) - .WithMany(p => p.Orders) - .HasForeignKey(d => d.ShipVia) - .HasConstraintName("FK_Orders_Shippers"); - }); - - modelBuilder.Entity(entity => { - entity.HasKey(e => new { e.OrderId, e.ProductId }) - .HasName("PK_Order_Details"); - entity.ToTable("OrderDetails"); - entity.HasIndex(e => e.OrderId, "OrderID"); - entity.HasIndex(e => e.OrderId, "OrdersOrder_Details"); - entity.HasIndex(e => e.ProductId, "ProductID"); - entity.HasIndex(e => e.ProductId, "ProductsOrder_Details"); - entity.Property(e => e.OrderId).HasColumnName("OrderID"); - entity.Property(e => e.ProductId).HasColumnName("ProductID"); - entity.Property(e => e.Quantity).HasDefaultValueSql("((1))"); - entity.Property(e => e.UnitPrice).HasColumnType("money"); - entity.HasOne(d => d.Order) - .WithMany(p => p.OrderDetails) - .HasForeignKey(d => d.OrderId) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("FK_Order_Details_Orders"); - entity.HasOne(d => d.Product) - .WithMany(p => p.OrderDetails) - .HasForeignKey(d => d.ProductId) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("FK_Order_Details_Products"); - }); - modelBuilder.Entity(entity => { - entity.HasIndex(e => e.CategoryId, "CategoriesProducts"); - entity.HasIndex(e => e.CategoryId, "CategoryID"); - entity.HasIndex(e => e.ProductName, "ProductName"); - entity.HasIndex(e => e.SupplierId, "SupplierID"); - entity.HasIndex(e => e.SupplierId, "SuppliersProducts"); - entity.Property(e => e.ProductId).HasColumnName("ProductID"); - entity.Property(e => e.CategoryId).HasColumnName("CategoryID"); - entity.Property(e => e.ProductName) - .IsRequired() - .HasMaxLength(40); - entity.Property(e => e.QuantityPerUnit).HasMaxLength(20); - entity.Property(e => e.ReorderLevel).HasDefaultValueSql("((0))"); - entity.Property(e => e.SupplierId).HasColumnName("SupplierID"); - entity.Property(e => e.UnitPrice) - .HasColumnType("money") - .HasDefaultValueSql("((0))"); - entity.Property(e => e.UnitsInStock).HasDefaultValueSql("((0))"); - entity.Property(e => e.UnitsOnOrder).HasDefaultValueSql("((0))"); - entity.HasOne(d => d.Category) - .WithMany(p => p.Products) - .HasForeignKey(d => d.CategoryId) - .HasConstraintName("FK_Products_Categories"); - entity.HasOne(d => d.Supplier) - .WithMany(p => p.Products) - .HasForeignKey(d => d.SupplierId) - .HasConstraintName("FK_Products_Suppliers"); - }); - modelBuilder.Entity(entity => { - entity.Property(e => e.ShipperId).HasColumnName("ShipperID"); - entity.Property(e => e.CompanyName) - .IsRequired() - .HasMaxLength(40); - entity.Property(e => e.Phone).HasMaxLength(24); - }); - modelBuilder.Entity(entity => { - entity.HasIndex(e => e.CompanyName, "CompanyName"); - entity.HasIndex(e => e.PostalCode, "PostalCode"); - entity.Property(e => e.SupplierId).HasColumnName("SupplierID"); - entity.Property(e => e.Address).HasMaxLength(60); - entity.Property(e => e.City).HasMaxLength(15); - entity.Property(e => e.CompanyName) - .IsRequired() - .HasMaxLength(40); - entity.Property(e => e.ContactName).HasMaxLength(30); - entity.Property(e => e.ContactTitle).HasMaxLength(30); - entity.Property(e => e.Country).HasMaxLength(15); - entity.Property(e => e.Fax).HasMaxLength(24); - entity.Property(e => e.HomePage).HasColumnType("ntext"); - entity.Property(e => e.Phone).HasMaxLength(24); - entity.Property(e => e.PostalCode).HasMaxLength(10); - entity.Property(e => e.Region).HasMaxLength(15); - }); - modelBuilder.Entity(entity => { - entity.HasIndex(e => e.EmployeeId, "EmployeeId"); - entity.HasIndex(e => e.LastName, "LastName"); - entity.HasIndex(e => e.FirstName, "FirstName"); - entity.HasIndex(e => e.Title, "Title"); - entity.HasIndex(e => e.BirthDate, "BirthDate"); - entity.HasIndex(e => e.HireDate, "HireDate"); - entity.HasIndex(e => e.Notes, "Notes"); - }); - OnModelCreatingPartial(modelBuilder); - } - - partial void OnModelCreatingPartial(ModelBuilder modelBuilder); - } -} diff --git a/CS/PivotTableBindToData/Northwind/Order.cs b/CS/PivotTableBindToData/Northwind/Order.cs deleted file mode 100644 index 3026c9d..0000000 --- a/CS/PivotTableBindToData/Northwind/Order.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; - -#nullable disable - -namespace PivotTableBindToData.Northwind { - public partial class Order { - public Order() { - OrderDetails = new HashSet(); - } - - public int OrderId { get; set; } - public string CustomerId { get; set; } - public int? EmployeeId { get; set; } - public DateTime? OrderDate { get; set; } - public DateTime? RequiredDate { get; set; } - public DateTime? ShippedDate { get; set; } - public int? ShipVia { get; set; } - public decimal? Freight { get; set; } - public string ShipName { get; set; } - public string ShipAddress { get; set; } - public string ShipCity { get; set; } - public string ShipRegion { get; set; } - public string ShipPostalCode { get; set; } - public string ShipCountry { get; set; } - - public virtual Customer Customer { get; set; } - public virtual Shipper ShipViaNavigation { get; set; } - public virtual ICollection OrderDetails { get; set; } - } -} diff --git a/CS/PivotTableBindToData/Northwind/OrderDetail.cs b/CS/PivotTableBindToData/Northwind/OrderDetail.cs deleted file mode 100644 index ec68afc..0000000 --- a/CS/PivotTableBindToData/Northwind/OrderDetail.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; - -#nullable disable - -namespace PivotTableBindToData.Northwind { - public partial class OrderDetail { - public int OrderId { get; set; } - public int ProductId { get; set; } - public decimal UnitPrice { get; set; } - public short Quantity { get; set; } - public float Discount { get; set; } - - public virtual Order Order { get; set; } - public virtual Product Product { get; set; } - } -} diff --git a/CS/PivotTableBindToData/Northwind/Product.cs b/CS/PivotTableBindToData/Northwind/Product.cs deleted file mode 100644 index 5867548..0000000 --- a/CS/PivotTableBindToData/Northwind/Product.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; - -#nullable disable - -namespace PivotTableBindToData.Northwind { - public partial class Product { - public Product() { - OrderDetails = new HashSet(); - } - - public int ProductId { get; set; } - public string ProductName { get; set; } - public int? SupplierId { get; set; } - public int? CategoryId { get; set; } - public string QuantityPerUnit { get; set; } - public decimal? UnitPrice { get; set; } - public short? UnitsInStock { get; set; } - public short? UnitsOnOrder { get; set; } - public short? ReorderLevel { get; set; } - public bool Discontinued { get; set; } - - public virtual Category Category { get; set; } - public virtual Supplier Supplier { get; set; } - public virtual ICollection OrderDetails { get; set; } - } -} diff --git a/CS/PivotTableBindToData/Northwind/Shipper.cs b/CS/PivotTableBindToData/Northwind/Shipper.cs deleted file mode 100644 index 9a924e6..0000000 --- a/CS/PivotTableBindToData/Northwind/Shipper.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; - -#nullable disable - -namespace PivotTableBindToData.Northwind { - public partial class Shipper { - public Shipper() { - Orders = new HashSet(); - } - - public int ShipperId { get; set; } - public string CompanyName { get; set; } - public string Phone { get; set; } - - public virtual ICollection Orders { get; set; } - } -} diff --git a/CS/PivotTableBindToData/Northwind/Supplier.cs b/CS/PivotTableBindToData/Northwind/Supplier.cs deleted file mode 100644 index e976378..0000000 --- a/CS/PivotTableBindToData/Northwind/Supplier.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; - -#nullable disable - -namespace PivotTableBindToData.Northwind { - public partial class Supplier { - public Supplier() { - Products = new HashSet(); - } - - public int SupplierId { get; set; } - public string CompanyName { get; set; } - public string ContactName { get; set; } - public string ContactTitle { get; set; } - public string Address { get; set; } - public string City { get; set; } - public string Region { get; set; } - public string PostalCode { get; set; } - public string Country { get; set; } - public string Phone { get; set; } - public string Fax { get; set; } - public string HomePage { get; set; } - - public virtual ICollection Products { get; set; } - } -} diff --git a/CS/PivotTableBindToData/Pages/Index.razor b/CS/PivotTableBindToData/Pages/Index.razor index 4c39476..1d9d1c2 100644 --- a/CS/PivotTableBindToData/Pages/Index.razor +++ b/CS/PivotTableBindToData/Pages/Index.razor @@ -1,59 +1,81 @@ @page "/" -@using PivotTableBindToData.Data -@using PivotTableBindToData.Northwind +@using PivotTableBindToData.SalesDb @using Microsoft.EntityFrameworkCore -@using System.Drawing; -@inject IDbContextFactory NorthwindContextFactory -@implements IDisposable +@using DevExpress.Data.Linq +@inject IDbContextFactory SalesContextFactory +@inject IConfiguration Configuration +@implements IAsyncDisposable @using DevExpress.Blazor.PivotTable -
- +@if (DataExists == true) { + - - - - + + + @($"Q{context.Text}") - - - - + + + - -
+} +else if (DataExists == false) { +
+

The Sales table (or the database file) was not found.

+ + @if (IsGenerating) { +

Generating 1,000,000 rows, please wait...

+ } +
+} @code { - NorthwindContext Northwind { get; set; } - IEnumerable Data { get; set; } - - protected override async Task OnInitializedAsync() { - Northwind = NorthwindContextFactory.CreateDbContext(); - - var products = await Northwind.Products.ToListAsync(); - var categories = await Northwind.Categories.ToListAsync(); - var orders = await Northwind.Orders.ToListAsync(); - var orderDetails = await Northwind.OrderDetails.ToListAsync(); - - Data = (from c in categories - join p in products on c.CategoryId equals p.CategoryId - join od in orderDetails on p.ProductId equals od.ProductId - select new PivotRow{ - CategoryName = c.CategoryName, - ProductName = p.ProductName, - UnitPrice = p.UnitPrice, - OrderDate = od.Order.OrderDate, - ShipCountry = od.Order.ShipCountry, - ShipCity = od.Order.ShipCity - }) - .ToList(); + SalesContext SalesContext { get; set; } + EntityServerModeSource DataSource { get; set; } + bool? DataExists { get; set; } + bool IsGenerating { get; set; } + + protected override async Task OnAfterRenderAsync(bool firstRender) { + if (!firstRender || SalesContext != null) + return; + + SalesContext = await SalesContextFactory.CreateDbContextAsync(); + SalesContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + + DataExists = await SalesDataGenerator.SalesTableExistsAsync(SalesContext); + if (DataExists == true) + CreateDataSource(); + + StateHasChanged(); + } + + async Task GenerateDataAsync() { + IsGenerating = true; + StateHasChanged(); + + await Task.Run(() => SalesDataGenerator.GenerateAsync(SalesContext, Configuration.GetValue("DataProvider", DataProviders.SQLite.ToString()))); + + CreateDataSource(); + DataExists = true; + IsGenerating = false; + StateHasChanged(); + } + + void CreateDataSource() { + DataSource = new EntityServerModeSource { + QueryableSource = SalesContext.Sales, + KeyExpression = nameof(Sale.SaleId) + }; } - public void Dispose() { - Northwind?.Dispose(); + public async ValueTask DisposeAsync() { + DataSource?.Dispose(); + if (SalesContext != null) + await SalesContext.DisposeAsync(); } } diff --git a/CS/PivotTableBindToData/PivotTableBindToData.csproj b/CS/PivotTableBindToData/PivotTableBindToData.csproj index 0af5f86..a7a6f3f 100644 --- a/CS/PivotTableBindToData/PivotTableBindToData.csproj +++ b/CS/PivotTableBindToData/PivotTableBindToData.csproj @@ -1,24 +1,29 @@ - + - net8.0 + net10.0 disable enable - - - - - - + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/CS/PivotTableBindToData/Program.cs b/CS/PivotTableBindToData/Program.cs index 0ecacb5..2d2f5c8 100644 --- a/CS/PivotTableBindToData/Program.cs +++ b/CS/PivotTableBindToData/Program.cs @@ -1,8 +1,5 @@ -using PivotTableBindToData.Data; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Web; -using Microsoft.EntityFrameworkCore; -using PivotTableBindToData.Northwind; +using Microsoft.EntityFrameworkCore; +using PivotTableBindToData.SalesDb; var builder = WebApplication.CreateBuilder(args); @@ -10,12 +7,22 @@ builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); builder.Services.AddDevExpressBlazor(); -builder.Services.AddSingleton(); -builder.Services.AddDbContextFactory((sp, options) => { - var env = sp.GetRequiredService(); - var dbPath = Path.Combine(env.ContentRootPath, "Northwind.db"); - options.UseSqlite("Data Source=" + dbPath); -}); + + +var dataProviderSetting = builder.Configuration.GetValue("DataProvider", DataProviders.SQLite.ToString()); + +if (dataProviderSetting == DataProviders.SQLite.ToString()) { + builder.Services.AddDbContextFactory((sp, options) => { + var env = sp.GetRequiredService(); + var dbPath = Path.Combine(env.ContentRootPath, "sales.db"); + options.UseSqlite("Data Source=" + dbPath); + }); +} +else if (dataProviderSetting == DataProviders.SqlServer.ToString()) { + builder.Services.AddDbContextFactory(options => { + options.UseSqlServer(builder.Configuration.GetConnectionString("SalesDatabase")); + }); +} var app = builder.Build(); diff --git a/CS/PivotTableBindToData/SalesDb/DataProviders.cs b/CS/PivotTableBindToData/SalesDb/DataProviders.cs new file mode 100644 index 0000000..04a6755 --- /dev/null +++ b/CS/PivotTableBindToData/SalesDb/DataProviders.cs @@ -0,0 +1,6 @@ +namespace PivotTableBindToData.SalesDb { + public enum DataProviders { + SQLite, + SqlServer + } +} diff --git a/CS/PivotTableBindToData/SalesDb/Sale.cs b/CS/PivotTableBindToData/SalesDb/Sale.cs new file mode 100644 index 0000000..cc78f5e --- /dev/null +++ b/CS/PivotTableBindToData/SalesDb/Sale.cs @@ -0,0 +1,13 @@ +#nullable disable + +namespace PivotTableBindToData.SalesDb { + public partial class Sale { + public int SaleId { get; set; } + public int StoreNumber { get; set; } + public string StoreName { get; set; } + public DateTime? SaleDate { get; set; } + public int? Year { get; set; } + public int? Quarter { get; set; } + public decimal? Amount { get; set; } + } +} diff --git a/CS/PivotTableBindToData/SalesDb/SalesContext.cs b/CS/PivotTableBindToData/SalesDb/SalesContext.cs new file mode 100644 index 0000000..7aca169 --- /dev/null +++ b/CS/PivotTableBindToData/SalesDb/SalesContext.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore; + +#nullable disable + +namespace PivotTableBindToData.SalesDb { + public partial class SalesContext : DbContext { + + public SalesContext(DbContextOptions options) + : base(options) { + } + + public virtual DbSet Sales { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) { + OnModelCreatingPartial(modelBuilder); + } + + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); + } +} diff --git a/CS/PivotTableBindToData/SalesDb/SalesDataGenerator.cs b/CS/PivotTableBindToData/SalesDb/SalesDataGenerator.cs new file mode 100644 index 0000000..301583c --- /dev/null +++ b/CS/PivotTableBindToData/SalesDb/SalesDataGenerator.cs @@ -0,0 +1,49 @@ +using System.Reflection; +using System.Text.RegularExpressions; +using Microsoft.EntityFrameworkCore; + +#nullable disable + +namespace PivotTableBindToData.SalesDb { + public static class SalesDataGenerator { + + public static async Task SalesTableExistsAsync(SalesContext context) { + try { + await context.Sales.Select(s => s.SaleId).Take(1).ToListAsync(); + return true; + } + catch { + return false; + } + } + + public static async Task GenerateAsync(SalesContext context, string dataProvider) { + string resourceName = dataProvider switch { + nameof(DataProviders.SQLite) => "PivotTableBindToData.SalesDb.Scripts.SQLiteDbGenerator.sql", + nameof(DataProviders.SqlServer) => "PivotTableBindToData.SalesDb.Scripts.SqlServerDbGenerator.sql", + _ => "" + }; + + string script = await ReadEmbeddedScriptAsync(resourceName); + + var batches = Regex.Split(script, @"^\s*GO\s*$", RegexOptions.Multiline | RegexOptions.IgnoreCase) + .Select(batch => batch.Trim()) + .Where(batch => batch.Length > 0); + + context.Database.SetCommandTimeout(0); + + foreach (var batch in batches) + await context.Database.ExecuteSqlRawAsync(batch); + } + + static async Task ReadEmbeddedScriptAsync(string resourceName) { + var assembly = Assembly.GetExecutingAssembly(); + using var stream = assembly.GetManifestResourceStream(resourceName); + if (stream == null) + throw new InvalidOperationException($"Embedded resource '{resourceName}' was not found."); + + using var reader = new StreamReader(stream); + return await reader.ReadToEndAsync(); + } + } +} diff --git a/CS/PivotTableBindToData/SalesDb/Scripts/SQLiteDbGenerator.sql b/CS/PivotTableBindToData/SalesDb/Scripts/SQLiteDbGenerator.sql new file mode 100644 index 0000000..9873b68 --- /dev/null +++ b/CS/PivotTableBindToData/SalesDb/Scripts/SQLiteDbGenerator.sql @@ -0,0 +1,209 @@ +DROP TABLE IF EXISTS Sales; + +CREATE TABLE Sales ( + SaleID INTEGER PRIMARY KEY AUTOINCREMENT, + StoreNumber INTEGER NOT NULL, + SaleDate TEXT NOT NULL, + Year INTEGER GENERATED ALWAYS AS (CAST(strftime('%Y', SaleDate) AS INTEGER)) STORED, + Quarter INTEGER GENERATED ALWAYS AS ((CAST(strftime('%m', SaleDate) AS INTEGER) + 2) / 3) STORED, + Amount REAL NOT NULL, + StoreName TEXT NULL +); + +BEGIN TRANSACTION; + +WITH RECURSIVE +Dates(SaleDate) AS ( + VALUES('2007-01-01') + UNION ALL + SELECT date(SaleDate, '+1 day') + FROM Dates + WHERE SaleDate < '2025-12-31' +), +Stores(StoreNumber) AS ( + VALUES(1) + UNION ALL + SELECT StoreNumber + 1 + FROM Stores + WHERE StoreNumber < 122 +), +DayActivity AS ( + SELECT SaleDate, abs(random()) % 121 + 2 AS NumActiveStores + FROM Dates +), +RandomizedStores AS ( + SELECT + d.SaleDate, + s.StoreNumber, + ROW_NUMBER() OVER (PARTITION BY d.SaleDate ORDER BY random()) AS rn + FROM DayActivity d + CROSS JOIN Stores s +), +ActiveStores AS ( + SELECT + rs.SaleDate, + rs.StoreNumber, + abs(random()) % 4 + 1 AS TransCount + FROM RandomizedStores rs + JOIN DayActivity d ON d.SaleDate = rs.SaleDate + WHERE rs.rn <= d.NumActiveStores +), +Transactions(SaleDate, StoreNumber, TxNo, TransCount) AS ( + SELECT SaleDate, StoreNumber, 1, TransCount + FROM ActiveStores + UNION ALL + SELECT SaleDate, StoreNumber, TxNo + 1, TransCount + FROM Transactions + WHERE TxNo < TransCount +) +INSERT INTO Sales (StoreNumber, SaleDate, Amount) +SELECT + StoreNumber, + SaleDate, + ROUND(15.0 + ((abs(random()) / 9223372036854775807.0) * (950.0 - 15.0)), 2) AS Amount +FROM Transactions; + +COMMIT; + +WITH StoreNames(StoreNumber, StoreName) AS ( + VALUES + (1, 'Contoso Canberra Store'), + (2, 'Contoso Asia Online Store'), + (3, 'Contoso Asia Reseller'), + (4, 'Contoso Yerevan Store'), + (5, 'Contoso Baildon Store'), + (6, 'Contoso Carlisle Store'), + (7, 'Contoso Cheshire Store'), + (8, 'Contoso Edinburgh Store'), + (9, 'Contoso Glasgow Store'), + (10, 'Contoso Knotty Ash Store'), + (11, 'Contoso Lancashire Store'), + (12, 'Contoso Leeds Store'), + (13, 'Contoso Liverpool Store'), + (14, 'Contoso Manchester Store'), + (15, 'Contoso Northampton Store'), + (16, 'Contoso West Yorkshire Store'), + (17, 'Contoso York Store'), + (18, 'Contoso Baumholder Store'), + (19, 'Contoso Berlin Store'), + (20, 'Contoso Dusseldorf Store'), + (21, 'Contoso Giebelstadt Store'), + (22, 'Contoso Hofheim Store'), + (23, 'Contoso Landstuhl Store'), + (24, 'Contoso Minden Store'), + (25, 'Contoso Munich Store'), + (26, 'Contoso Catalog Store'), + (27, 'Contoso Athens Store'), + (28, 'Contoso Copenhagen Store'), + (29, 'Contoso Europe Online Store'), + (30, 'Contoso Europe Reseller'), + (31, 'Contoso Kolkata Store'), + (32, 'Contoso Mumbai Store'), + (33, 'Contoso New Delhi Store'), + (34, 'Contoso Dublin Store'), + (35, 'Contoso Madrid Store'), + (36, 'Contoso Firenze Store'), + (37, 'Contoso Milan Store'), + (38, 'Contoso Roma Store'), + (39, 'Contoso Torino Store'), + (40, 'Contoso Venezia Store'), + (41, 'Contoso Calgary Store'), + (42, 'Contoso Bishkek Store'), + (43, 'Contoso Valletta Store'), + (44, 'Contoso Amsterdam Store'), + (45, 'Contoso Warsaw Store'), + (46, 'Contoso Lisbon Store'), + (47, 'Contoso Bucharest Store'), + (48, 'Contoso North America Online Store'), + (49, 'Contoso North America Reseller'), + (50, 'Contoso Singapore Store'), + (51, 'Contoso Ljubljana Store'), + (52, 'Contoso Alexandria Store'), + (53, 'Contoso Attleboro Store'), + (54, 'Contoso Austin Store'), + (55, 'Contoso Bacliff Store'), + (56, 'Contoso Bangor Store'), + (57, 'Contoso Bellevue Store'), + (58, 'Contoso Boston Store'), + (59, 'Contoso Buffalo Store'), + (60, 'Contoso Cambridge Store'), + (61, 'Contoso Cape May Store'), + (62, 'Contoso Cedar Park Store'), + (63, 'Contoso Charlottesville Store'), + (64, 'Contoso Dallas Store'), + (65, 'Contoso Edgerton Store'), + (66, 'Contoso Fall City Store'), + (67, 'Contoso Fond du Lac Store'), + (68, 'Contoso Fort Collins Store'), + (69, 'Contoso Fort Worth Store'), + (70, 'Contoso Fredericksburg Store'), + (71, 'Contoso Germantown Store'), + (72, 'Contoso Grand Prairie Store'), + (73, 'Contoso Haverhill Store'), + (74, 'Contoso Holyoke Store'), + (75, 'Contoso Humble Store'), + (76, 'Contoso Jacksonville Store'), + (77, 'Contoso Kennebunkport Store'), + (78, 'Contoso Key West Store'), + (79, 'Contoso Lafayette Store'), + (80, 'Contoso Leominster Store'), + (81, 'Contoso Lewisville Store'), + (82, 'Contoso Littleton Store'), + (83, 'Contoso Loveland Store'), + (84, 'Contoso Martinsville Store'), + (85, 'Contoso Montclair Store'), + (86, 'Contoso Nantucket Store'), + (87, 'Contoso New Brunswick Store'), + (88, 'Contoso New Haven Store'), + (89, 'Contoso Norfolk Store'), + (90, 'Contoso North Harford Store'), + (91, 'Contoso Orlando Store'), + (92, 'Contoso Oshkosh Store'), + (93, 'Contoso Parker Store'), + (94, 'Contoso Pasadena Store'), + (95, 'Contoso Paterson Store'), + (96, 'Contoso Poestenkill Store'), + (97, 'Contoso Provincetown Store'), + (98, 'Contoso Queens Store'), + (99, 'Contoso Redmond Store'), + (100, 'Contoso Richardson Store'), + (101, 'Contoso Roanoke Store'), + (102, 'Contoso Sheboygan Store'), + (103, 'Contoso South Portland Store'), + (104, 'Contoso Spring Store'), + (105, 'Contoso Sunnyside Store'), + (106, 'Contoso Thornton Store'), + (107, 'Contoso Virginia Beach Store'), + (108, 'Contoso Waterbury Store'), + (109, 'Contoso Wheat Ridge Store'), + (110, 'Contoso Yakima Store'), + (111, 'Contoso Lyon Store'), + (112, 'Contoso Marseille Store'), + (113, 'Contoso Nice Store'), + (114, 'Contoso Strasbourg Store'), + (115, 'Contoso Toulouse Store'), + (116, 'Contoso Berne Store'), + (117, 'Contoso Stockholm Store'), + (118, 'Contoso Busan Store'), + (119, 'Contoso Seoul Store'), + (120, 'Contoso Kyoto Store'), + (121, 'Contoso Sapporo Store'), + (122, 'Contoso Yokohama Store') +) +UPDATE Sales +SET StoreName = ( + SELECT sn.StoreName + FROM StoreNames sn + WHERE sn.StoreNumber = Sales.StoreNumber +) +WHERE StoreNumber IN (SELECT StoreNumber FROM StoreNames); + + + +-- Indexes covering the grouping/aggregation used by the pivot table +-- (Row: StoreName, Columns: Year/Quarter, Data: Amount, Count: SaleID). +CREATE INDEX IX_Sales_Pivot_Full ON Sales (StoreName, Year, Quarter, Amount, SaleID); +CREATE INDEX IX_Sales_SaleDate ON Sales (SaleDate, StoreName, Amount, SaleID); +CREATE INDEX IX_Sales_Year_Quarter ON Sales (Year, Quarter, StoreName, Amount, SaleID); + +SELECT 'Data generation successfully completed!'; diff --git a/CS/PivotTableBindToData/SalesDb/Scripts/SqlServerDbGenerator.sql b/CS/PivotTableBindToData/SalesDb/Scripts/SqlServerDbGenerator.sql new file mode 100644 index 0000000..ffe98c9 --- /dev/null +++ b/CS/PivotTableBindToData/SalesDb/Scripts/SqlServerDbGenerator.sql @@ -0,0 +1,222 @@ +IF OBJECT_ID('dbo.Sales', 'U') IS NOT NULL + DROP TABLE dbo.Sales; +GO + +CREATE TABLE Sales ( + SaleID INT IDENTITY(1,1) PRIMARY KEY, + StoreNumber INT NOT NULL, + SaleDate DATE NOT NULL, + [Year] AS (datepart(year,CONVERT([date],[SaleDate]))) PERSISTED, + [Quarter] AS (datepart(quarter,CONVERT([date],[SaleDate]))) PERSISTED, + Amount DECIMAL(18, 2) NOT NULL, + [StoreName] [nvarchar](100) NULL +); + +SET NOCOUNT ON; + +-- 1. Prepare base date variables (from 2007-01-01 to 2025-12-31) +DECLARE @CurrentDate DATE = '2007-01-01'; +DECLARE @EndDate DATE = '2025-12-31'; + +-- 2. Create a temporary table with a list of all 122 stores to speed up querying +IF OBJECT_ID('tempdb..#Stores') IS NOT NULL DROP TABLE #Stores; +CREATE TABLE #Stores (StoreNumber INT); + +DECLARE @i INT = 1; +WHILE @i <= 122 +BEGIN + INSERT INTO #Stores (StoreNumber) VALUES (@i); + SET @i = @i + 1; +END + +-- Open a transaction to significantly speed up bulk inserts (executemany) +BEGIN TRAN; + +-- 3. Loop through days +WHILE @CurrentDate <= @EndDate +BEGIN + -- Random number of active stores on this day: from 2 to 122 + DECLARE @NumActiveStores INT = ABS(CHECKSUM(NEWID())) % 121 + 2; + + -- Bulk generate and insert data for the current day + INSERT INTO Sales (StoreNumber, SaleDate, Amount) + SELECT + s.StoreNumber, + @CurrentDate, + -- Generate a receipt amount from $15.00 to $950.00. + -- RAND(CHECKSUM(NEWID())) is used to recalculate the value for each row + ROUND(15.0 + (RAND(CHECKSUM(NEWID())) * (950.0 - 15.0)), 2) AS Amount + FROM ( + -- Select a random sample of stores (random.sample) + SELECT TOP (@NumActiveStores) StoreNumber + FROM #Stores + ORDER BY NEWID() + ) s + CROSS APPLY ( + -- Generate from 1 to 4 transactions for each selected store (random.randint) + SELECT TOP (ABS(CHECKSUM(NEWID())) % 4 + 1) 1 AS TransCount + FROM (VALUES (1), (2), (3), (4)) AS t(N) + ) trans; + + SET @CurrentDate = DATEADD(DAY, 1, @CurrentDate); +END + +COMMIT TRAN; + +DROP TABLE #Stores; + +update [Sales] set [StoreName] = 'Contoso Canberra Store' where [StoreNumber] = 1 +update [Sales] set [StoreName] = 'Contoso Asia Online Store' where [StoreNumber] = 2 +update [Sales] set [StoreName] = 'Contoso Asia Reseller' where [StoreNumber] = 3 +update [Sales] set [StoreName] = 'Contoso Yerevan Store' where [StoreNumber] = 4 +update [Sales] set [StoreName] = 'Contoso Baildon Store' where [StoreNumber] = 5 +update [Sales] set [StoreName] = 'Contoso Carlisle Store' where [StoreNumber] = 6 +update [Sales] set [StoreName] = 'Contoso Cheshire Store' where [StoreNumber] = 7 +update [Sales] set [StoreName] = 'Contoso Edinburgh Store' where [StoreNumber] = 8 +update [Sales] set [StoreName] = 'Contoso Glasgow Store' where [StoreNumber] = 9 +update [Sales] set [StoreName] = 'Contoso Knotty Ash Store' where [StoreNumber] = 10 +update [Sales] set [StoreName] = 'Contoso Lancashire Store' where [StoreNumber] = 11 +update [Sales] set [StoreName] = 'Contoso Leeds Store' where [StoreNumber] = 12 +update [Sales] set [StoreName] = 'Contoso Liverpool Store' where [StoreNumber] = 13 +update [Sales] set [StoreName] = 'Contoso Manchester Store' where [StoreNumber] = 14 +update [Sales] set [StoreName] = 'Contoso Northampton Store' where [StoreNumber] = 15 +update [Sales] set [StoreName] = 'Contoso West Yorkshire Store' where [StoreNumber] = 16 +update [Sales] set [StoreName] = 'Contoso York Store' where [StoreNumber] = 17 +update [Sales] set [StoreName] = 'Contoso Baumholder Store' where [StoreNumber] = 18 +update [Sales] set [StoreName] = 'Contoso Berlin Store' where [StoreNumber] = 19 +update [Sales] set [StoreName] = 'Contoso Dusseldorf Store' where [StoreNumber] = 20 +update [Sales] set [StoreName] = 'Contoso Giebelstadt Store' where [StoreNumber] = 21 +update [Sales] set [StoreName] = 'Contoso Hofheim Store' where [StoreNumber] = 22 +update [Sales] set [StoreName] = 'Contoso Landstuhl Store' where [StoreNumber] = 23 +update [Sales] set [StoreName] = 'Contoso Minden Store' where [StoreNumber] = 24 +update [Sales] set [StoreName] = 'Contoso Munich Store' where [StoreNumber] = 25 +update [Sales] set [StoreName] = 'Contoso Catalog Store' where [StoreNumber] = 26 +update [Sales] set [StoreName] = 'Contoso Athens Store' where [StoreNumber] = 27 +update [Sales] set [StoreName] = 'Contoso Copenhagen Store' where [StoreNumber] = 28 +update [Sales] set [StoreName] = 'Contoso Europe Online Store' where [StoreNumber] = 29 +update [Sales] set [StoreName] = 'Contoso Europe Reseller' where [StoreNumber] = 30 +update [Sales] set [StoreName] = 'Contoso Kolkata Store' where [StoreNumber] = 31 +update [Sales] set [StoreName] = 'Contoso Mumbai Store' where [StoreNumber] = 32 +update [Sales] set [StoreName] = 'Contoso New Delhi Store' where [StoreNumber] = 33 +update [Sales] set [StoreName] = 'Contoso Dublin Store' where [StoreNumber] = 34 +update [Sales] set [StoreName] = 'Contoso Madrid Store' where [StoreNumber] = 35 +update [Sales] set [StoreName] = 'Contoso Firenze Store' where [StoreNumber] = 36 +update [Sales] set [StoreName] = 'Contoso Milan Store' where [StoreNumber] = 37 +update [Sales] set [StoreName] = 'Contoso Roma Store' where [StoreNumber] = 38 +update [Sales] set [StoreName] = 'Contoso Torino Store' where [StoreNumber] = 39 +update [Sales] set [StoreName] = 'Contoso Venezia Store' where [StoreNumber] = 40 +update [Sales] set [StoreName] = 'Contoso Calgary Store' where [StoreNumber] = 41 +update [Sales] set [StoreName] = 'Contoso Bishkek Store' where [StoreNumber] = 42 +update [Sales] set [StoreName] = 'Contoso Valletta Store' where [StoreNumber] = 43 +update [Sales] set [StoreName] = 'Contoso Amsterdam Store' where [StoreNumber] = 44 +update [Sales] set [StoreName] = 'Contoso Warsaw Store' where [StoreNumber] = 45 +update [Sales] set [StoreName] = 'Contoso Lisbon Store' where [StoreNumber] = 46 +update [Sales] set [StoreName] = 'Contoso Bucharest Store' where [StoreNumber] = 47 +update [Sales] set [StoreName] = 'Contoso North America Online Store' where [StoreNumber] = 48 +update [Sales] set [StoreName] = 'Contoso North America Reseller' where [StoreNumber] = 49 +update [Sales] set [StoreName] = 'Contoso Singapore Store' where [StoreNumber] = 50 +update [Sales] set [StoreName] = 'Contoso Ljubljana Store' where [StoreNumber] = 51 +update [Sales] set [StoreName] = 'Contoso Alexandria Store' where [StoreNumber] = 52 +update [Sales] set [StoreName] = 'Contoso Attleboro Store' where [StoreNumber] = 53 +update [Sales] set [StoreName] = 'Contoso Austin Store' where [StoreNumber] = 54 +update [Sales] set [StoreName] = 'Contoso Bacliff Store' where [StoreNumber] = 55 +update [Sales] set [StoreName] = 'Contoso Bangor Store' where [StoreNumber] = 56 +update [Sales] set [StoreName] = 'Contoso Bellevue Store' where [StoreNumber] = 57 +update [Sales] set [StoreName] = 'Contoso Boston Store' where [StoreNumber] = 58 +update [Sales] set [StoreName] = 'Contoso Buffalo Store' where [StoreNumber] = 59 +update [Sales] set [StoreName] = 'Contoso Cambridge Store' where [StoreNumber] = 60 +update [Sales] set [StoreName] = 'Contoso Cape May Store' where [StoreNumber] = 61 +update [Sales] set [StoreName] = 'Contoso Cedar Park Store' where [StoreNumber] = 62 +update [Sales] set [StoreName] = 'Contoso Charlottesville Store' where [StoreNumber] = 63 +update [Sales] set [StoreName] = 'Contoso Dallas Store' where [StoreNumber] = 64 +update [Sales] set [StoreName] = 'Contoso Edgerton Store' where [StoreNumber] = 65 +update [Sales] set [StoreName] = 'Contoso Fall City Store' where [StoreNumber] = 66 +update [Sales] set [StoreName] = 'Contoso Fond du Lac Store' where [StoreNumber] = 67 +update [Sales] set [StoreName] = 'Contoso Fort Collins Store' where [StoreNumber] = 68 +update [Sales] set [StoreName] = 'Contoso Fort Worth Store' where [StoreNumber] = 69 +update [Sales] set [StoreName] = 'Contoso Fredericksburg Store' where [StoreNumber] = 70 +update [Sales] set [StoreName] = 'Contoso Germantown Store' where [StoreNumber] = 71 +update [Sales] set [StoreName] = 'Contoso Grand Prairie Store' where [StoreNumber] = 72 +update [Sales] set [StoreName] = 'Contoso Haverhill Store' where [StoreNumber] = 73 +update [Sales] set [StoreName] = 'Contoso Holyoke Store' where [StoreNumber] = 74 +update [Sales] set [StoreName] = 'Contoso Humble Store' where [StoreNumber] = 75 +update [Sales] set [StoreName] = 'Contoso Jacksonville Store' where [StoreNumber] = 76 +update [Sales] set [StoreName] = 'Contoso Kennebunkport Store' where [StoreNumber] = 77 +update [Sales] set [StoreName] = 'Contoso Key West Store' where [StoreNumber] = 78 +update [Sales] set [StoreName] = 'Contoso Lafayette Store' where [StoreNumber] = 79 +update [Sales] set [StoreName] = 'Contoso Leominster Store' where [StoreNumber] = 80 +update [Sales] set [StoreName] = 'Contoso Lewisville Store' where [StoreNumber] = 81 +update [Sales] set [StoreName] = 'Contoso Littleton Store' where [StoreNumber] = 82 +update [Sales] set [StoreName] = 'Contoso Loveland Store' where [StoreNumber] = 83 +update [Sales] set [StoreName] = 'Contoso Martinsville Store' where [StoreNumber] = 84 +update [Sales] set [StoreName] = 'Contoso Montclair Store' where [StoreNumber] = 85 +update [Sales] set [StoreName] = 'Contoso Nantucket Store' where [StoreNumber] = 86 +update [Sales] set [StoreName] = 'Contoso New Brunswick Store' where [StoreNumber] = 87 +update [Sales] set [StoreName] = 'Contoso New Haven Store' where [StoreNumber] = 88 +update [Sales] set [StoreName] = 'Contoso Norfolk Store' where [StoreNumber] = 89 +update [Sales] set [StoreName] = 'Contoso North Harford Store' where [StoreNumber] = 90 +update [Sales] set [StoreName] = 'Contoso Orlando Store' where [StoreNumber] = 91 +update [Sales] set [StoreName] = 'Contoso Oshkosh Store' where [StoreNumber] = 92 +update [Sales] set [StoreName] = 'Contoso Parker Store' where [StoreNumber] = 93 +update [Sales] set [StoreName] = 'Contoso Pasadena Store' where [StoreNumber] = 94 +update [Sales] set [StoreName] = 'Contoso Paterson Store' where [StoreNumber] = 95 +update [Sales] set [StoreName] = 'Contoso Poestenkill Store' where [StoreNumber] = 96 +update [Sales] set [StoreName] = 'Contoso Provincetown Store' where [StoreNumber] = 97 +update [Sales] set [StoreName] = 'Contoso Queens Store' where [StoreNumber] = 98 +update [Sales] set [StoreName] = 'Contoso Redmond Store' where [StoreNumber] = 99 +update [Sales] set [StoreName] = 'Contoso Richardson Store' where [StoreNumber] = 100 +update [Sales] set [StoreName] = 'Contoso Roanoke Store' where [StoreNumber] = 101 +update [Sales] set [StoreName] = 'Contoso Sheboygan Store' where [StoreNumber] = 102 +update [Sales] set [StoreName] = 'Contoso South Portland Store' where [StoreNumber] = 103 +update [Sales] set [StoreName] = 'Contoso Spring Store' where [StoreNumber] = 104 +update [Sales] set [StoreName] = 'Contoso Sunnyside Store' where [StoreNumber] = 105 +update [Sales] set [StoreName] = 'Contoso Thornton Store' where [StoreNumber] = 106 +update [Sales] set [StoreName] = 'Contoso Virginia Beach Store' where [StoreNumber] = 107 +update [Sales] set [StoreName] = 'Contoso Waterbury Store' where [StoreNumber] = 108 +update [Sales] set [StoreName] = 'Contoso Wheat Ridge Store' where [StoreNumber] = 109 +update [Sales] set [StoreName] = 'Contoso Yakima Store' where [StoreNumber] = 110 +update [Sales] set [StoreName] = 'Contoso Lyon Store' where [StoreNumber] = 111 +update [Sales] set [StoreName] = 'Contoso Marseille Store' where [StoreNumber] = 112 +update [Sales] set [StoreName] = 'Contoso Nice Store' where [StoreNumber] = 113 +update [Sales] set [StoreName] = 'Contoso Strasbourg Store' where [StoreNumber] = 114 +update [Sales] set [StoreName] = 'Contoso Toulouse Store' where [StoreNumber] = 115 +update [Sales] set [StoreName] = 'Contoso Berne Store' where [StoreNumber] = 116 +update [Sales] set [StoreName] = 'Contoso Stockholm Store' where [StoreNumber] = 117 +update [Sales] set [StoreName] = 'Contoso Busan Store' where [StoreNumber] = 118 +update [Sales] set [StoreName] = 'Contoso Seoul Store' where [StoreNumber] = 119 +update [Sales] set [StoreName] = 'Contoso Kyoto Store' where [StoreNumber] = 120 +update [Sales] set [StoreName] = 'Contoso Sapporo Store' where [StoreNumber] = 121 +update [Sales] set [StoreName] = 'Contoso Yokohama Store' where [StoreNumber] = 122 + + + +-- Indexes covering the grouping/aggregation used by the pivot table +-- (Row: StoreName, Columns: Year/Quarter, Data: Amount, Count: SaleID). +CREATE NONCLUSTERED INDEX [IX_Sales_Pivot_Full] ON [dbo].[Sales] +( + [StoreName] ASC, + [Year] ASC, + [Quarter] ASC +) +INCLUDE ([Amount], [SaleID]) +WITH (PAD_INDEX = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON); +GO + +CREATE NONCLUSTERED INDEX [IX_Sales_SaleDate] ON [dbo].[Sales] +( + [SaleDate] ASC +) +INCLUDE ([StoreName], [Amount], [SaleID]) +WITH (PAD_INDEX = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON); +GO + +CREATE NONCLUSTERED INDEX [IX_Sales_Year_Quarter] ON [dbo].[Sales] +( + [Year] ASC, + [Quarter] ASC +) +INCLUDE ([StoreName], [Amount], [SaleID]) +WITH (PAD_INDEX = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON); +GO + +PRINT 'Data generation successfully completed!'; diff --git a/CS/PivotTableBindToData/appsettings.json b/CS/PivotTableBindToData/appsettings.json index 10f68b8..a8cfe78 100644 --- a/CS/PivotTableBindToData/appsettings.json +++ b/CS/PivotTableBindToData/appsettings.json @@ -1,4 +1,8 @@ { + "DataProvider": "SQLite", // SQLite or SqlServer + "ConnectionStrings": { + "SalesDatabase": "Server=(localdb)\\MSSQLLocalDB;Database=Example;Trusted_Connection=True;" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/README.md b/README.md index 4a9576e..d058106 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,12 @@ This example uses [Entity Framework Core](https://learn.microsoft.com/en-us/ef/c - [Index.razor](./CS/PivotTableBindToData/Pages/Index.razor) - [Program.cs](./CS/PivotTableBindToData/Program.cs) +- [SalesDataGenerator.cs](./CS/PivotTableBindToData/SalesDb/SalesDataGenerator.cs) + +If the `Sales` table (or the database file) is not found, the page displays a **Generate data** +button instead of the Pivot Table. After confirmation, the app runs the SQL script that matches +the configured data provider (SQLite or SQL Server) to create the table and populate it with +1,000,000 sample rows. ## Documentation