Build a Blazor Server CRUD Application with EF Core and DB2 for i (IBM i)
Introduction
This tutorial shows how to create a Blazor Server application connected to a DB2 for i database via the NTi Entity Framework Core extension, compatible with .NET 8, .NET 9 and .NET 10.
The goal is to implement a full CRUD to manage products, categories and orders from a modern .NET web application.
This approach lets you use EF Core with IBM i to simplify data access and speed up business application development.
The commands in this tutorial target .NET 8: for .NET 9 or .NET 10, simply replace the framework version and the package versions listed in step 1. On the server side, the NTi EF Core extension requires IBM i 7.2 or later.
Step 1 - Create and configure the project
Create the project from the command line:
dotnet new blazor -n myApp -f net8.0 -int Server
cd myApp
💡 The
blazorservertemplate no longer exists as of .NET 8. Theblazortemplate with the-int Serveroption (Server interactivity) creates a Blazor Web App with equivalent behavior, whose pages live inComponents/Pages. For .NET 9 or .NET 10, replacenet8.0withnet9.0ornet10.0.
Add the required packages:
dotnet add package Aumerial.Data.Nti
dotnet add package Aumerial.EntityFrameworkCore --version 8.5.0
dotnet add package Microsoft.EntityFrameworkCore.Design --version 8.0.*
Pick the Aumerial.EntityFrameworkCore version matching the EF Core version you target: the major version follows EF Core, the minor version is the NTi generation.
| Target | Aumerial.EntityFrameworkCore | Microsoft EF Core packages |
|---|---|---|
| .NET 8 (EF Core 8) | 8.5.0 |
8.0.* |
| .NET 9 (EF Core 9) | 9.5.0 |
9.0.* |
| .NET 10 (EF Core 10) | 10.5.0 |
10.0.* |
💡 All three versions rely on the
Aumerial.Data.Nti5.0.0 ADO.NET provider, installed automatically as a dependency if you skip the first command.
Add the connection string to appsettings.json, specifying the default schema (the library) in which all created entities will be placed:
{
"ConnectionStrings": {
"DefaultConnection": "server=Server;user=User;password=Pwd;database=Db;pooling=true"
}
}
💡 Connection pooling is off by default with NTi 5:
pooling=trueis recommended for any web application.
Step 2 - Define the entities
Create a Models folder and add the following classes.
Category.cs
A category can contain multiple products:
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection Products { get; set; }
}
Product.cs
A product belongs to a category and can be linked to multiple orders:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public int StockQuantity { get; set; }
public decimal Weight { get; set; }
public bool IsAvailable { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
public ICollection Orders { get; set; } = new List();
}
Order.cs
An order can contain multiple products:
public class Order
{
public int Id { get; set; }
public DateTime OrderDate { get; set; }
public DateTime? DeliveryDate { get; set; }
public decimal TotalAmount { get; set; }
public ICollection Products { get; set; }
} Step 3 - Configure the DbContext
Add an AppDbContext class inheriting from DbContext to manage the entities and their relationships:
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions options) : base(options) { }
public DbSet Products { get; set; }
public DbSet Categories { get; set; }
public DbSet Orders { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
} Step 4 - Configure Program.cs
Register the DbContext in Program.cs as a service via dependency injection for your Blazor components. Add using Aumerial.EntityFrameworkCore; at the top of the file to make the UseNTi method available:
using Aumerial.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext(options =>
options.UseNTi(connectionString));
💡
AddDbContextis recommended for most Blazor Server applications. TheDbContextis instantiated with a Scoped lifetime, recreated on each user request. UseAddDbContextFactoryfor background tasks or multi-threaded processing (see the Microsoft documentation).
Step 5 - Create and manage migrations
Generate an initial migration to create the tables in the database. This command creates a file in the Migrations folder containing the SQL instructions to create your tables:
dotnet ef migrations add InitialCreate
Then apply the migration:
dotnet ef database update

