🚀 Blazor Server quick-start template demonstrating Syncfusion
PivotTablewith complete CRUD operations, Entity Framework Core, SQL Server LocalDB, and real-time data synchronization — includes NORTHWND sample database, layered architecture, and production-ready configuration for enterprise business intelligence applications.
📺 Official Demo: https://blazor.syncfusion.com/demos/pivot-table/overview?theme=fluent2
📚 Official Documentation: https://ej2.syncfusion.com/blazor/documentation/pivot-table/getting-started
- 🔍 Overview
- ✨ Key Features
- 📋 Prerequisites
- 🧭 Quick Start
- 🗂️ Project Structure
- 🧱 Architecture & Data Flow
- ⚙️ Configuration & Customization
- 💡 Usage Examples
- 🔧 CRUD Operations
- 🧪 Testing
- ❓ Troubleshooting & FAQ
- 🤝 Contributing
- 📜 License & Support
This repository provides a production-ready starter template for integrating Syncfusion's powerful PivotTable component with Entity Framework Core and SQL Server in Blazor Server applications. It demonstrates a complete end-to-end solution for building data-driven business intelligence dashboards with full database CRUD capabilities.
The sample includes:
- ✅ Interactive Pivot Table with CRUD operations (Create, Read, Update, Delete)
- ✅ Entity Framework Core for type-safe database access
- ✅ SQL Server LocalDB with NORTHWND sample database
- ✅ Real-time data synchronization between UI and database
- ✅ Layered architecture with proper separation of concerns
- ✅ RESTful API controller for data operations
- ✅ Inline cell editing with immediate database persistence
- ✅ Pre-configured connection strings and database context
- 📈 Business analysts building custom reporting and analytics tools
- 💼 Enterprise developers creating data-driven dashboards
- 🎓 .NET developers learning Syncfusion Blazor integration
- 🛠️ Teams building ERP, CRM, or business intelligence systems
- 🏢 Organizations migrating from ASP.NET to Blazor Server
-
❌ Problem: Complex pivot table implementations require extensive custom code
-
✅ Solution: Ready-to-use Syncfusion PivotTable with minimal configuration
-
❌ Problem: Database operations in pivot tables are difficult to implement
-
✅ Solution: Complete CRUD operations with Entity Framework Core integration
-
❌ Problem: Real-time synchronization between UI and database is challenging
-
✅ Solution: Event-driven architecture with
EditCompletedevent handlers
| Feature | Description | Benefit |
|---|---|---|
| 🎯 Inline CRUD Operations | Edit, add, and delete records directly in pivot table cells | Streamlined data entry without separate forms |
| 📊 Entity Framework Core | Type-safe database access with LINQ queries | Compile-time validation and reduced runtime errors |
| 💾 SQL Server LocalDB | Embedded database with NORTHWND sample data | Zero-configuration development environment |
| 🔄 Real-Time Sync | Immediate database updates on user edits | Consistent data state across application |
| 🏗️ Layered Architecture | Clear separation: UI → Controller → DAL → DbContext | Maintainable and testable codebase |
| 📡 RESTful API | HTTP endpoints for CRUD operations | Scalable for microservices and API clients |
| 🎨 Virtual Scrolling | Efficient rendering for large datasets | Handles 10,000+ records smoothly |
| 🔒 Transaction Safety | EF Core change tracking and SaveChanges | Data integrity and rollback support |
| 📈 Multi-Dimensional Analysis | Rows, columns, values, and filters configuration | Flexible business intelligence reporting |
| 🧮 Data Formatting | Currency, date, and number format settings | Professional data presentation |
Ensure you have the following installed on your system:
- Visual Studio 2022 (17.0 or higher) — Download
- Workload: ASP.NET and web development
- Workload: .NET desktop development
- .NET SDK 7.0 or higher — Download
- SQL Server LocalDB (included with Visual Studio) — Standalone Download
- Git (for cloning repository) — Download
- ✅ Windows 10/11 (LocalDB requirement)
- ✅ Windows Server 2019/2022
⚠️ Linux/macOS (requires SQL Server Express/full edition)
- ✅ Chrome (Latest)
- ✅ Firefox (Latest)
- ✅ Edge (Latest)
- ✅ Safari (Latest)
git clone https://github.com/SyncfusionExamples/Blazor-PivotTable-CRUD-with-EntityFramework
cd Blazor-PivotTable-CRUD-with-EntityFramework- Navigate to
MyBlazorAppfolder - Open
MyBlazorApp.slnin Visual Studio 2022 - Wait for NuGet package restore to complete automatically
The project is pre-configured to use SQL Server LocalDB with an attached NORTHWND database file:
// Located in: Data/OrderContext.cs
optionsBuilder.UseSqlServer(@"Data Source=(LocalDB)\MSSQLLocalDB;
AttachDbFilename=" + Environment.CurrentDirectory + @"\App_Data\NORTHWND.MDF;
Integrated Security=True");Note: The NORTHWND.MDF file must exist in MyBlazorApp\App_Data\ directory.
- Press
F5or click Debug → Start Debugging - The browser will automatically open to
https://localhost:7XXX
cd MyBlazorApp\MyBlazorApp
dotnet build
dotnet runYou should see an interactive pivot table displaying:
- Rows: CustomerID, OrderDate
- Columns: ShipName
- Values: Freight (with N2 number format)
Test CRUD Operations:
- Edit: Double-click any cell to modify the value
- Add: Right-click to add new records
- Delete: Right-click to remove records
- Verify: Changes persist immediately to the database
Blazor-PivotTable-CRUD-with-EntityFramework/
├── MyBlazorApp/
│ ├── MyBlazorApp.sln # Visual Studio solution file
│ └── MyBlazorApp/
│ ├── MyBlazorApp.csproj # Project dependencies & configuration
│ ├── Program.cs # Application entry point & service registration
│ ├── appsettings.json # Configuration settings
│ ├── _Imports.razor # Global Razor component imports
│ ├── App.razor # Root Blazor component
│ │
│ ├── App_Data/
│ │ └── NORTHWND.MDF # SQL Server LocalDB database file
│ │
│ ├── Pages/
│ │ ├── Index.razor # Main pivot table page with CRUD logic
│ │ ├── _Host.cshtml # Blazor Server host page
│ │ ├── Error.cshtml # Error handling page
│ │ └── HttpClient.razor # Alternative HTTP-based CRUD example
│ │
│ ├── Controllers/
│ │ └── PivotController.cs # RESTful API endpoints for CRUD operations
│ │
│ ├── Data/
│ │ ├── Orders.cs # Entity model for Orders table
│ │ ├── OrderContext.cs # EF Core DbContext configuration
│ │ └── OrderDataAccessLayer.cs # Data access layer with CRUD methods
│ │
│ ├── Shared/
│ │ ├── MainLayout.razor # Application layout template
│ │ └── NavMenu.razor # Navigation menu component
│ │
│ ├── wwwroot/
│ │ ├── css/ # Stylesheets & Syncfusion themes
│ │ ├── images/ # Static images
│ │ └── scripts/ # JavaScript libraries (lodash)
│ │
│ └── Properties/
│ └── launchSettings.json # Development server configuration
│
└── README.md # This file
Pages/Index.razor— Main pivot table component with inline CRUD event handling viaEditCompletedeventControllers/PivotController.cs— RESTful API withGet,Update,Add, andDeleteendpoints for HTTP-based operationsData/OrderDataAccessLayer.cs— Business logic layer withGetAllOrders(),AddOrder(),UpdateOrder(),DeleteOrder()methodsData/OrderContext.cs— Entity Framework DbContext with SQL Server connection configurationData/Orders.cs— Entity model representing Orders table structureMyBlazorApp.csproj— NuGet package references for Syncfusion, EF Core, and System.Linq.Dynamic.Core
┌─────────────────────────────────────────────────────────────┐
│ Presentation Layer │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Index.razor (SfPivotView Component) │ │
│ │ - User interactions (edit, add, delete) │ │
│ │ - EditCompleted event handler │ │
│ └───────────────────────────────────────────────────────┘ │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ API/Controller Layer │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ PivotController.cs (Optional HTTP API) │ │
│ │ - GET: Fetch data │ │
│ │ - POST /Update: Update records │ │
│ │ - POST /Add: Create records │ │
│ │ - POST /Delete: Remove records │ │
│ └───────────────────────────────────────────────────────┘ │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ Business Logic Layer (DAL) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ OrderDataAccessLayer.cs │ │
│ │ - GetAllOrders(): Fetch all orders │ │
│ │ - AddOrder(Order): Create new order │ │
│ │ - UpdateOrder(Order): Modify existing order │ │
│ │ - DeleteOrder(int id): Remove order │ │
│ └───────────────────────────────────────────────────────┘ │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ Data Access Layer (EF Core) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ OrderContext.cs (DbContext) │ │
│ │ - DbSet<Order> Orders │ │
│ │ - Connection string configuration │ │
│ │ - Change tracking & SaveChanges() │ │
│ └───────────────────────────────────────────────────────┘ │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ Database Layer │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ SQL Server LocalDB │ │
│ │ - NORTHWND.MDF database │ │
│ │ - Orders table │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Index.razorrendersSfPivotViewcomponent- Component calls
Orders.GetAllOrders()via dependency injection OrderDataAccessLayerqueriesOrderContext.OrdersDbSet- Entity Framework executes SQL query against LocalDB
- Results returned as
IQueryable<Order>and displayed in pivot table
- User double-clicks cell and modifies value
EditCompletedevent fires withEditCompletedEventArgs<Order>- Event handler extracts modified records from
args.ModifiedData - For each modified order, calls
Orders.UpdateOrder(order) - DAL updates entity state to
EntityState.Modified SaveChanges()commits transaction to database- UI automatically refreshes to reflect changes
- User right-clicks and selects "Add New Record"
EditCompletedevent fires with new records inargs.AddedData- Event handler calls
Orders.AddOrder(order)for each record - DAL adds entity to
db.OrdersDbSet SaveChanges()inserts new row into database
- User right-clicks record and selects "Delete"
EditCompletedevent fires with records inargs.RemovedData- Event handler calls
Orders.DeleteOrder(order.OrderID) - DAL finds entity by ID, removes from DbSet
SaveChanges()executes DELETE SQL statement
Option 1: LocalDB (Default)
// OrderContext.cs
optionsBuilder.UseSqlServer(@"Data Source=(LocalDB)\MSSQLLocalDB;
AttachDbFilename=" + Environment.CurrentDirectory + @"\App_Data\NORTHWND.MDF;
Integrated Security=True");Option 2: Full SQL Server Instance
optionsBuilder.UseSqlServer(@"Server=YOUR_SERVER_NAME;
Database=NORTHWND;
User Id=YOUR_USERNAME;
Password=YOUR_PASSWORD;
TrustServerCertificate=True");Option 3: Azure SQL Database
optionsBuilder.UseSqlServer(@"Server=tcp:yourserver.database.windows.net,1433;
Database=NORTHWND;
User ID=yourusername@yourserver;
Password=yourpassword;
Encrypt=True;
TrustServerCertificate=False;
Connection Timeout=30");<SfPivotView TValue="Order"
Width="100%"
Height="600"
EnableVirtualization="true" <!-- For large datasets -->
ShowFieldList="true" <!-- Show/hide field list -->
ShowTooltip="true" <!-- Enable tooltips -->
AllowConditionalFormatting="true"> <!-- Conditional formatting -->
<PivotViewDataSourceSettings TValue="Order"
ExpandAll="false" <!-- Auto-expand rows -->
DataSource="@Orders.GetAllOrders()">
<!-- Define Rows (Hierarchical) -->
<PivotViewColumns>
<PivotViewColumn Name="ShipName"></PivotViewColumn>
<PivotViewColumn Name="ShipCountry"></PivotViewColumn>
</PivotViewColumns>
<!-- Define Columns -->
<PivotViewRows>
<PivotViewRow Name="CustomerID" ExpandAll="true"></PivotViewRow>
<PivotViewRow Name="OrderDate"></PivotViewRow>
</PivotViewRows>
<!-- Define Aggregated Values -->
<PivotViewValues>
<PivotViewValue Name="Freight" Caption="Total Freight"></PivotViewValue>
<PivotViewValue Name="OrderID" Caption="Order Count" Type="Count"></PivotViewValue>
</PivotViewValues>
<!-- Data Formatting -->
<PivotViewFormatSettings>
<PivotViewFormatSetting Name="Freight" Format="C2"></PivotViewFormatSetting>
<PivotViewFormatSetting Name="OrderDate" Format="dd/MM/yyyy"></PivotViewFormatSetting>
</PivotViewFormatSettings>
</PivotViewDataSourceSettings>
<!-- Enable CRUD Operations -->
<PivotViewCellEditSettings AllowEditing="true"
AllowAdding="true"
AllowDeleting="true"
Mode="EditMode.Normal">
</PivotViewCellEditSettings>
<!-- Event Handlers -->
<PivotViewEvents TValue="Order"
EditCompleted="EditCompleted"
OnLoad="OnLoad"
DataBound="OnDataBound">
</PivotViewEvents>
</SfPivotView>| Format Code | Example Output | Use Case |
|---|---|---|
C0 |
$1,234 | Currency without decimals |
C2 |
$1,234.56 | Currency with 2 decimals |
N0 |
1,234 | General number |
N2 |
1,234.56 | Decimal numbers |
P0 |
50% | Percentage |
dd/MM/yyyy |
15/04/2026 | Date format |
MMM dd, yyyy |
Apr 15, 2026 | Long date format |
@page "/"
@using MyBlazorApp.Data
@using Syncfusion.Blazor.PivotView
@inject OrderDataAccessLayer Orders
<SfPivotView TValue="Order" Height="400">
<PivotViewDataSourceSettings TValue="Order"
DataSource="@Orders.GetAllOrders()">
<PivotViewColumns>
<PivotViewColumn Name="ShipName"></PivotViewColumn>
</PivotViewColumns>
<PivotViewRows>
<PivotViewRow Name="CustomerID"></PivotViewRow>
</PivotViewRows>
<PivotViewValues>
<PivotViewValue Name="Freight"></PivotViewValue>
</PivotViewValues>
</PivotViewDataSourceSettings>
<PivotViewCellEditSettings AllowEditing="true"
AllowAdding="true"
AllowDeleting="true">
</PivotViewCellEditSettings>
<PivotViewEvents TValue="Order" EditCompleted="EditCompleted">
</PivotViewEvents>
</SfPivotView>
@code {
private void EditCompleted(EditCompletedEventArgs<Order> args)
{
if (args.ModifiedData?.Count > 0)
{
foreach (var key in args.ModifiedData.Keys)
{
Orders.UpdateOrder(args.ModifiedData[key]);
}
}
if (args.RemovedData?.Count > 0)
{
foreach (int key in args.RemovedData.Keys)
{
Orders.DeleteOrder(args.RemovedData[key].OrderID);
}
}
if (args.AddedData?.Count > 0)
{
foreach (Order order in args.AddedData)
{
Orders.AddOrder(order);
}
}
}
}@page "/httpclient"
@using Syncfusion.Blazor.PivotView
@using System.Net.Http.Json
@inject HttpClient Http
<SfPivotView TValue="Order" Height="400">
<PivotViewDataSourceSettings TValue="Order" Url="api/Pivot">
<!-- Same configuration as Example 1 -->
</PivotViewDataSourceSettings>
<PivotViewCellEditSettings AllowEditing="true"
AllowAdding="true"
AllowDeleting="true">
</PivotViewCellEditSettings>
<PivotViewEvents TValue="Order" EditCompleted="OnEditCompleted">
</PivotViewEvents>
</SfPivotView>
@code {
private async Task OnEditCompleted(EditCompletedEventArgs<Order> args)
{
if (args.ModifiedData?.Count > 0)
{
await Http.PostAsJsonAsync("api/Pivot/Update", args.ModifiedData);
}
if (args.AddedData?.Count > 0)
{
var addedDict = args.AddedData.Select((order, index) =>
new KeyValuePair<int, Order>(index, order)).ToDictionary(x => x.Key, x => x.Value);
await Http.PostAsJsonAsync("api/Pivot/Add", addedDict);
}
if (args.RemovedData?.Count > 0)
{
await Http.PostAsJsonAsync("api/Pivot/Delete", args.RemovedData);
}
}
}// OrderDataAccessLayer.cs - Add custom query methods
public IEnumerable<Order> GetOrdersByCustomer(string customerId)
{
try
{
return db.Orders.Where(o => o.CustomerID == customerId).ToList();
}
catch
{
throw;
}
}
public decimal GetTotalFreightByCustomer(string customerId)
{
try
{
return db.Orders
.Where(o => o.CustomerID == customerId)
.Sum(o => o.Freight);
}
catch
{
throw;
}
}UI Interaction:
- Right-click on any cell in the pivot table
- Select "Add New Record" from context menu
- Fill in the new record details in the dialog
- Click "Add" button
Code Flow:
// Index.razor - EditCompleted event handler
if (args.AddedData?.Count > 0)
{
foreach (Order order in args.AddedData)
{
Orders.AddOrder(order); // Calls DAL method
}
}
// OrderDataAccessLayer.cs
public void AddOrder(Order Order)
{
try
{
db.Orders.Add(Order); // Add to EF context
db.SaveChanges(); // Persist to database
}
catch
{
throw;
}
}UI Interaction:
- Data loads automatically when page renders
Code Flow:
// Index.razor - Data binding
<PivotViewDataSourceSettings TValue="Order"
DataSource="@Orders.GetAllOrders()">
// OrderDataAccessLayer.cs
public DbSet<Order> GetAllOrders()
{
try
{
return db.Orders; // Returns IQueryable for deferred execution
}
catch
{
throw;
}
}UI Interaction:
- Double-click on any cell to enter edit mode
- Modify the value
- Press Enter or click outside the cell
Code Flow:
// Index.razor - EditCompleted event handler
if (args.ModifiedData?.Count > 0)
{
foreach (var key in args.ModifiedData.Keys)
{
Orders.UpdateOrder(args.ModifiedData[key]);
}
}
// OrderDataAccessLayer.cs
public void UpdateOrder(Order Order)
{
try
{
// Detach local instance if exists (prevents tracking conflicts)
var local = db.Set<Order>().Local.FirstOrDefault(
entry => entry.OrderID.Equals(Order.OrderID));
if (local != null)
{
db.Entry(local).State = EntityState.Detached;
}
db.Entry(Order).State = EntityState.Modified;
db.SaveChanges();
}
catch
{
throw;
}
}UI Interaction:
- Right-click on a row
- Select "Delete" from context menu
- Confirm deletion in dialog
Code Flow:
// Index.razor - EditCompleted event handler
if (args.RemovedData?.Count > 0)
{
foreach (int key in args.RemovedData.Keys)
{
Orders.DeleteOrder(args.RemovedData[key].OrderID);
}
}
// OrderDataAccessLayer.cs
public void DeleteOrder(int? id)
{
try
{
Order Order = GetOrderData(id);
if (Order != null)
{
db.Orders.Remove(Order);
db.SaveChanges();
}
}
catch
{
throw;
}
}-
Data Display
- Pivot table renders with NORTHWND data
- All columns and rows are visible
- Freight values display with N2 format
- OrderDate displays as dd/MM/yyyy
-
Edit Operation
- Double-click cell to enter edit mode
- Modify Freight value and press Enter
- Refresh browser and verify change persists
-
Add Operation
- Right-click and select "Add New Record"
- Fill required fields (CustomerID, OrderDate, Freight, ShipName)
- Verify new record appears in pivot table
- Refresh browser and verify record persists
-
Delete Operation
- Right-click on a row and select "Delete"
- Confirm deletion
- Verify record disappears from pivot table
- Refresh browser and verify record is permanently deleted
Check data directly in SQL Server:
-- Connect to (LocalDB)\MSSQLLocalDB
USE NORTHWND;
-- View all orders
SELECT TOP 10 * FROM Orders ORDER BY OrderID DESC;
-- Check recent modifications
SELECT * FROM Orders WHERE OrderID = <YourTestOrderID>;
-- Verify deletions
SELECT COUNT(*) FROM Orders;Solution:
# Ensure LocalDB is installed
sqllocaldb info
# Create instance if missing
sqllocaldb create MSSQLLocalDB
# Start instance
sqllocaldb start MSSQLLocalDB
# Verify NORTHWND.MDF exists in App_Data folder
dir MyBlazorApp\App_Data\NORTHWND.MDFSolution:
# Restore NuGet packages
dotnet restore
# Or in Visual Studio: Right-click Solution → Restore NuGet PackagesSolution:
<!-- Ensure AllowEditing is true -->
<PivotViewCellEditSettings AllowEditing="true"
AllowAdding="true"
AllowDeleting="true">
</PivotViewCellEditSettings>
<!-- Verify event handler is registered -->
<PivotViewEvents TValue="Order" EditCompleted="EditCompleted">
</PivotViewEvents>Solution:
// OrderDataAccessLayer.cs - Add detach logic before update
var local = db.Set<Order>().Local.FirstOrDefault(
entry => entry.OrderID.Equals(Order.OrderID));
if (local != null)
{
db.Entry(local).State = EntityState.Detached;
}Q: Can I use a different database instead of NORTHWND?
A: Yes, modify OrderContext.cs connection string and update Orders.cs entity model to match your table schema.
Q: How do I deploy this to production?
A: Replace LocalDB connection string with full SQL Server instance, publish via Visual Studio or dotnet publish, and configure IIS/Azure App Service.
Q: Can I add authentication/authorization?
A: Yes, implement ASP.NET Core Identity and add [Authorize] attributes to PivotController and pages.
Q: How do I handle large datasets (100K+ rows)?
A: Enable server-side processing by implementing virtual scrolling, pagination, and asynchronous data loading with IQueryable.
Q: Can I export pivot table data to Excel/PDF?
A: Yes, Syncfusion PivotTable supports export functionality. Add ExcelExport and PDFExport services to Inject services.
We welcome contributions! Here's how you can help:
- Fork the repository on GitHub
- Clone your fork locally:
git clone https://github.com/SyncfusionExamples/Blazor-PivotTable-CRUD-with-EntityFramework
- Create a feature branch:
git checkout -b feature/amazing-feature
- Make your changes following coding standards:
- Use C# naming conventions (PascalCase for classes, camelCase for parameters)
- Add XML documentation comments for public methods
- Include unit tests for new features
- Commit your changes:
git commit -m "Add amazing feature: detailed description" - Push to your branch:
git push origin feature/amazing-feature
- Open a Pull Request with detailed description
# Clone repository
git clone https://github.com/YourUsername/Blazor-PivotTable-CRUD-with-EntityFramework.git
cd Blazor-PivotTable-CRUD-with-EntityFramework/MyBlazorApp
# Restore packages
dotnet restore
# Build solution
dotnet build
# Run application
cd MyBlazorApp
dotnet run- Follow Microsoft C# Coding Conventions
- Use 4 spaces for indentation (not tabs)
- Add comments for complex logic
- Keep methods under 50 lines when possible
This project is provided as-is for educational and commercial use. Syncfusion components require a valid license for production deployment. See Syncfusion Licensing for details.
- 📚 Syncfusion Blazor Documentation: https://ej2.syncfusion.com/blazor/documentation/
- 📺 Video Tutorials: Syncfusion YouTube Channel
- 💬 Community Forum: Syncfusion Blazor Forum
- 🐛 Report Issues: GitHub Issues
- 📧 Contact: support@syncfusion.com
- 🔗 Entity Framework Core Documentation
- 📖 Blazor Server Documentation
- 🎯 SQL Server LocalDB
- 🧑💻 Syncfusion Blazor Samples