EFRadix.Core.Postgres 1.0.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package EFRadix.Core.Postgres --version 1.0.1
                    
NuGet\Install-Package EFRadix.Core.Postgres -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EFRadix.Core.Postgres" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EFRadix.Core.Postgres" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="EFRadix.Core.Postgres" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EFRadix.Core.Postgres --version 1.0.1
                    
#r "nuget: EFRadix.Core.Postgres, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EFRadix.Core.Postgres@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EFRadix.Core.Postgres&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=EFRadix.Core.Postgres&version=1.0.1
                    
Install as a Cake Tool

EFRadix.Core.Postgres

A PostgreSQL data access SDK for .NET 8 built on top of Entity Framework Core and Npgsql. Provides a generic repository pattern, automatic CRUD operations, and a Roslyn source generator that emits typed repository context classes from your DbContext at compile time — zero boilerplate, full IntelliSense.


Table of Contents


Installation

dotnet add package EFRadix.Core.Postgres

Quick Start

1. Define your entities

using EFRadix.Core.Postgres.Entities;

public class Student : BaseEntity
{
    public string Name { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
}

2. Define your DbContext

using EFRadix.Core.Postgres.Contexts;
using Microsoft.EntityFrameworkCore;

public class AppDbContext(DbContextOptions<AppDbContext> options)
    : BaseNpgsqlDbContext<AppDbContext>(options)
{
    public DbSet<Student> Students { get; set; }
}

3. Register in DI

// Program.cs
builder.Services.AddEFRadixNpgsqlDataContext<AppDbContext, AppRepositoryContext>(
    builder.Configuration);

4. Inject and use

public class StudentService(IAppRepositoryContext db)
{
    public Task<Student?> GetAsync(string id) =>
        db.StudentRepository.GetByIdAsync(id);

    public Task CreateAsync(Student student) =>
        db.StudentRepository.AddAsync(student);
}

IAppRepositoryContext and AppRepositoryContext are generated at compile time by the EFRadix source generator from your DbSet properties.


Core Concepts

BaseEntity

All entities must inherit from BaseEntity:

public abstract class BaseEntity
{
    public string Id { get; set; } = Guid.NewGuid().ToString("N");
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
  • Id — a GUID string, auto-generated on construction.
  • CreatedAt — UTC timestamp, set on construction.

Extend with additional audit fields (UpdatedAt, CreatedBy, etc.) as needed.


IBaseRepository<T>

The generic CRUD contract available for every entity:

Method Description
GetQueryable() Returns a no-tracking IQueryable<T> for composing custom LINQ queries
GetByIdAsync(id) Fetches a single entity by primary key; returns null if not found
AddAsync(entity) Inserts and immediately saves
AddRangeAsync(entities) Bulk insert and immediately saves
UpdateAsync(entity) Updates and immediately saves
RemoveAsync(entity) Deletes and immediately saves
RemoveRangeAsync(entities) Bulk delete and immediately saves
SaveChangesAsync() Explicitly flushes all pending changes on the context

All write operations call SaveChangesAsync internally. Use the explicit SaveChangesAsync() when batching multiple tracked changes manually.


IRepositoryContext

The entry point for all repository access:

public interface IRepositoryContext
{
    IBaseRepository<T> Repository<T>() where T : BaseEntity;
}

The source generator extends this with named typed properties, giving you:

public interface IAppRepositoryContext : IRepositoryContext
{
    IBaseRepository<Student> StudentRepository { get; }
    IBaseRepository<Tenant> TenantRepository { get; }
}

Inject IAppRepositoryContext (the generated interface) rather than IRepositoryContext to get typed property access and full IntelliSense.


BaseNpgsqlDbContext<T>

The base class for your DbContext. It handles two things automatically:

  • Npgsql switches — configures EnableLegacyTimestampBehavior and DisableDateTimeInfinityConversions based on NpgsqlDataOptions.
  • Entity configuration discovery — calls modelBuilder.ApplyConfigurationsFromAssembly(...) so all IEntityTypeConfiguration<T> implementations in your assembly are applied without manual registration.
public class AppDbContext(DbContextOptions<AppDbContext> options)
    : BaseNpgsqlDbContext<AppDbContext>(options)
{
    public DbSet<Student> Students { get; set; }
    public DbSet<Tenant> Tenants { get; set; }
}

Source Generator

EFRadix includes a Roslyn incremental source generator that runs at compile time. It scans your DbContext for DbSet<T> properties and emits:

  • I{Name}RepositoryContext — a typed interface extending IRepositoryContext with a named property per entity.
  • {Name}RepositoryContext — a partial class implementing the interface, with each property delegating to Repository<T>().

Example — given AppDbContext with DbSet<Student> and DbSet<Tenant>, the generator produces:

// I AppRepositoryContext.g.cs
public interface IAppRepositoryContext : IRepositoryContext
{
    IBaseRepository<Student> StudentRepository { get; }
    IBaseRepository<Tenant> TenantRepository { get; }
}

// AppRepositoryContext.g.cs
public partial class AppRepositoryContext
    : RepositoryContext<AppDbContext>, IAppRepositoryContext
{
    public AppRepositoryContext(AppDbContext context) : base(context) { }

    public IBaseRepository<Student> StudentRepository => Repository<Student>();
    public IBaseRepository<Tenant> TenantRepository => Repository<Tenant>();
}

The generated files appear under Analyzers in your project tree. No additional attributes or configuration are required — the generator detects any class inheriting from BaseNpgsqlDbContext<T> automatically.


Registration

AddEFRadixNpgsqlDataContext

Register your DbContext and generated repository context in one call:

builder.Services.AddEFRadixNpgsqlDataContext<AppDbContext, AppRepositoryContext>(
    builder.Configuration);

With options:

builder.Services.AddEFRadixNpgsqlDataContext<AppDbContext, AppRepositoryContext>(
    builder.Configuration,
    options =>
    {
        options.ConnectionStringName = "DbConnection";
        options.UseJsonNet = true;
    });

This registers:

  • AppDbContext as a scoped DbContext backed by an NpgsqlDataSource.
  • IRepositoryContextAppRepositoryContext (scoped).
  • The BaseRepository<,> open generic in EFRadixRepositoryRegistry for runtime resolution.

NpgsqlDataOptions

Property Type Default Description
ConnectionStringName string "DbConnection" Key used to resolve the connection string from IConfiguration
UseJsonNet bool false Enables Newtonsoft.Json support for JSON columns
EnableLegacyTimestampBehavior bool true Disables Npgsql's UTC-only enforcement for DateTime values
DisableDateTimeInfinityConversions bool true Disables mapping of DateTime.MinValue/MaxValue to -infinity/infinity

Connection string in appsettings.json:

{
  "ConnectionStrings": {
    "DbConnection": "Host=localhost;Database=mydb;Username=postgres;Password=secret"
  }
}

Entity Configuration

Place IEntityTypeConfiguration<T> implementations in the same assembly as your DbContext. They are discovered and applied automatically.

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

public class StudentConfiguration : IEntityTypeConfiguration<Student>
{
    public void Configure(EntityTypeBuilder<Student> builder)
    {
        builder.HasKey(s => s.Id);
        builder.Property(s => s.Name).IsRequired().HasMaxLength(200);
        builder.Property(s => s.Email).IsRequired().HasMaxLength(200);
    }
}

Advanced Usage

Custom Queries with GetQueryable

GetQueryable() returns a no-tracking IQueryable<T>, which you can compose with standard LINQ:

var activeStudents = db.StudentRepository
    .GetQueryable()
    .Where(s => s.Name.Contains("John"))
    .OrderBy(s => s.CreatedAt)
    .ToListAsync();

For projections, joins, or pagination, compose directly on the queryable before materializing.


Using Without the Source Generator

If you prefer not to use the source generator, register with DynamicRepositoryContext<TContext> directly and access repositories via the generic Repository<T>() method:

// Registration
services.AddScoped<IRepositoryContext>(sp =>
    new DynamicRepositoryContext<AppDbContext>(sp.GetRequiredService<AppDbContext>()));

// Usage — no named properties, generic access only
var repo = repositoryContext.Repository<Student>();
await repo.AddAsync(student);

Design Notes

  • Immediate persistence — every write method calls SaveChangesAsync internally. This keeps the API simple but means each operation is its own transaction. Use SaveChangesAsync() explicitly if you need to batch multiple tracked changes into a single transaction.
  • No-tracking readsGetQueryable() and GetByIdAsync() use AsNoTracking() for performance. Entities returned from these methods are not tracked by the context.
  • Provider registryEFRadixRepositoryRegistry decouples the core RepositoryContext from any specific provider. Provider packages register their BaseRepository<,> type at startup, keeping the core layer provider-agnostic.
  • Compile-time generation — the source generator uses Roslyn's incremental pipeline, so regeneration is cache-aware and does not affect IDE responsiveness.
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on EFRadix.Core.Postgres:

Package Downloads
DotnetCqrsPgTemplate.Api

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.4 156 4/7/2026
1.0.2 103 4/7/2026
1.0.1 89 4/7/2026
1.0.0 96 4/7/2026