To add or modify a table, create or update the relevant entity, add it to AppDbContext if needed, then generate a new migration:
dotnet ef migrations add MyNewMigration
dotnet ef database update
To remove the last migration before it is applied:
dotnet ef migrations remove
To roll back to a previous database version:
dotnet ef database update MigrationName
Replace
MigrationNamewith the name of the migration you want to apply or revert to.
Step 6 - Add initial seed data
Add seed data in Program.cs, after var app = builder.Build();:
using (var scope = app.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService();
var categories = new List
{
new Category { Name = "Electronics" },
new Category { Name = "Books" },
new Category { Name = "Home Appliances" },
new Category { Name = "Fashion" },
new Category { Name = "Toys" }
};
context.Categories.AddRange(categories);
var products = new List
{
new Product { Name = "Smartphone", Price = 500, StockQuantity = 10, Category = categories[0], IsAvailable = true },
new Product { Name = "Laptop", Price = 1200, StockQuantity = 5, Category = categories[0], IsAvailable = true },
new Product { Name = "Washing Machine", Price = 300, StockQuantity = 8, Category = categories[2], IsAvailable = true },
new Product { Name = "T-Shirt", Price = 20, StockQuantity = 50, Category = categories[3], IsAvailable = true },
new Product { Name = "Children's Book", Price = 15, StockQuantity = 100, Category = categories[1], IsAvailable = true },
new Product { Name = "Toy Car", Price = 30, StockQuantity = 20, Category = categories[4], IsAvailable = true },
new Product { Name = "Microwave Oven", Price = 250, StockQuantity = 6, Category = categories[2], IsAvailable = true },
new Product { Name = "Jeans", Price = 40, StockQuantity = 30, Category = categories[3], IsAvailable = true }
};
context.Products.AddRange(products);
var orders = new List
{
new Order
{
OrderDate = DateTime.Now.AddDays(-10),
DeliveryDate = DateTime.Now.AddDays(-7),
TotalAmount = 750,
Products = new List { products[0], products[1], products[3] }
},
new Order
{
OrderDate = DateTime.Now.AddDays(-5),
DeliveryDate = DateTime.Now.AddDays(-3),
TotalAmount = 600,
Products = new List { products[4], products[5], products[6] }
},
new Order
{
OrderDate = DateTime.Now.AddDays(-2),
DeliveryDate = null,
TotalAmount = 290,
Products = new List { products[2], products[7] }
}
};
context.Orders.AddRange(orders);
await context.SaveChangesAsync();
}
💡
SaveChangesAsyncis the normal path with NTi: async is real end to end, with no sync-over-async. The synchronousSaveChangesremains available.
Step 7 - Generate the CRUD pages
Visual Studio can automatically generate Razor CRUD components for each of your entities in just a few clicks.
- Right-click on the Components/Pages folder of your project
- Select Add > New Scaffolded Item > Razor Components using Entity Framework (CRUD)
- Configure the options:
- Model Class: select the desired entity (e.g.
Product) - DbContext Class: select
AppDbContext
- Model Class: select the desired entity (e.g.
Visual Studio automatically generates a set of Razor CRUD components in a dedicated folder (e.g. Components/Pages/ProductPages):
Index.razor- records listCreate.razor- add formEdit.razor- edit formDetails.razor- record detail viewDelete.razor- delete confirmation
Repeat the operation for each entity: Category, Order.
Step 8 - Add an image field (BLOB)
Add an Image field of type byte[] to the Product entity:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public int StockQuantity { get; set; }
public decimal Weight { get; set; }
public bool IsAvailable { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
public ICollection Orders { get; set; } = new List();
[Column(TypeName = "BLOB(1M)"), DataType(DataType.Upload)]
public byte[] Image { get; set; }
}
Generate and apply the migration:
dotnet ef migrations add AddProductImage
dotnet ef database update
Then update the Components/Pages/ProductPages/Create.razor and Edit.razor components to add the upload field:
@if (Product?.Image != null && Product.Image.Length > 0)
{
Current image:
}
else
{
No image available
}
And the method to handle the upload:
private async Task UploadFile(InputFileChangeEventArgs e)
{
var file = e.File;
if (file != null)
{
using var memoryStream = new MemoryStream();
await file.OpenReadStream().CopyToAsync(memoryStream);
Product.Image = memoryStream.ToArray();
}
}

What's next?
- Entity Framework Core - configuration, data types and migrations
- Identity Authentication - protect the application with ASP.NET Core Identity stored in DB2 for i
- Connection - connection string, pooling, MFA
- Quick Start Guide - first connection and first IBM i calls