NovaCore.Lib.Repositories 0.0.2

The owner has unlisted this package. This could mean that the package is deprecated, has security vulnerabilities or shouldn't be used anymore.
dotnet add package NovaCore.Lib.Repositories --version 0.0.2
                    
NuGet\Install-Package NovaCore.Lib.Repositories -Version 0.0.2
                    
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="NovaCore.Lib.Repositories" Version="0.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NovaCore.Lib.Repositories" Version="0.0.2" />
                    
Directory.Packages.props
<PackageReference Include="NovaCore.Lib.Repositories" />
                    
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 NovaCore.Lib.Repositories --version 0.0.2
                    
#r "nuget: NovaCore.Lib.Repositories, 0.0.2"
                    
#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 NovaCore.Lib.Repositories@0.0.2
                    
#: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=NovaCore.Lib.Repositories&version=0.0.2
                    
Install as a Cake Addin
#tool nuget:?package=NovaCore.Lib.Repositories&version=0.0.2
                    
Install as a Cake Tool

NovaCore.Lib.Repositories

Spring-Data-style repositories for EF Core, wired at compile time. No unit-of-work abstraction, no runtime proxying, no per-entity registration — and no reflection, so the package works in a trimmed or NativeAOT application.

Consumers inject IRepository<Customer> and never need to know whether an entity uses the generic repository or an application-defined custom one.

Setup

builder.Services.AddDbContext<AppDbContext>(o => o.UseSqlServer(connectionString));
builder.Services.AddRepositories<AppDbContext>();

That is the whole configuration. A source generator ships with the package and replaces the AddRepositories call with the exact registrations for your model, computed while your project compiles. Nothing scans assemblies at run time.

Using a repository

public sealed class Handler(IRepository<Customer> customers, IRepository<Order> orders)
{
    public async Task RunAsync(CancellationToken ct)
    {
        var customer = await customers.FindAsync(42, ct);
        var open = await orders.ListAsync(o => o.Status == OrderStatus.Open, ct);
        orders.Add(new Order { CustomerId = customer!.Id });
    }
}

DbContext remains the unit of work. Repositories only stage changes; the application decides when to commit:

await dbContext.SaveChangesAsync(ct);

Repositories for entities belonging to the same context share that scope's DbContext instance, so a single SaveChangesAsync commits work staged through any of them. With several contexts registered, each one is its own unit of work and is saved separately.

Custom repositories

Declare an interface deriving from IRepository<T> and a class deriving from EfRepository<T>:

public interface IOrderRepository : IRepository<Order>
{
    Task<Order?> FindByExternalIdAsync(string id);
}

public sealed class OrderRepository(AppDbContext db) : EfRepository<Order>(db), IOrderRepository
{
    public Task<Order?> FindByExternalIdAsync(string id)
        => Set.SingleOrDefaultAsync(x => x.ExternalId == id);
}

It is discovered automatically. Afterwards IRepository<Order> and IOrderRepository resolve to the same instance, so callers that only need the standard operations are unaffected:

public Handler(IRepository<Order> orders)  { }   // gets OrderRepository
public Handler(IOrderRepository orders)    { }   // same instance, richer contract

Custom repositories may override any generic operation; overrides apply through both contracts. The constructor parameter is what names the context a repository belongs to, which is what lets two repositories for the same entity target different contexts.

EfRepository<T> exposes protected DbSet<T> Set and protected DbContext Db, which is the sanctioned escape hatch for paging, Include, projections, AsNoTracking, ExecuteUpdateAsync and anything else deliberately absent from IRepository<T>.

What gets discovered

Entities come from the context's DbSet<T> properties, following the same rules EF Core itself uses — including non-public ones. Custom repositories are found in the project that calls AddRepositories and in every assembly it references. There is nothing to configure and no assembly list to pass.

An entity with no DbSet — a derived type in a hierarchy, or one configured only through OnModelCreating or an IEntityTypeConfiguration<T> — is not discovered, and IRepository<T> for it will not resolve. Claim it explicitly:

builder.Services.AddRepository<AppDbContext, ArchivedOrder>();

Call AddRepositories from the project that references your repositories. The generator runs where the call site is, and sees that project's references — not the projects that reference it. If AddInfrastructure() in a class library calls AddRepositories<AppDbContext>() but the repositories live in the application, they are invisible. NCR0011 warns when this happens.

Multiple DbContexts

builder.Services.AddDbContext<WriteDbContext>(...);
builder.Services.AddDbContext<ReadDbContext>(...);
builder.Services.AddRepositories<WriteDbContext>();
builder.Services.AddRepositories<ReadDbContext>();

Each context contributes the entities it exposes. When the same entity appears in both — a read/write split — the first call site owns the unqualified IRepository<T> contract; ordering is by file path then position, so it does not vary between machines or builds. The other context's repositories stay reachable through their own interfaces (IOrderReadRepository), which is normally what you want. AddRepository<TDbContext, TEntity>() overrides the choice, and NCR0008 reports it.

Build-time diagnostics

Misconfigurations that used to throw at startup are now build errors, reported on the offending declaration.

ID Severity Meaning
NCR0001 Error A repository targets more than one entity
NCR0002 Error Two custom repositories target the same entity for one context
NCR0003 Error Repository dependency cycle, direct or multi-hop, via contract or concrete type
NCR0004 Error <InterceptorsNamespaces> does not list the generated namespace, so nothing would be registered
NCR0008 Info An entity is exposed by more than one DbContext
NCR0011 Warning A custom repository was found but nothing in this project registers it
NCR0012 Error The registration call cannot be intercepted

Decorating a repository is rejected. A class implementing IRepository<T> whose constructor also takes IRepository<T> is a decorator, not a repository, and would deadlock the container. NCR0003 fails the build instead. Register decorators yourself after AddRepositories. Depending on a different entity's repository is fine.

Notes and limits

  • C# only. The mechanism is a C# interceptor; F# and VB consumers are not supported.
  • The call must be a direct call. Invoking AddRepositories through a delegate or by reflection is not intercepted and throws, because substitution happens at the call site.
  • Targets Microsoft.Extensions.DependencyInjection. Registrations you add yourself always win, in either call order. If you replace IRepository<T> after AddRepositories, any custom interface for that entity reports the conflict rather than handing back an incompatible instance.
  • Call AddRepositories after AddDbContext. It works the other way round too, but a context registered afterwards cannot be checked for the transient lifetime described below.
  • A transient DbContext is rejected at startup, not at build time: the lifetime is a run-time argument to AddDbContext and cannot be read from source.
  • A singleton DbContext is accepted. It is an EF Core anti-pattern for its own reasons, but it does not break the guarantee that repositories in a scope share one unit of work.
  • Cycles introduced by your own registrations cannot be seen at build time. Where the container can detect them it does; where an alias hides the edge, resolution throws a named error rather than hanging.
  • Two AddRepositories calls in different projects cannot be arbitrated at compile time. The first to execute still wins at run time, but no diagnostic is possible.
  • Keyless and owned types have no meaningful FindAsync/Add/Remove behaviour, as with EF Core.

API

Member Purpose
IRepository<T> FindAsync, FirstOrDefaultAsync, ListAsync, AnyAsync, CountAsync, Add(Range), Update(Range), Remove(Range)
EfRepository<T> Default implementation and the base class for custom repositories
IRepositoryFactory For<TEntity>(), for dynamic selection where constructor injection will not do
AddRepositories<TDbContext>() Registration and discovery
AddRepository<TDbContext, TEntity>() Claim an entity the context does not expose as a DbSet
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated