EF CORE - SETTING UP EF CORE WITH SQLITE
ENTITY FRAMEWORK CORE (EF CORE) — PART 1: SETTING UP EF CORE WITH SQLITE
After completing PART 0, this tutorial verifies .NET 8, creates an ASP.NET Core Razor Pages project named dotnetef, configures EF Core to use Ubuntu's system SQLite library, and creates Data/products.db.
1. Verify .NET 8 and prepare the EF tool
dotnet --version
dotnet --list-sdks
The SDK version should begin with 8. Install the EF Core 8 CLI tool:
dotnet tool install --global dotnet-ef --version "8.*"
If it is already installed, update it:
dotnet tool update --global dotnet-ef --version "8.*"
Verify:
dotnet ef --version
2. Create the Razor Pages project
Open a terminal in the directory where the project should be created, then run:
mkdir -p ~/dotnetprojects
cd ~/dotnetprojects
dotnet new webapp -n dotnetef --framework net8.0
Enter the new project directory:
cd dotnetef
The directory should contain:
dotnetef.csproj
Program.cs
Pages/
wwwroot/
Restore and build the newly generated application:
dotnet restore
dotnet build
Optionally run it before making database changes:
dotnet run
Open the address shown in the terminal and confirm that the default Home and Privacy pages load. Stop the server with Ctrl+C.
3. Understand the starting template
The webapp template already provides the Razor Pages foundation required for this series:
Pages/Index.cshtmlfor HomePages/Privacy.cshtmlfor PrivacyPages/Shared/_Layout.cshtmlfor the shared interface- Bootstrap and client files under
wwwroot - Razor Pages registration in
Program.cs
Do not run dotnet new web for this series. The webapp template is used because it creates a Razor Pages project immediately.
4. What is Entity Framework Core?
Entity Framework Core is an object-relational mapper for .NET. It allows an application to work with database rows through C# objects.
| Application concept | Database concept |
|---|---|
| C# model class | Table |
| Model object | Row |
| Object property | Column |
DbContext | Database session |
DbSet<Product> | Products table |
| Migration | Recorded schema change |
5. Why use SQLite?
SQLite stores the database in a local file and does not require a separate database server. It is useful for learning, demonstrations, prototypes and small applications.
The database created in this tutorial will be:
Data/products.db
6. Install EF Core using the system SQLite library
Use matching explicit EF Core versions. This example uses 8.0.30:
dotnet add package Microsoft.EntityFrameworkCore.Sqlite.Core \
--version 8.0.30
dotnet add package Microsoft.EntityFrameworkCore.Design \
--version 8.0.30
dotnet add package SQLitePCLRaw.bundle_sqlite3 \
--version 2.1.11
The packages have different responsibilities:
| Package | Purpose |
|---|---|
Microsoft.EntityFrameworkCore.Sqlite.Core | EF Core SQLite provider without bundled native SQLite |
Microsoft.EntityFrameworkCore.Design | Supports migrations and design-time tools |
SQLitePCLRaw.bundle_sqlite3 | Loads Ubuntu's system library named sqlite3 |
Do not add Microsoft.EntityFrameworkCore.Sqlite on an older Ubuntu installation affected by this glibc mismatch. It can bring in bundled e_sqlite3, which fails when the required glibc version is unavailable.
7. Verify the selected packages
dotnet list package
dotnet list package --include-transitive | grep -i sqlite
The list should contain Sqlite.Core and bundle_sqlite3. It should not contain:
SQLitePCLRaw.bundle_e_sqlite3
8. Create the model and data directories
On Linux, macOS or PowerShell:
mkdir -p Models Data
On Windows Command Prompt:
mkdir Models
mkdir Data
9. Create the Product model
Create Models/Product.cs:
using System.ComponentModel.DataAnnotations;
namespace dotnetef.Models;
public class Product
{
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; } = "";
[StringLength(500)]
public string? Description { get; set; }
[Range(0.01, 1000000)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
[Display(Name = "Stock Quantity")]
[Range(0, 1000000)]
public int StockQuantity { get; set; }
[Display(Name = "Created At")]
[DataType(DataType.DateTime)]
public DateTime CreatedAt { get; set; }
= DateTime.UtcNow;
}
10. Understand the Product model
| Property | Database role |
|---|---|
Id | Primary key generated for each product |
Name | Required product name, limited to 100 characters |
Description | Optional description |
Price | Required positive monetary value |
StockQuantity | Non-negative inventory count |
CreatedAt | UTC creation timestamp |
Data annotations serve two purposes: they describe validation rules and help EF Core construct the database schema.
SQLite can store decimal values, but some comparison and ordering operations have provider limitations. This does not prevent the basic CRUD operations in this series.
11. Create the database context
Create Data/ProductDbContext.cs:
using dotnetef.Models;
using Microsoft.EntityFrameworkCore;
namespace dotnetef.Data;
public class ProductDbContext : DbContext
{
public ProductDbContext(
DbContextOptions<ProductDbContext> options)
: base(options)
{
}
public DbSet<Product> Products => Set<Product>();
}
12. Understand ProductDbContext
The context inherits from EF Core's DbContext:
public class ProductDbContext : DbContext
Configuration is supplied through dependency injection:
DbContextOptions<ProductDbContext> options
This property represents the Products table:
public DbSet<Product> Products => Set<Product>();
13. Add the SQLite connection string
Open appsettings.json and use:
{
"ConnectionStrings": {
"ProductDatabase": "Data Source=Data/products.db"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
The connection string means:
Data Source=Data/products.db
SQLite will use the file products.db inside the existing Data directory.
14. Register and initialize SQLite in Program.cs
Add these namespaces at the top:
using dotnetef.Data;
using Microsoft.EntityFrameworkCore;
Initialize the system SQLite provider before creating the application builder:
SQLitePCL.Batteries_V2.Init();
Register the context after creating the builder:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddDbContext<ProductDbContext>(options =>
options.UseSqlite(
builder.Configuration.GetConnectionString(
"ProductDatabase"
)
)
);
15. Use the complete Program.cs
using dotnetef.Data;
using Microsoft.EntityFrameworkCore;
SQLitePCL.Batteries_V2.Init();
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddDbContext<ProductDbContext>(options =>
options.UseSqlite(
builder.Configuration.GetConnectionString(
"ProductDatabase"
)
)
);
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
The newly generated dotnetef project does not contain the custom NotFound page from the earlier tutorial series, so MapFallbackToPage() is not used here.
16. Clean and build before migration
dotnet clean
rm -rf bin obj
dotnet restore
dotnet build
Resolve any compilation error before generating a migration.
17. Create the first migration
dotnet ef migrations add CreateProductDatabase \
--context ProductDbContext \
--output-dir Data/Migrations/Product
The migration records the instructions needed to create the Products table. It does not yet create products.db.
The project should now contain files similar to:
Data/
├── Migrations/
│ └── Product/
│ ├── ..._CreateProductDatabase.cs
│ ├── ..._CreateProductDatabase.Designer.cs
│ └── ProductDbContextModelSnapshot.cs
└── ProductDbContext.cs
18. Create the SQLite database
dotnet ef database update \
--context ProductDbContext
EF Core applies the migration and creates:
Data/products.db
19. List the migrations
dotnet ef migrations list \
--context ProductDbContext
The output should include:
CreateProductDatabase
20. Inspect the SQLite database
If the SQLite command-line program is installed, run:
sqlite3 Data/products.db ".tables"
The output should include:
Products __EFMigrationsHistory
Inspect the Products table definition:
sqlite3 Data/products.db ".schema Products"
Alternatively, open Data/products.db using a graphical SQLite database browser.
21. Understand the migration history table
EF Core creates:
__EFMigrationsHistory
This internal table records which migrations have already been applied. EF Core uses it to avoid applying the same migration more than once.
22. Test the application
dotnet run
The default Home and Privacy pages should continue to work. The database exists, but no Product page has been created yet.
23. Current project structure
dotnetef/
├── Data/
│ ├── Migrations/Product/
│ ├── ProductDbContext.cs
│ └── products.db
├── Models/
│ └── Product.cs
├── Pages/
├── wwwroot/
├── appsettings.json
├── dotnetef.csproj
└── Program.cs
24. Common problems
| Problem | Check |
|---|---|
dotnet ef is unavailable | Install or update the global dotnet-ef tool |
| Package incompatibility | Ensure EF Core major version 8 is installed |
sqlite3 is unavailable | Install sqlite3 libsqlite3-0 libsqlite3-dev |
GLIBC_2.33 not found | Use Sqlite.Core and bundle_sqlite3, not bundled e_sqlite3 |
| SQLite provider is not initialized | Add SQLitePCL.Batteries_V2.Init() before creating the builder |
| Unable to open database file | Ensure the Data directory exists |
| Context cannot be created | Check context registration and constructor |
| Migration contains no Products table | Check the DbSet<Product> property |
| Database file is not visible | Run dotnet ef database update from the project directory |
What has been achieved?
- Ubuntu's SQLite runtime and development files have been installed before project creation.
- EF Core 8 has been added without the incompatible bundled native SQLite library.
- The system SQLite provider has been configured and initialized.
- A validated Product model has been created.
ProductDbContexthas been registered through dependency injection.- The first migration has been generated.
- The physical
Data/products.dbdatabase has been created. - The Products and migration-history tables have been verified.
PART 1 complete: Ubuntu and dotnetef now have a functioning SQLite database foundation without depending on an e_sqlite3 binary that requires a newer glibc.
Official references
- Microsoft Learn: Razor Pages with Entity Framework Core
- Microsoft Learn: EF Core SQLite provider
- Microsoft Learn: EF Core migrations
- Microsoft Learn: EF Core CLI tools
- NuGet: SQLitePCLRaw system SQLite provider
Next: ENTITY FRAMEWORK CORE (EF CORE) — PART 2 implements complete Product CRUD operations using Razor Pages and asynchronous EF Core queries.