Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ jobs:
- src/eQuantic.Core.Data.EntityFramework.PostgreSql/eQuantic.Core.Data.EntityFramework.PostgreSql.Net10.csproj
- src/eQuantic.Core.Data.EntityFramework.MongoDb/eQuantic.Core.Data.EntityFramework.MongoDb.Net8.csproj
- src/eQuantic.Core.Data.EntityFramework.MongoDb/eQuantic.Core.Data.EntityFramework.MongoDb.Net10.csproj
- src/eQuantic.Core.Data.EntityFramework.CosmosDb/eQuantic.Core.Data.EntityFramework.CosmosDb.Net8.csproj
- src/eQuantic.Core.Data.EntityFramework.CosmosDb/eQuantic.Core.Data.EntityFramework.CosmosDb.Net10.csproj
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
Expand Down Expand Up @@ -57,6 +59,7 @@ jobs:
- tests/eQuantic.Core.Data.EntityFramework.Tests/eQuantic.Core.Data.EntityFramework.Tests.csproj
- tests/eQuantic.Core.Data.EntityFramework.SqlServer.Tests/eQuantic.Core.Data.EntityFramework.SqlServer.Tests.csproj
- tests/eQuantic.Core.Data.EntityFramework.MongoDb.Tests/eQuantic.Core.Data.EntityFramework.MongoDb.Tests.csproj
- tests/eQuantic.Core.Data.EntityFramework.CosmosDb.Tests/eQuantic.Core.Data.EntityFramework.CosmosDb.Tests.csproj
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ jobs:
- src/eQuantic.Core.Data.EntityFramework.PostgreSql/eQuantic.Core.Data.EntityFramework.PostgreSql.Net10.csproj
- src/eQuantic.Core.Data.EntityFramework.MongoDb/eQuantic.Core.Data.EntityFramework.MongoDb.Net8.csproj
- src/eQuantic.Core.Data.EntityFramework.MongoDb/eQuantic.Core.Data.EntityFramework.MongoDb.Net10.csproj
- src/eQuantic.Core.Data.EntityFramework.CosmosDb/eQuantic.Core.Data.EntityFramework.CosmosDb.Net8.csproj
- src/eQuantic.Core.Data.EntityFramework.CosmosDb/eQuantic.Core.Data.EntityFramework.CosmosDb.Net10.csproj
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
Expand Down Expand Up @@ -68,6 +70,7 @@ jobs:
- tests/eQuantic.Core.Data.EntityFramework.Tests/eQuantic.Core.Data.EntityFramework.Tests.csproj
- tests/eQuantic.Core.Data.EntityFramework.SqlServer.Tests/eQuantic.Core.Data.EntityFramework.SqlServer.Tests.csproj
- tests/eQuantic.Core.Data.EntityFramework.MongoDb.Tests/eQuantic.Core.Data.EntityFramework.MongoDb.Tests.csproj
- tests/eQuantic.Core.Data.EntityFramework.CosmosDb.Tests/eQuantic.Core.Data.EntityFramework.CosmosDb.Tests.csproj
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
Expand Down
104 changes: 84 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

