STX.EFCore.Client 3.0.0

dotnet add package STX.EFCore.Client --version 3.0.0
                    
NuGet\Install-Package STX.EFCore.Client -Version 3.0.0
                    
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="STX.EFCore.Client" Version="3.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="STX.EFCore.Client" Version="3.0.0" />
                    
Directory.Packages.props
<PackageReference Include="STX.EFCore.Client" />
                    
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 STX.EFCore.Client --version 3.0.0
                    
#r "nuget: STX.EFCore.Client, 3.0.0"
                    
#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 STX.EFCore.Client@3.0.0
                    
#: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=STX.EFCore.Client&version=3.0.0
                    
Install as a Cake Addin
#tool nuget:?package=STX.EFCore.Client&version=3.0.0
                    
Install as a Cake Tool

STX.EFCore.Client

A general-purpose EF Core client for common data operations, designed for use in a Storage Broker.

Main Features

Method Description
InsertAsync Inserts a single entity and returns it detached.
SelectAllAsync Returns an IQueryable<T> of all entities of the given type.
SelectAsync Finds and returns a single entity by its primary key(s).
UpdateAsync Updates a single entity and returns it detached.
DeleteAsync Deletes a single entity and returns it detached.
BulkInsertAsync Inserts a collection of entities, optionally within a transaction.
BulkReadAsync Reads a collection of entities by matching against the provided objects.
BulkUpdateAsync Updates a collection of entities, optionally within a transaction.
BulkDeleteAsync Deletes a collection of entities, optionally within a transaction.
BulkUpsertAsync Inserts new entities and updates existing ones in a single batch, optionally within a transaction. Existence is determined by primary key.
ExistsAsync Returns true if an entity with the supplied primary key(s) exists in the data store.

All methods accept an optional CancellationToken. The bulk write operations (BulkInsertAsync, BulkUpdateAsync, BulkDeleteAsync, BulkUpsertAsync) also accept a useTransaction flag (defaults to true) that wraps the operation in a database transaction and rolls back automatically on failure.

Exception Behaviour

EFCoreClient does not wrap exceptions. All EF Core and .NET exceptions propagate directly to the caller, giving consumers full visibility and control:

Exception When thrown
ArgumentNullException A required argument (object, objectIds, or objects collection) is null.
DbUpdateException The database rejects a write - e.g. unique constraint, foreign key violation, or NOT NULL failure.
DbUpdateConcurrencyException A concurrency conflict is detected on update or delete (subclass of DbUpdateException).
InvalidOperationException The entity type is not registered in the DbContext model, or an incompatible primary key is supplied.
OperationCanceledException The operation was cancelled via the CancellationToken.

Thread Safety

EFCoreClient is not thread-safe. All operations on a single instance share the same underlying DbContext, which is not designed for concurrent access.

Create one EFCoreClient instance per request or per scope — not as a singleton. In an ASP.NET Core application this means constructing it inside your scoped StorageBroker, which is itself registered as a scoped service:

// ✅ Correct — one EFCoreClient per scope via the scoped StorageBroker
services.AddDbContext<StorageBroker>();  // scoped by default

// ❌ Wrong — a singleton EFCoreClient shared across all requests
services.AddSingleton<IEFCoreClient>(new EFCoreClient(myDbContext));

This is standard EF Core behaviour. See DbContext Lifetime, Configuration, and Initialization for details.

How do I use this?

Before — manual broker implementation

A storage broker previously required manually managing EntityState for every operation:

public partial class StorageBroker : EFxceptionsContext, IStorageBroker
{
    private readonly IConfiguration configuration;

    public StorageBroker(IConfiguration configuration)
    {
        this.configuration = configuration;
        this.Database.Migrate();
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        AddConfigurations(modelBuilder);
    }

    private static void AddConfigurations(ModelBuilder modelBuilder)
    {
        . . .
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        . . .
    }

    private async ValueTask<T> InsertAsync<T>(T @object)
        where T : class
    {
        this.Entry(@object).State = EntityState.Added;
        await this.SaveChangesAsync();
        this.Entry(@object).State = EntityState.Detached;

        return @object;
    }

    private async ValueTask<IQueryable<T>> SelectAllAsync<T>()
        where T : class =>
            this.Set<T>();

    private async ValueTask<T> SelectAsync<T>(params object[] @objectIds)
        where T : class =>
            await this.FindAsync<T>(objectIds);

