Skip to main content
Version: Current

Database Setup (EF Core)

The PrimusSaaS.Identity.Broker.EntityFrameworkCore package provides a production-ready EfCorePrimusUserStore<TUser, TContext> that persists users to any database supported by EF Core.

The package depends only on the EF Core abstraction layer — no specific database provider is bundled. You pick the provider that fits your stack.


Supported Databases

DatabaseNuGet Provider Package
SQL Server / Azure SQLMicrosoft.EntityFrameworkCore.SqlServer
PostgreSQLNpgsql.EntityFrameworkCore.PostgreSQL
SQLite (local dev / edge)Microsoft.EntityFrameworkCore.Sqlite
MySQL / MariaDBPomelo.EntityFrameworkCore.MySql
Azure Cosmos DBMicrosoft.EntityFrameworkCore.Cosmos
OracleOracle.EntityFrameworkCore

Installation

# Core broker + EF Core extension
dotnet add package PrimusSaaS.Identity.Broker
dotnet add package PrimusSaaS.Identity.Broker.EntityFrameworkCore

# Plus your database provider — pick one:
dotnet add package Microsoft.EntityFrameworkCore.SqlServer # SQL Server
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL # PostgreSQL
dotnet add package Microsoft.EntityFrameworkCore.Sqlite # SQLite
dotnet add package Pomelo.EntityFrameworkCore.MySql # MySQL

Step 1 — Define Your User Entity

Inherit from PrimusBrokerUser and add any application-specific columns:

using PrimusSaaS.Identity.Broker.EntityFrameworkCore;

public class AppUser : PrimusBrokerUser
{
// Add your own columns here
public string? TenantId { get; set; }
public string? SubscriptionTier { get; set; }
}

PrimusBrokerUser already provides:

PropertyTypeDescription
IdstringStable identifier (GUID by default)
EmailstringPrimary lookup key — unique
DisplayNamestring?Optional display name
Rolestring?Single-role shorthand — kept for simple apps and backward compatibility
RolesICollection<PrimusUserRole>Multi-role navigation collection — each entry becomes a ClaimTypes.Role claim
LastProviderstring?Last provider used to authenticate this user (azure, auth0, google, okta, local, saml:*)
FailedLoginCountintConsecutive failed logins — reset on success
LockoutEndDateTimeOffset?Null = not locked out
CreatedAtDateTimeOffsetFirst record creation
LastLoginAtDateTimeOffset?Last successful login

Step 2 — Create Your DbContext

using Microsoft.EntityFrameworkCore;

public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

public DbSet<AppUser> Users { get; set; } = null!;

protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<AppUser>(e =>
{
e.HasKey(u => u.Id);
e.HasIndex(u => u.Email).IsUnique();
e.Property(u => u.Email).IsRequired().HasMaxLength(256);
});

// Required: configure the roles join table so EF migrations create it.
// Without this, role persistence breaks silently.
builder.Entity<PrimusUserRole>(e =>
{
e.HasKey(r => r.Id);
e.HasIndex(r => new { r.UserId, r.Name }).IsUnique();
e.Property(r => r.Name).IsRequired().HasMaxLength(128);
});
}
}

Step 3 — Implement Your User Store

Subclass EfCorePrimusUserStore<TUser, TContext>:

public sealed class AppUserStore : EfCorePrimusUserStore<AppUser, AppDbContext>
{
public AppUserStore(AppDbContext db) : base(db) { }

// Override to set fields when a user logs in for the first time (JIT provisioning)
protected override AppUser CreateUser(string email, string provider, ClaimsPrincipal? claims)
=> new() { Email = email, LastProvider = provider, Role = "User" };
}

The base class automatically handles JIT provisioning, lockout enforcement, and last-provider tracking. For custom claims or multi-role RBAC, see Advanced Configuration.


Step 4 — Register in Program.cs

The SQLite example below works for local development. For production, swap options.UseSqlite(...) for the provider matching your database from the Supported Databases table above.

Program.cs
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlite("Data Source=myapp.db"));

builder.Services.AddPrimusAuthBroker(builder.Configuration, builder.Environment.IsDevelopment());

builder.Services.AddPrimusEfCoreUserStore<AppUserStore>();

Step 5 — Add Configuration

appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=myapp.db"
}
}

In production, set ConnectionStrings__DefaultConnection as an environment variable. Never commit real connection strings to source control.


Step 6 — Run Migrations

Migrations create the initial schema when you first set up the database, and apply any changes whenever you update your model. Run these two commands once to get started:

dotnet ef migrations add InitialCreate
dotnet ef database update

Run dotnet ef migrations add <Name> again whenever you add or change properties on AppUser or PrimusBrokerUser.

CI/CD and container deployments

To apply migrations automatically on startup, add this to Program.cs before app.Run():

using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Database.MigrateAsync();
}

Available built-in implementations

What you needBuilt-in optionNotes
User persistenceEfCorePrimusUserStore (this page)Any EF Core-supported database
Audit logging — fileFileAuditSinkJSON Lines, async writes
Audit logging — loggerLoggerAuditSinkWrites to ILogger / stdout
Audit logging — multi-sinkCompositeAuditSinkFan-out to any combination
Audit logging — RedisRedisAuditSinkDistributed, last 10 k events
Rate limiting — single serverInMemoryRateLimiterDefault — no external dependencies
Rate limiting — distributedRedisRateLimiterRequired for multi-instance deployments

For Redis setup, see Bot Protection.


Next Steps