**The Entity Framework Core implementation of [eQuantic.Core.Data](https://github.com/eQuantic/core-data).**
You code against the provider-agnostic `IRepository<TEntity, TKey>` / `IUnitOfWork` contracts; this package
supplies the EF Core engine for **SQL Server, PostgreSQL, MySQL and MongoDB**.
supplies the EF Core engine for **SQL Server, PostgreSQL, MySQL, MongoDB and Azure Cosmos DB**.

```csharp
// A repository over any IEntity<TKey>, obtained from your DbContext-backed unit of work:
Expand All @@ -21,6 +21,67 @@ var page = await repo.GetPagedAsync(
// page is a PagedResult<OrderData>: Items + TotalCount + PageIndex/PageSize/PageCount + Has*Page
```

## Why

The Repository pattern keeps your domain ignorant of the persistence engine — you code against
`IRepository<TEntity, TKey>` and can swap SQL Server for PostgreSQL, MongoDB or Cosmos DB without touching a
line of domain code. What usually rots is the *query surface*: a sprawl of `GetPaged`/`GetFiltered`
overloads and `Action<Configuration>` callbacks, with filters passed as magic strings.

On the `eQuantic.Core.Data` **v5** contracts, this provider collapses that into **one `QueryOptions<TEntity>`
per read** — authored typed and fluent, checked at compile time, and translated to EF Core server-side.

## Getting started

Install the provider for your database — pick the major that matches your runtime (see
[Versioning](#versioning) below):

```bash
dotnet add package eQuantic.Core.Data.EntityFramework.SqlServer --version 8.*
```

Give your entities a key via `IEntity<TKey>`, keep your usual `DbContext`, and derive the provider's unit of
work:

```csharp
using eQuantic.Core.Data.Repository;
using eQuantic.Core.Data.EntityFramework.SqlServer.Repository;
using Microsoft.EntityFrameworkCore;

public class OrderData : IEntity<Guid>
{
public Guid Id { get; set; }
public decimal Total { get; set; }
public Guid GetKey() => Id;
public void SetKey(Guid key) => Id = key;
}

public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
public DbSet<OrderData> Orders => Set<OrderData>();
}

public interface IAppUnitOfWork : IQueryableUnitOfWork { }

public class AppUnitOfWork(IServiceProvider sp, AppDbContext ctx)
: UnitOfWork<AppDbContext>(sp, ctx), IAppUnitOfWork;
```

Register the context and repositories — `AddRelationalRepositories` for the SQL providers,
`AddQueryableRepositories` for the document providers (MongoDB, Cosmos DB):

```csharp
services.AddDbContext<AppDbContext>(o => o.UseSqlServer(connectionString));
services.AddRelationalRepositories<IAppUnitOfWork, AppUnitOfWork>();
```

Then inject `IAppUnitOfWork`, ask it for a repository, and query with a `QueryOptions` (the snippet above).
The full slice — specifications, custom repositories, a domain service — is in the
[walkthrough](Repository.md).

> Swap the suffix (`PostgreSql`, `MySql`, `MongoDb`, `CosmosDb`) and the `UseXxx` call to target another
> database.

## What this package gives you

`eQuantic.Core.Data` defines the **contracts** — `IRepository`, `IUnitOfWork`, `QueryOptions`,
Expand Down Expand Up @@ -63,16 +124,31 @@ end up as one predicate the provider translates. The full query-string grammar i
| `eQuantic.Core.Data.EntityFramework.PostgreSql` | PostgreSQL |
| `eQuantic.Core.Data.EntityFramework.MySql` | MySQL (Pomelo) |
| `eQuantic.Core.Data.EntityFramework.MongoDb` | MongoDB (EF Core provider) |
| `eQuantic.Core.Data.EntityFramework.CosmosDb` | Azure Cosmos DB (EF Core provider) |

The three relational providers share `eQuantic.Core.Data.EntityFramework.Relational`; the document providers
(`MongoDb`, `CosmosDb`) are non-relational and build directly on the base
`eQuantic.Core.Data.EntityFramework`. Register your `DbContext`-backed unit of work and the open-generic
repositories through `AddRelationalRepositories<TUnitOfWorkInterface, TUnitOfWorkImpl>()` (relational) or the
base `AddQueryableRepositories<TUnitOfWork>()` (document) — the full wiring is in the
[walkthrough](Repository.md).

**Azure Cosmos DB:** scope a read to one logical partition with the Cosmos-specific `WithPartitionKey`
extension so it doesn't fan out into a cross-partition scan:

```csharp
new QueryOptions<OrderData>()
.WithPartitionKey(tenantId)
.Where(o => o.Status, FilterOperator.Equal, OrderStatus.Paid);
```

The three relational providers share `eQuantic.Core.Data.EntityFramework.Relational`; every provider builds
on the base `eQuantic.Core.Data.EntityFramework`. Register your `DbContext`-backed unit of work and the
open-generic repositories through `AddRelationalRepositories<TUnitOfWorkInterface, TUnitOfWorkImpl>()` — the
full wiring is in the [walkthrough](Repository.md).
Cosmos has no server-side set-based delete/update (`ExecuteDelete`/`ExecuteUpdate` are relational-only), so
`DeleteMany`/`UpdateMany` load the matching documents and modify them through the context.

## Versioning — pick the package major that matches your runtime
## Versioning

This library targets **.NET 8** and **.NET 10**, and each runtime is published as its **own package major**
so the EF Core lines never mix:
Pick the package major that matches your runtime — this library targets **.NET 8** and **.NET 10**, and each
runtime is published as its **own package major** so the EF Core lines never mix:

| Your app | Install | Targets |
|---|---|---|
Expand All @@ -84,18 +160,6 @@ so the EF Core lines never mix:
> must not be read as a .NET version. You normally consume only the provider package for your runtime
> (8.x / 10.x), which pulls the right shared assemblies transitively.

## Install

```bash
# .NET 8 app + SQL Server
dotnet add package eQuantic.Core.Data.EntityFramework.SqlServer --version 8.*

# .NET 10 app + PostgreSQL
dotnet add package eQuantic.Core.Data.EntityFramework.PostgreSql --version 10.*
```

Swap the suffix for `PostgreSql`, `MySql` or `MongoDb` as needed.

## Learn more

- [Repository Pattern walkthrough](Repository.md) — data entities, unit of work, repository and
Expand Down
8 changes: 6 additions & 2 deletions Repository.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@ await orders.RemoveAsync(order); // stage a delete
int affected = await unitOfWork.CommitAsync(); // one round-trip, returns affected rows
```

Set-based writes run directly in the database (EF `ExecuteUpdate`/`ExecuteDelete`) and do not need a
commit:
On the relational providers, set-based writes run as a single server-side statement (EF
`ExecuteUpdate`/`ExecuteDelete`) and do not need a commit:

```csharp
long cancelled = await orders.UpdateManyAsync(
Expand All @@ -240,6 +240,10 @@ long removed = await orders.DeleteManyAsync(o => o.Total == 0m);

`DeleteManyAsync`/`UpdateManyAsync` also accept an `ISpecification<T>` in place of the predicate.

> The document providers implement these two methods differently — **MongoDB** through its native driver,
> **Azure Cosmos DB** by loading the matching documents and modifying them through the context (Cosmos has no
> server-side `ExecuteUpdate`/`ExecuteDelete`). The contract is identical; only the execution differs.

## 8. Custom repositories

Need repository-specific methods, or the plain `IRepository`/`IAsyncRepository` shape resolved by
Expand Down
6 changes: 6 additions & 0 deletions docs/IMPROVEMENT_PLAN.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Improvement Plan — eQuantic.Core.Data.EntityFramework

> **📜 Historical — completed.** This is the original pre-migration analysis. Its recommendations have been
> delivered: the security/correctness/CI phases in PR #1, and the **v5 contract migration** (per-major
> `8.x`/`10.x` + multi-framework `4.x` packages, targets trimmed to net8/net10) shipped on top of it. It is
> kept for the record and describes the **old** state (net6–net10, v4 contracts) — it does **not** reflect
> the current codebase; see the [README](../README.md) and [walkthrough](../Repository.md) instead.

> Deep analysis performed on 2026-07-16 of this repository (v4.4.2 / published 6.x–10.x lines) and of the
> contracts repository [`eQuantic/core-data`](https://github.com/eQuantic/core-data) (v4.3.2).
> Every finding cites `file:line` and was verified against the source code, not inferred.
Expand Down
63 changes: 63 additions & 0 deletions eQuantic.Core.Data.EntityFramework.sln
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.Net10", "src\eQuantic.Core.Data.EntityFramework\eQuantic.Core.Data.EntityFramework.Net10.csproj", "{B8AA3B17-808A-4D8B-8C1F-EB42E9C5C354}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "eQuantic.Core.Data.EntityFramework.CosmosDb", "eQuantic.Core.Data.EntityFramework.CosmosDb", "{9B074A5E-5D97-BD47-3CDB-A58654D0504C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.CosmosDb.Net8", "src\eQuantic.Core.Data.EntityFramework.CosmosDb\eQuantic.Core.Data.EntityFramework.CosmosDb.Net8.csproj", "{AEB46671-269B-4590-AC64-9BDDCE0FC8B6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework", "src\eQuantic.Core.Data.EntityFramework\eQuantic.Core.Data.EntityFramework.csproj", "{709CD1F1-48C2-40C3-8470-9111FB2E0C86}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.CosmosDb.Net10", "src\eQuantic.Core.Data.EntityFramework.CosmosDb\eQuantic.Core.Data.EntityFramework.CosmosDb.Net10.csproj", "{29C8E041-4A5E-4D97-B1F2-EF9C31A951EC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.CosmosDb.Tests", "tests\eQuantic.Core.Data.EntityFramework.CosmosDb.Tests\eQuantic.Core.Data.EntityFramework.CosmosDb.Tests.csproj", "{59E690D6-CE93-423B-9B56-4C28B1A5359D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -234,6 +244,54 @@ Global
{B8AA3B17-808A-4D8B-8C1F-EB42E9C5C354}.Release|x64.Build.0 = Release|Any CPU
{B8AA3B17-808A-4D8B-8C1F-EB42E9C5C354}.Release|x86.ActiveCfg = Release|Any CPU
{B8AA3B17-808A-4D8B-8C1F-EB42E9C5C354}.Release|x86.Build.0 = Release|Any CPU
{AEB46671-269B-4590-AC64-9BDDCE0FC8B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AEB46671-269B-4590-AC64-9BDDCE0FC8B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AEB46671-269B-4590-AC64-9BDDCE0FC8B6}.Debug|x64.ActiveCfg = Debug|Any CPU
{AEB46671-269B-4590-AC64-9BDDCE0FC8B6}.Debug|x64.Build.0 = Debug|Any CPU
{AEB46671-269B-4590-AC64-9BDDCE0FC8B6}.Debug|x86.ActiveCfg = Debug|Any CPU
{AEB46671-269B-4590-AC64-9BDDCE0FC8B6}.Debug|x86.Build.0 = Debug|Any CPU
{AEB46671-269B-4590-AC64-9BDDCE0FC8B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AEB46671-269B-4590-AC64-9BDDCE0FC8B6}.Release|Any CPU.Build.0 = Release|Any CPU
{AEB46671-269B-4590-AC64-9BDDCE0FC8B6}.Release|x64.ActiveCfg = Release|Any CPU
{AEB46671-269B-4590-AC64-9BDDCE0FC8B6}.Release|x64.Build.0 = Release|Any CPU
{AEB46671-269B-4590-AC64-9BDDCE0FC8B6}.Release|x86.ActiveCfg = Release|Any CPU
{AEB46671-269B-4590-AC64-9BDDCE0FC8B6}.Release|x86.Build.0 = Release|Any CPU
{709CD1F1-48C2-40C3-8470-9111FB2E0C86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{709CD1F1-48C2-40C3-8470-9111FB2E0C86}.Debug|Any CPU.Build.0 = Debug|Any CPU
{709CD1F1-48C2-40C3-8470-9111FB2E0C86}.Debug|x64.ActiveCfg = Debug|Any CPU
{709CD1F1-48C2-40C3-8470-9111FB2E0C86}.Debug|x64.Build.0 = Debug|Any CPU
{709CD1F1-48C2-40C3-8470-9111FB2E0C86}.Debug|x86.ActiveCfg = Debug|Any CPU
{709CD1F1-48C2-40C3-8470-9111FB2E0C86}.Debug|x86.Build.0 = Debug|Any CPU
{709CD1F1-48C2-40C3-8470-9111FB2E0C86}.Release|Any CPU.ActiveCfg = Release|Any CPU
{709CD1F1-48C2-40C3-8470-9111FB2E0C86}.Release|Any CPU.Build.0 = Release|Any CPU
{709CD1F1-48C2-40C3-8470-9111FB2E0C86}.Release|x64.ActiveCfg = Release|Any CPU
{709CD1F1-48C2-40C3-8470-9111FB2E0C86}.Release|x64.Build.0 = Release|Any CPU
{709CD1F1-48C2-40C3-8470-9111FB2E0C86}.Release|x86.ActiveCfg = Release|Any CPU
{709CD1F1-48C2-40C3-8470-9111FB2E0C86}.Release|x86.Build.0 = Release|Any CPU
{29C8E041-4A5E-4D97-B1F2-EF9C31A951EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{29C8E041-4A5E-4D97-B1F2-EF9C31A951EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{29C8E041-4A5E-4D97-B1F2-EF9C31A951EC}.Debug|x64.ActiveCfg = Debug|Any CPU
{29C8E041-4A5E-4D97-B1F2-EF9C31A951EC}.Debug|x64.Build.0 = Debug|Any CPU
{29C8E041-4A5E-4D97-B1F2-EF9C31A951EC}.Debug|x86.ActiveCfg = Debug|Any CPU
{29C8E041-4A5E-4D97-B1F2-EF9C31A951EC}.Debug|x86.Build.0 = Debug|Any CPU
{29C8E041-4A5E-4D97-B1F2-EF9C31A951EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{29C8E041-4A5E-4D97-B1F2-EF9C31A951EC}.Release|Any CPU.Build.0 = Release|Any CPU
{29C8E041-4A5E-4D97-B1F2-EF9C31A951EC}.Release|x64.ActiveCfg = Release|Any CPU
{29C8E041-4A5E-4D97-B1F2-EF9C31A951EC}.Release|x64.Build.0 = Release|Any CPU
{29C8E041-4A5E-4D97-B1F2-EF9C31A951EC}.Release|x86.ActiveCfg = Release|Any CPU
{29C8E041-4A5E-4D97-B1F2-EF9C31A951EC}.Release|x86.Build.0 = Release|Any CPU
{59E690D6-CE93-423B-9B56-4C28B1A5359D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{59E690D6-CE93-423B-9B56-4C28B1A5359D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{59E690D6-CE93-423B-9B56-4C28B1A5359D}.Debug|x64.ActiveCfg = Debug|Any CPU
{59E690D6-CE93-423B-9B56-4C28B1A5359D}.Debug|x64.Build.0 = Debug|Any CPU
{59E690D6-CE93-423B-9B56-4C28B1A5359D}.Debug|x86.ActiveCfg = Debug|Any CPU
{59E690D6-CE93-423B-9B56-4C28B1A5359D}.Debug|x86.Build.0 = Debug|Any CPU
{59E690D6-CE93-423B-9B56-4C28B1A5359D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{59E690D6-CE93-423B-9B56-4C28B1A5359D}.Release|Any CPU.Build.0 = Release|Any CPU
{59E690D6-CE93-423B-9B56-4C28B1A5359D}.Release|x64.ActiveCfg = Release|Any CPU
{59E690D6-CE93-423B-9B56-4C28B1A5359D}.Release|x64.Build.0 = Release|Any CPU
{59E690D6-CE93-423B-9B56-4C28B1A5359D}.Release|x86.ActiveCfg = Release|Any CPU
{59E690D6-CE93-423B-9B56-4C28B1A5359D}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -259,6 +317,11 @@ Global
{BEB4D7A8-0559-D436-17A0-F8FA25A80731} = {0CA2F610-6D57-4D1F-92E3-EDCCEDAD8297}
{0CAE00C1-4547-4DDA-A526-A4BBB78898A2} = {BEB4D7A8-0559-D436-17A0-F8FA25A80731}
{B8AA3B17-808A-4D8B-8C1F-EB42E9C5C354} = {BEB4D7A8-0559-D436-17A0-F8FA25A80731}
{9B074A5E-5D97-BD47-3CDB-A58654D0504C} = {0CA2F610-6D57-4D1F-92E3-EDCCEDAD8297}
{AEB46671-269B-4590-AC64-9BDDCE0FC8B6} = {9B074A5E-5D97-BD47-3CDB-A58654D0504C}
{709CD1F1-48C2-40C3-8470-9111FB2E0C86} = {0CA2F610-6D57-4D1F-92E3-EDCCEDAD8297}
{29C8E041-4A5E-4D97-B1F2-EF9C31A951EC} = {9B074A5E-5D97-BD47-3CDB-A58654D0504C}
{59E690D6-CE93-423B-9B56-4C28B1A5359D} = {EE71F02F-413D-4BC0-832F-726B6D8C0AD7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {83FECDD1-8A97-40B1-8529-3D5216E674C3}
Expand Down
Loading
Loading