    private async ValueTask<T> UpdateAsync<T>(T @object)
        where T : class
    {
        this.Entry(@object).State = EntityState.Modified;
        await this.SaveChangesAsync();
        this.Entry(@object).State = EntityState.Detached;

        return @object;
    }

    private async ValueTask<T> DeleteAsync<T>(T @object)
        where T : class
    {
        this.Entry(@object).State = EntityState.Deleted;
        await this.SaveChangesAsync();
        this.Entry(@object).State = EntityState.Detached;

        return @object;
    }
}

After — using EFCoreClient

Pass this (your DbContext) to the EFCoreClient constructor and delegate all operations to it. This eliminates manual state management, adds structured exception handling, cancellation support, and bulk operation support out of the box:

public partial class StorageBroker : EFxceptionsContext, IStorageBroker
{
    private readonly IConfiguration configuration;
    private readonly IEFCoreClient efCoreClient;

    public StorageBroker(IConfiguration configuration)
    {
        this.configuration = configuration;
        this.Database.Migrate();
        this.efCoreClient = new EFCoreClient(this);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        AddConfigurations(modelBuilder);
    }

    private static void AddConfigurations(ModelBuilder modelBuilder)
    {
        . . .
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        . . .
    }

    private async ValueTask<T> InsertAsync<T>(T @object, CancellationToken cancellationToken = default)
        where T : class =>
            await efCoreClient.InsertAsync(@object, cancellationToken);

    private async ValueTask<IQueryable<T>> SelectAllAsync<T>(CancellationToken cancellationToken = default)
        where T : class =>
            await efCoreClient.SelectAllAsync<T>(cancellationToken);

    private async ValueTask<T> SelectAsync<T>(object[] objectIds, CancellationToken cancellationToken = default)
        where T : class =>
            await efCoreClient.SelectAsync<T>(objectIds, cancellationToken);

    private async ValueTask<T> UpdateAsync<T>(T @object, CancellationToken cancellationToken = default)
        where T : class =>
            await efCoreClient.UpdateAsync(@object, cancellationToken);

    private async ValueTask<T> DeleteAsync<T>(T @object, CancellationToken cancellationToken = default)
        where T : class =>
            await efCoreClient.DeleteAsync(@object, cancellationToken);

    private async ValueTask BulkInsertAsync<T>(
        IEnumerable<T> objects,
        bool useTransaction = true,
        CancellationToken cancellationToken = default)
        where T : class =>
            await efCoreClient.BulkInsertAsync(objects, useTransaction, cancellationToken);

    private async ValueTask<IEnumerable<T>> BulkReadAsync<T>(
        IEnumerable<T> objects,
        CancellationToken cancellationToken = default)
        where T : class =>
            await efCoreClient.BulkReadAsync(objects, cancellationToken);

    private async ValueTask BulkUpdateAsync<T>(
        IEnumerable<T> objects,
        bool useTransaction = true,
        CancellationToken cancellationToken = default)
        where T : class =>
            await efCoreClient.BulkUpdateAsync(objects, useTransaction, cancellationToken);

    private async ValueTask BulkDeleteAsync<T>(
        IEnumerable<T> objects,
        bool useTransaction = true,
        CancellationToken cancellationToken = default)
        where T : class =>
            await efCoreClient.BulkDeleteAsync(objects, useTransaction, cancellationToken);

    private async ValueTask BulkUpsertAsync<T>(
        IEnumerable<T> objects,
        bool useTransaction = true,
        CancellationToken cancellationToken = default)
        where T : class =>
            await efCoreClient.BulkUpsertAsync(objects, useTransaction, cancellationToken);

    private async ValueTask<bool> ExistsAsync<T>(
        object[] objectIds,
        CancellationToken cancellationToken = default)
        where T : class =>
            await efCoreClient.ExistsAsync<T>(objectIds, cancellationToken);
}
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
3.0.0 81 5/8/2026
2.0.0 5,095 12/16/2024
1.0.0 1,883 11/26/2024
0.0.0.4 375 11/13/2024
0.0.0.3 1,262 10/4/2024
0.0.0.2 214 10/2/2024
0.0.0.1 183 10/2/2024

v3.0.0 — Targets .NET 10 and EF Core 10.
Adds BulkUpsertAsync and ExistsAsync.
Removes exception categorisation — raw EF Core exceptions now propagate directly to the caller.
Null argument validation uses ArgumentNullException (standard .NET).
This release contains breaking changes — see the README for migration guidance.