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.cshtml for Home
  • Pages/Privacy.cshtml for Privacy
  • Pages/Shared/_Layout.cshtml for 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 conceptDatabase concept
C# model classTable
Model objectRow
Object propertyColumn
DbContextDatabase session
DbSet<Product>Products table
MigrationRecorded 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:

PackagePurpose
Microsoft.EntityFrameworkCore.Sqlite.CoreEF Core SQLite provider without bundled native SQLite
Microsoft.EntityFrameworkCore.DesignSupports migrations and design-time tools
SQLitePCLRaw.bundle_sqlite3Loads 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

PropertyDatabase role
IdPrimary key generated for each product
NameRequired product name, limited to 100 characters
DescriptionOptional description
PriceRequired positive monetary value
StockQuantityNon-negative inventory count
CreatedAtUTC 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

ProblemCheck
dotnet ef is unavailableInstall or update the global dotnet-ef tool
Package incompatibilityEnsure EF Core major version 8 is installed
sqlite3 is unavailableInstall sqlite3 libsqlite3-0 libsqlite3-dev
GLIBC_2.33 not foundUse Sqlite.Core and bundle_sqlite3, not bundled e_sqlite3
SQLite provider is not initializedAdd SQLitePCL.Batteries_V2.Init() before creating the builder
Unable to open database fileEnsure the Data directory exists
Context cannot be createdCheck context registration and constructor
Migration contains no Products tableCheck the DbSet<Product> property
Database file is not visibleRun 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.
  • ProductDbContext has been registered through dependency injection.
  • The first migration has been generated.
  • The physical Data/products.db database 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

Next: ENTITY FRAMEWORK CORE (EF CORE) — PART 2 implements complete Product CRUD operations using Razor Pages and asynchronous EF Core queries.