Common.EntityFramework 1.0.3

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

Common.EntityFramework

A generic Entity Framework repository and unit of work library with built-in pagination, soft deletes, and audit logging.

Packages

Package Description
Common.EntityFramework.Contracts Models and interfaces — no EF dependency
Common.EntityFramework Repository implementation — requires EF Core

Consumers that only need the contracts (DTOs, interfaces) can reference Common.EntityFramework.Contracts alone.


Getting Started

1. Inherit from a base entity

public class Product : EntityBase<int>
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

// Or with audit timestamps (adds CreateDate and LastUpdated):
public class Order : AuditableEntityBase<int>
{
    public string CustomerName { get; set; }
}

2. Register the Unit of Work

Register your DbContext and then UnitOfWork scoped to it. Both should be scoped so they share the same DbContext instance per request.

services.AddDbContext<AppDbContext>(options => ...);
services.AddScoped<IUnitOfWork<AppDbContext>, UnitOfWork<AppDbContext>>();

To use a custom audit log mapper, use the factory overload instead:

services.AddScoped<IAuditLogMapper, MyAuditLogMapper>();
services.AddScoped<IUnitOfWork<AppDbContext>>(sp =>
    new UnitOfWork<AppDbContext>(
        sp.GetRequiredService<AppDbContext>(),
        sp.GetRequiredService<IAuditLogMapper>()
    ));

3. Inject and use the Unit of Work

The Unit of Work acts as a transaction boundary. You call Create<T>() to get a repository for any entity type, perform all your reads and writes, then call SaveAsync() once to commit everything together.

public class OrderService
{
    private readonly IUnitOfWork<AppDbContext> _uow;

    public OrderService(IUnitOfWork<AppDbContext> uow) => _uow = uow;

    public async Task PlaceOrder(Order order, IEnumerable<Product> products)
    {
        var orderRepo   = _uow.Create<Order>();
        var productRepo = _uow.Create<Product>();

        await orderRepo.AddAsync(order);

        foreach (var product in products)
        {
            product.SoftDelete(); // mark unavailable
            productRepo.Attach(product);
        }

        // All changes committed in a single transaction:
        await _uow.SaveAsync();
    }
}

Reads do not require a save — just call Create<T>() and query:

public async Task<Order> GetOrder(int id)
{
    var repo = _uow.Create<Order>();
    return await repo.FirstOrDefaultAsync(o => o.Id == id);
}

Repository Methods

Method Description
GetManyAsync(predicate, mapper, ...) Returns a projected IEnumerable<TMapped>
GetManyAsync(predicate, ...) Returns raw IQueryable<T> for further composition
FirstOrDefaultAsync(predicate, ...) Returns the first match or null
FirstOrDefaultAsync<R>(predicate, mapper, ...) Returns first match projected, with null-handling mode
CountAsync(predicate, ...) Returns count matching predicate
Any(predicate) Returns true if any record matches
AddAsync(entity) Stages a single entity for insert
AddAsync(entities) Stages multiple entities for insert
Attach(entity) Attaches a detached entity for update tracking
AttachRange(entities) Attaches multiple detached entities
SoftDelete(entity/entities/predicate) Sets DeletedUTC, excluded from future queries
HardDelete(entity/entities/predicate) Permanently removes from the database
GetPage(request, ...) Dynamic paged query with search and ordering
GetPaginatedAsync(predicate, pageSize, ...) Returns full result set chunked into pages

All query methods filter out soft-deleted records by default. Pass allowDeleted: true to include them.


Features

Soft Deletes

All entities inherit DeletedUTC. Setting it excludes the record from all queries unless allowDeleted: true is passed.

repo.SoftDelete(product);           // single entity
repo.SoftDelete(p => p.Price == 0); // predicate overload

await repo.GetManyAsync(p => true, allowDeleted: true); // include soft-deleted

Hard delete is also available:

repo.HardDelete(product);
repo.HardDelete(p => p.Price == 0);

NullMappingMode

Controls the behavior of FirstOrDefaultAsync<R> when no record is found:

// Returns null (default):
await repo.FirstOrDefaultAsync(predicate, mapper, nullMappingMode: NullMappingMode.ReturnNull);

// Returns new R() with default values:
await repo.FirstOrDefaultAsync(predicate, mapper, nullMappingMode: NullMappingMode.ReturnEmpty);

// Runs the mapper against a null source:
await repo.FirstOrDefaultAsync(predicate, mapper, nullMappingMode: NullMappingMode.Map);

Pagination

GetPage accepts a SearchRequest that supports dynamic filtering, sorting, and skip/take:

var request = new SearchRequest
{
    Skip = 0,
    Take = 20,
    SearchTerms = new[]
    {
        new SearchTerm { Field = "Name", Operator = ConditionalOperator.Like, Value = "widget" }
    },
    Orderings = new[]
    {
        new Ordering { Field = "Price", SortDirection = SortDirection.Asc }
    }
};

Page<Product> page = await repo.GetPage(request);
// page.Data       — the records for this page
// page.TotalRecords — total matches before pagination

// With a mapper:
Page<ProductDto> page = await repo.GetPage(request, p => new ProductDto(p));

GetPaginatedAsync returns the entire result set pre-chunked into pages:

PaginatedList<Product> pages = await repo.GetPaginatedAsync(p => p.Price > 0, pageSize: 10);
var firstPage = pages[0];

Audit Logging

Replace SaveAsync() with SaveWithAuditLogsAsync() to automatically record all pending changes before saving:

await _uow.SaveWithAuditLogsAsync(loggedBy: currentUserId, loggedByType: "Employee");

Each AuditLog record captures:

  • Table name and change state (Added, Modified, Deleted(soft))
  • Serialized list of property changes (original → current value)
  • Stack trace at the time of save
  • Optional logged-by user ID and type

The default mapper (DefaultAuditLogMapper) handles all of this automatically. To customize, implement IAuditLogMapper and register it as shown in step 2 above.


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

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
1.0.3 110 5/20/2026
1.0.2 102 5/20/2026
1.0.1 104 5/20/2026
1.0.0 1,202 3/26/2025