diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..5f778c8
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,73 @@
+name: CI
+
+on:
+ push:
+ pull_request:
+
+concurrency:
+ group: ci-${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build:
+ name: Build ${{ matrix.project }}
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ strategy:
+ fail-fast: false
+ matrix:
+ project:
+ - src/eQuantic.Core.Data.EntityFramework/eQuantic.Core.Data.EntityFramework.csproj
+ - src/eQuantic.Core.Data.EntityFramework/eQuantic.Core.Data.EntityFramework.Net8.csproj
+ - src/eQuantic.Core.Data.EntityFramework/eQuantic.Core.Data.EntityFramework.Net10.csproj
+ - src/eQuantic.Core.Data.EntityFramework.Relational/eQuantic.Core.Data.EntityFramework.Relational.csproj
+ - src/eQuantic.Core.Data.EntityFramework.SqlServer/eQuantic.Core.Data.EntityFramework.SqlServer.Net8.csproj
+ - src/eQuantic.Core.Data.EntityFramework.SqlServer/eQuantic.Core.Data.EntityFramework.SqlServer.Net10.csproj
+ - src/eQuantic.Core.Data.EntityFramework.MySql/eQuantic.Core.Data.EntityFramework.MySql.Net8.csproj
+ - src/eQuantic.Core.Data.EntityFramework.MySql/eQuantic.Core.Data.EntityFramework.MySql.Net10.csproj
+ - src/eQuantic.Core.Data.EntityFramework.PostgreSql/eQuantic.Core.Data.EntityFramework.PostgreSql.Net8.csproj
+ - 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
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 10.0.x
+ - name: Cache NuGet packages
+ uses: actions/cache@v4
+ with:
+ path: ~/.nuget/packages
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
+ restore-keys: |
+ ${{ runner.os }}-nuget-
+ - name: Build
+ run: dotnet build ${{ matrix.project }} --configuration Release -p:ContinuousIntegrationBuild=true
+
+ test:
+ name: Test ${{ matrix.project }}
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ strategy:
+ fail-fast: false
+ matrix:
+ project:
+ - 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
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 10.0.x
+ - name: Cache NuGet packages
+ uses: actions/cache@v4
+ with:
+ path: ~/.nuget/packages
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
+ restore-keys: |
+ ${{ runner.os }}-nuget-
+ - name: Test
+ run: dotnet test ${{ matrix.project }} --configuration Release
diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml
deleted file mode 100644
index 372c6ff..0000000
--- a/.github/workflows/dotnetcore.yml
+++ /dev/null
@@ -1,67 +0,0 @@
-name: eQuantic Core Data EntityFramework
-
-on: [push]
-
-jobs:
- build:
- runs-on: windows-latest
-
- steps:
- - uses: actions/checkout@v3
- - name: Setup .NET Core
- uses: actions/setup-dotnet@v3
- with:
- dotnet-version: 10.0.x
- - name: Build eQuantic.Core.Data.EntityFramework Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework/eQuantic.Core.Data.EntityFramework.csproj --configuration Release
- - name: Build eQuantic.Core.Data.EntityFramework .net 6 Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework/eQuantic.Core.Data.EntityFramework.Net6.csproj --configuration Release
- - name: Build eQuantic.Core.Data.EntityFramework .net 7 Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework/eQuantic.Core.Data.EntityFramework.Net7.csproj --configuration Release
- - name: Build eQuantic.Core.Data.EntityFramework .net 8 Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework/eQuantic.Core.Data.EntityFramework.Net8.csproj --configuration Release
- - name: Build eQuantic.Core.Data.EntityFramework .net 9 Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework/eQuantic.Core.Data.EntityFramework.Net9.csproj --configuration Release
- - name: Build eQuantic.Core.Data.EntityFramework .net 10 Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework/eQuantic.Core.Data.EntityFramework.Net10.csproj --configuration Release
-
- - name: Build eQuantic.Core.Data.EntityFramework.SqlServer Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework.SqlServer/eQuantic.Core.Data.EntityFramework.SqlServer.csproj --configuration Release
- - name: Build eQuantic.Core.Data.EntityFramework.SqlServer .net 6 Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework.SqlServer/eQuantic.Core.Data.EntityFramework.SqlServer.Net6.csproj --configuration Release
- - name: Build eQuantic.Core.Data.EntityFramework.SqlServer .net 7 Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework.SqlServer/eQuantic.Core.Data.EntityFramework.SqlServer.Net7.csproj --configuration Release
- - name: Build eQuantic.Core.Data.EntityFramework.SqlServer .net 8 Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework.SqlServer/eQuantic.Core.Data.EntityFramework.SqlServer.Net8.csproj --configuration Release
- - name: Build eQuantic.Core.Data.EntityFramework.SqlServer .net 9 Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework.SqlServer/eQuantic.Core.Data.EntityFramework.SqlServer.Net9.csproj --configuration Release
- - name: Build eQuantic.Core.Data.EntityFramework.SqlServer .net 10 Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework.SqlServer/eQuantic.Core.Data.EntityFramework.SqlServer.Net10.csproj --configuration Release
-
- - name: Build eQuantic.Core.Data.EntityFramework.MySql Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework.MySql/eQuantic.Core.Data.EntityFramework.MySql.csproj --configuration Release
- - name: Build eQuantic.Core.Data.EntityFramework.MySql .net 8 Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework.MySql/eQuantic.Core.Data.EntityFramework.MySql.Net8.csproj --configuration Release
- - name: Build eQuantic.Core.Data.EntityFramework.MySql .net 9 Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework.MySql/eQuantic.Core.Data.EntityFramework.MySql.Net9.csproj --configuration Release
- - name: Build eQuantic.Core.Data.EntityFramework.MySql .net 10 Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework.MySql/eQuantic.Core.Data.EntityFramework.MySql.Net10.csproj --configuration Release
-
- - name: Build eQuantic.Core.Data.EntityFramework.PostgreSql Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework.PostgreSql/eQuantic.Core.Data.EntityFramework.PostgreSql.csproj --configuration Release
- - name: Build eQuantic.Core.Data.EntityFramework.PostgreSql .net 8 Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework.PostgreSql/eQuantic.Core.Data.EntityFramework.PostgreSql.Net8.csproj --configuration Release
- - name: Build eQuantic.Core.Data.EntityFramework.PostgreSql .net 9 Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework.PostgreSql/eQuantic.Core.Data.EntityFramework.PostgreSql.Net9.csproj --configuration Release
- - name: Build eQuantic.Core.Data.EntityFramework.PostgreSql .net 10 Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework.PostgreSql/eQuantic.Core.Data.EntityFramework.PostgreSql.Net10.csproj --configuration Release
-
- - name: Build eQuantic.Core.Data.EntityFramework.MongoDb Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework.MongoDb/eQuantic.Core.Data.EntityFramework.MongoDb.csproj --configuration Release
- - name: Build eQuantic.Core.Data.EntityFramework.MongoDb .net 9 Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework.MongoDb/eQuantic.Core.Data.EntityFramework.MongoDb.Net9.csproj --configuration Release
- - name: Build eQuantic.Core.Data.EntityFramework.MongoDb .net 10 Library
- run: dotnet build ./src/eQuantic.Core.Data.EntityFramework.MongoDb/eQuantic.Core.Data.EntityFramework.MongoDb.Net10.csproj --configuration Release
-
- - name: Push package into Nuget.org
- run: dotnet nuget push **/*.nupkg --skip-duplicate -k ${{secrets.nuget_key}} -s https://api.nuget.org/v3/index.json
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..cc821e5
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,111 @@
+name: Release
+
+# Only a pushed version tag (e.g. v10.0.3) triggers a release. A plain push to any branch — including
+# master — no longer publishes anything; that was the previous, unintentional behavior (see
+# docs/IMPROVEMENT_PLAN.md, finding Q1). To release: `git tag vX.Y.Z && git push origin vX.Y.Z`.
+on:
+ push:
+ tags:
+ - 'v*.*.*'
+
+concurrency:
+ group: release-${{ github.ref }}
+ cancel-in-progress: false
+
+jobs:
+ build:
+ name: Build ${{ matrix.project }}
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ strategy:
+ fail-fast: false
+ matrix:
+ project:
+ - src/eQuantic.Core.Data.EntityFramework/eQuantic.Core.Data.EntityFramework.csproj
+ - src/eQuantic.Core.Data.EntityFramework/eQuantic.Core.Data.EntityFramework.Net8.csproj
+ - src/eQuantic.Core.Data.EntityFramework/eQuantic.Core.Data.EntityFramework.Net10.csproj
+ - src/eQuantic.Core.Data.EntityFramework.Relational/eQuantic.Core.Data.EntityFramework.Relational.csproj
+ - src/eQuantic.Core.Data.EntityFramework.SqlServer/eQuantic.Core.Data.EntityFramework.SqlServer.Net8.csproj
+ - src/eQuantic.Core.Data.EntityFramework.SqlServer/eQuantic.Core.Data.EntityFramework.SqlServer.Net10.csproj
+ - src/eQuantic.Core.Data.EntityFramework.MySql/eQuantic.Core.Data.EntityFramework.MySql.Net8.csproj
+ - src/eQuantic.Core.Data.EntityFramework.MySql/eQuantic.Core.Data.EntityFramework.MySql.Net10.csproj
+ - src/eQuantic.Core.Data.EntityFramework.PostgreSql/eQuantic.Core.Data.EntityFramework.PostgreSql.Net8.csproj
+ - 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
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 10.0.x
+ - name: Cache NuGet packages
+ uses: actions/cache@v4
+ with:
+ path: ~/.nuget/packages
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
+ restore-keys: |
+ ${{ runner.os }}-nuget-
+ - name: Build & pack
+ run: dotnet build ${{ matrix.project }} --configuration Release -p:ContinuousIntegrationBuild=true
+ - name: Upload package artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: nupkg-${{ strategy.job-index }}
+ path: artifacts/*.nupkg
+ if-no-files-found: error
+ retention-days: 7
+
+ test:
+ name: Test ${{ matrix.project }}
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ strategy:
+ fail-fast: false
+ matrix:
+ project:
+ - 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
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 10.0.x
+ - name: Cache NuGet packages
+ uses: actions/cache@v4
+ with:
+ path: ~/.nuget/packages
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
+ restore-keys: |
+ ${{ runner.os }}-nuget-
+ - name: Test
+ run: dotnet test ${{ matrix.project }} --configuration Release
+
+ publish:
+ name: Publish to NuGet.org
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ needs: [build, test]
+ # This environment gates the publish behind whatever protection rules are configured for it in
+ # the repo's Settings -> Environments (e.g. required reviewers). GitHub auto-creates an environment
+ # on first use with NO protection rules, so a repo admin must add them for the gate to be effective.
+ environment:
+ name: nuget-release
+ url: https://www.nuget.org/profiles/eQuantic
+ steps:
+ - uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 10.0.x
+ - name: Download all package artifacts
+ uses: actions/download-artifact@v4
+ with:
+ pattern: nupkg-*
+ path: artifacts
+ merge-multiple: true
+ - name: Push to NuGet.org
+ env:
+ NUGET_KEY: ${{ secrets.nuget_key }}
+ run: dotnet nuget push "artifacts/*.nupkg" --skip-duplicate -k "$NUGET_KEY" -s https://api.nuget.org/v3/index.json
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..58d0fb3
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,32 @@
+
+
+
+
+
+ eQuantic Systems
+ Copyright © 2016
+ https://github.com/eQuantic/core-data-entityframework
+ https://github.com/eQuantic/core-data-entityframework
+ Git
+ LICENSE
+ README.md
+ Icon.png
+ latest
+ $(MSBuildThisFileDirectory)artifacts/
+ true
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index 44faac1..690b10e 100644
--- a/README.md
+++ b/README.md
@@ -1,42 +1,107 @@
-# eQuantic Core Data Entity Framework Library
+# eQuantic.Core.Data.EntityFramework
-The **eQuantic Data Core** provides a robust implementation of the **Repository Pattern** specifically for **Entity Framework Core**.
+**The Entity Framework Core implementation of [eQuantic.Core.Data](https://github.com/eQuantic/core-data).**
+You code against the provider-agnostic `IRepository` / `IUnitOfWork` contracts; this package
+supplies the EF Core engine for **SQL Server, PostgreSQL, MySQL and MongoDB**.
-This library offers seamless integration with the following database providers:
+```csharp
+// A repository over any IEntity, obtained from your DbContext-backed unit of work:
+var repo = unitOfWork.GetAsyncRepository();
-- **SQL Server**
-- **PostgreSQL**
-- **MySQL**
-- **MongoDB** (via EF Core provider)
+// Query typed and fluent — one QueryOptions, no magic strings:
+var page = await repo.GetPagedAsync(
+ PageRequest.Of(pageIndex: 1, pageSize: 20),
+ new QueryOptions()
+ .Where(o => o.Total, FilterOperator.GreaterThan, 100m)
+ .And(o => o.Customer.Name, FilterOperator.Contains, term)
+ .OrderByDescending(o => o.CreatedAt)
+ .Include(nameof(OrderData.Customer))
+ .NoTracking());
-## Version 4.4.0
+// page is a PagedResult: Items + TotalCount + PageIndex/PageSize/PageCount + Has*Page
+```
+
+## What this package gives you
-### Key Features and Improvements (v4.4.0)
+`eQuantic.Core.Data` defines the **contracts** — `IRepository`, `IUnitOfWork`, `QueryOptions`,
+`PageRequest`, `PagedResult`, specifications — with the persistence engine kept out of the type signatures
+(`IRepository`, not `IRepository`).
-- **.NET 10 Support**: Full compatibility with .NET 10, including optimized `ExecuteUpdate` operations using the new `UpdateSettersBuilder`.
-- **Improved Expression Conversion**: Enhanced reflection-based method lookup for `ExecuteUpdate` setters, ensuring robustness across different .NET frameworks and provider-specific quirks.
-- **Optimized Resource Management**: Implemented internal cleanup mechanisms in `UnitOfWork` to better manage memory and database connections.
-- **Enhanced Data Integrity**: Fixed shadow field inheritance issues by replacing brittle `new` keyword usage with `internal virtual` properties.
-- **Strict Pagination Validation**: Added explicit validation for pagination parameters in `QueryableReadRepository`.
-- **Full Multi-Provider Support**: Optimized implementations for **SqlServer**, **PostgreSql**, **MySql**, and **MongoDb**.
+This package is the **Entity Framework Core implementation** of those contracts. It translates a single
+`QueryOptions` into an EF `IQueryable` — applying, in order, custom *before* hooks, the
+specification and predicate filter, eager `Include`s, sortings (server-side / EF-translatable),
+`AsNoTracking`, `IgnoreQueryFilters`, query tags and custom *after* hooks — and returns `PagedResult`
+from paged reads. The relational providers also carry a parameterized raw-SQL executor (functions and
+stored procedures, always via `DbParameter`).
-## Installation
+## How you query
-To install **eQuantic.Core.Data.EntityFramework**, run the following command in the [Package Manager Console](https://docs.nuget.org/docs/start-here/using-the-package-manager-console):
+`QueryOptions` mirrors the eQuantic.Linq query builders, so filters read like code and fail at
+compile time — not at runtime:
-```powershell
-Install-Package eQuantic.Core.Data.EntityFramework
+```csharp
+new QueryOptions()
+ .Where(o => o.Total, FilterOperator.GreaterThanOrEqual, 100m) // typed member selector
+ .And(o => o.Status, FilterOperator.Equal, OrderStatus.Paid) // clauses fold left to right:
+ .Or(o => o.Customer.IsVip, FilterOperator.Equal, true) // (total>=100 AND paid) OR vip
+ .OrderByDescending(o => o.CreatedAt)
+ .ThenBy("customer.name") // string path for dynamic columns
+ .Include(nameof(OrderData.Customer))
+ .NoTracking();
```
-For specific providers, install the corresponding package:
+You reach for whichever filter form fits — member selector, `string` path, `ISpecification`,
+`Expression>`, a serialized `ExpressionModel`, or an `eQuantic.Linq.Web` query string — all
+end up as one predicate the provider translates. The full query-string grammar is documented in the
+[eQuantic.Linq reference](https://github.com/eQuantic/core-linq/blob/main/docs/query-string-syntax.md).
+
+## Providers
+
+| Package | Database |
+|---|---|
+| `eQuantic.Core.Data.EntityFramework.SqlServer` | SQL Server |
+| `eQuantic.Core.Data.EntityFramework.PostgreSql` | PostgreSQL |
+| `eQuantic.Core.Data.EntityFramework.MySql` | MySQL (Pomelo) |
+| `eQuantic.Core.Data.EntityFramework.MongoDb` | MongoDB (EF Core provider) |
+
+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()` — the
+full wiring is in the [walkthrough](Repository.md).
+
+## Versioning — 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 |
+|---|---|---|
+| .NET 8 | **8.x** (e.g. `8.2.0`) | `net8.0`, EF Core 8 |
+| .NET 10 | **10.x** (e.g. `10.1.0`) | `net10.0`, EF Core 10 |
+
+> The shared multi-framework assemblies (the referenceable base `eQuantic.Core.Data.EntityFramework` and
+> `eQuantic.Core.Data.EntityFramework.Relational`) stay in the **4.x** line on purpose — a neutral lane that
+> 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.*
+```
-- `eQuantic.Core.Data.EntityFramework.SqlServer`
-- `eQuantic.Core.Data.EntityFramework.PostgreSql`
-- `eQuantic.Core.Data.EntityFramework.MySql`
-- `eQuantic.Core.Data.EntityFramework.MongoDb`
+Swap the suffix for `PostgreSql`, `MySql` or `MongoDb` as needed.
-## Usage Examples
+## Learn more
-The following are examples of implementing the repository pattern:
+- [Repository Pattern walkthrough](Repository.md) — data entities, unit of work, repository and
+ specifications, end to end.
+- [eQuantic.Core.Data](https://github.com/eQuantic/core-data) — the contracts and the `QueryOptions` /
+ `PagedResult` / `PageRequest` query surface, backed by the
+ [eQuantic.Linq](https://github.com/eQuantic/core-linq) query engine.
-- [Repository Pattern Implementation](Repository.md)
+MIT © eQuantic Tech
diff --git a/Repository.md b/Repository.md
index 440a036..93c91b4 100644
--- a/Repository.md
+++ b/Repository.md
@@ -1,263 +1,336 @@
-# Repository Pattern with Entity Framework
+# Repository walkthrough (Entity Framework Core)
-## UnitOfWork example:
+An end-to-end slice built on **eQuantic.Core.Data v5** and this EF Core provider: data entities → a
+unit of work → repositories → specifications → a domain service that consumes them. You code against the
+provider-agnostic contracts (`IEntity`, `IQueryableUnitOfWork`, `QueryOptions`,
+`PageRequest`/`PagedResult`); the provider supplies the EF Core engine. The examples use SQL Server —
+PostgreSQL and MySQL are identical (swap the provider namespace and `UseSqlServer`), and MongoDB differs
+only in registration (see §3).
+
+## 1. Data entities — `IEntity`
+
+An entity is a plain class that implements `IEntity`. The key is exposed through `GetKey()`/
+`SetKey()` (there is no mandated `Id` property, though you will usually have one).
+
+```csharp
+using System;
+using eQuantic.Core.Data.Repository;
+
+public enum OrderStatus { Pending, Paid, Cancelled }
+
+public class CustomerData : IEntity
+{
+ public Guid Id { get; set; }
+ public string Name { get; set; } = string.Empty;
+ public bool IsVip { get; set; }
+
+ public Guid GetKey() => Id;
+ public void SetKey(Guid key) => Id = key;
+}
+
+public class OrderData : IEntity
+{
+ public Guid Id { get; set; }
+ public decimal Total { get; set; }
+ public OrderStatus Status { get; set; }
+ public DateTime CreatedAt { get; set; }
+
+ public Guid CustomerId { get; set; }
+ public CustomerData Customer { get; set; } = default!;
+
+ public Guid GetKey() => Id;
+ public void SetKey(Guid key) => Id = key;
+}
+```
+
+## 2. The DbContext
+
+A regular EF Core `DbContext` — the provider works with whatever context you already have.
```csharp
-using eQuantic.Core.Data.EntityFramework.Repository;
-using eQuantic.Core.Ioc;
using Microsoft.EntityFrameworkCore;
-namespace eQuantic.Core.Web.Examples.Infrastructure
+public class AppDbContext(DbContextOptions options) : DbContext(options)
{
- public class ExampleUnitOfWork : UnitOfWork
- {
- private readonly IContainer _container;
-
- public ExampleUnitOfWork(IContainer container, DbContext context) : base(context)
- {
- _container = container;
- }
-
- public override TRepository GetRepository()
- {
- return _container.Resolve();
- }
-
- public override TRepository GetRepository(string name)
- {
- return _container.Resolve(name);
- }
- }
+ public DbSet Orders => Set();
+ public DbSet Customers => Set();
}
```
-## Entity data example:
+## 3. The unit of work
+
+Derive the provider's `UnitOfWork`. Declaring your own interface (deriving
+`IQueryableUnitOfWork`) keeps call sites decoupled from the concrete type.
```csharp
using System;
+using eQuantic.Core.Data.EntityFramework.SqlServer.Repository; // provider base
using eQuantic.Core.Data.Repository;
-namespace eQuantic.Core.Web.Examples.Infrastructure.Data
+public interface IAppUnitOfWork : IQueryableUnitOfWork
{
- public class UserData : IEntity
- {
- public Guid Id { get; set; }
- public string UserName { get; set; }
- public string Password { get; set; }
- public string Email { get; set; }
- }
}
-namespace eQuantic.Core.Web.Examples.Infrastructure.Data
+public class AppUnitOfWork : UnitOfWork, IAppUnitOfWork
{
- public class PersonData : IEntity
+ public AppUnitOfWork(IServiceProvider serviceProvider, AppDbContext context)
+ : base(serviceProvider, context)
{
- public Guid Id { get; set; }
- public string Name { get; set; }
- public DateTime BirthDate { get; set; }
- public string Phone { get; set; }
- public virtual UserData User { get; set; }
}
}
```
-## Repository example:
+Register the context and the repositories. `AddRelationalRepositories` wires the unit of work, the generic
+repositories, and — because the relational unit of work implements it — the raw-SQL
+`ISqlUnitOfWork`.
-### Contract
+```csharp
+using eQuantic.Core.Data.EntityFramework.Relational.Repository.Extensions;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+
+services.AddDbContext(o => o.UseSqlServer(connectionString));
+services.AddRelationalRepositories();
+```
+
+No custom unit-of-work type? Use the provider's `DefaultUnitOfWork` (over a bare `DbContext`) instead.
+MongoDB has no SQL executor, so its unit of work derives
+`eQuantic.Core.Data.EntityFramework.MongoDb.Repository.UnitOfWork` and is registered with the
+SQL-agnostic `AddQueryableRepositories()`.
+
+## 4. Getting a repository
+
+Ask the unit of work for a repository over any `IEntity`. The queryable accessors resolve the
+generic repositories registered above:
+
+```csharp
+// asynchronous read + write:
+IAsyncRepository orders =
+ unitOfWork.GetAsyncRepository();
+
+// synchronous sibling:
+IRepository ordersSync =
+ unitOfWork.GetRepository();
+```
+
+All four accessors resolve from the generic registration above — the plain
+`GetAsyncRepository`/`GetRepository` shown here and the richer
+`GetAsyncQueryableRepository`/`GetQueryableRepository` variants (which add the `IQueryable`/`ISet` surface).
+Custom repositories (§8) layer your own named interface on top of the same wiring.
+
+## 5. Reading — one `QueryOptions`
+
+All query shaping — filtering, includes, sorting, tracking — is expressed through a single
+`QueryOptions`. Filters read like code and fail at compile time; clauses fold left to right.
+
+```csharp
+using eQuantic.Core.Data.Repository.Options;
+using eQuantic.Linq.Web; // FilterOperator
+
+var options = new QueryOptions()
+ .Where(o => o.Total, FilterOperator.GreaterThanOrEqual, 100m) // typed member selector
+ .And(o => o.Status, FilterOperator.Equal, OrderStatus.Paid) // (total>=100 AND paid)
+ .Or(o => o.Customer.IsVip, FilterOperator.Equal, true) // OR the customer is VIP
+ .OrderByDescending(o => o.CreatedAt)
+ .ThenBy("customer.name") // string path for dynamic columns
+ .Include(nameof(OrderData.Customer)) // eager load
+ .NoTracking();
+```
+
+Paged reads return a `PagedResult` — the items plus the totals needed to render a pager:
```csharp
-using System;
using eQuantic.Core.Data.Repository;
-using eQuantic.Core.Web.Examples.Infrastructure.Data;
-namespace eQuantic.Core.Web.Examples.Infrastructure.Repositories.Contracts
-{
- public interface IPersonRepository : IAsyncRepository
- {
- }
-}
+PagedResult page = await orders.GetPagedAsync(PageRequest.Of(pageIndex: 1, pageSize: 20), options);
+
+foreach (var order in page.Items) { /* ... */ }
+_ = page.TotalCount; // total across all pages
+_ = page.PageIndex; _ = page.PageSize; // echoed back
+_ = page.PageCount; // computed
+_ = page.HasPreviousPage; _ = page.HasNextPage;
```
-### Implementation
+The rest of the read surface follows the same shape — pass a `QueryOptions` (or none):
```csharp
-using System;
-using eQuantic.Core.Data.EntityFramework.Repository;
-using eQuantic.Core.Web.Examples.Infrastructure.Data;
-using eQuantic.Core.Web.Examples.Infrastructure.Repositories.Contracts;
+// by key (a non-null options routes through the key predicate + includes; otherwise DbSet.Find):
+OrderData? one = await orders.GetAsync(id, new QueryOptions().Include(nameof(OrderData.Customer)));
-namespace eQuantic.Core.Web.Examples.Infrastructure.Repositories
-{
- public class PersonRepository : AsyncRepository, IPersonRepository
- {
- public PersonRepository(ExampleUnitOfWork unitOfWork) : base(unitOfWork)
- {
- }
- }
-}
+// by predicate:
+IEnumerable big = await orders.GetFilteredAsync(o => o.Total >= 100m, new QueryOptions().NoTracking());
+
+// projection (server-side Select):
+IEnumerable summaries = await orders.GetMappedAsync(
+ o => new OrderSummary(o.Id, o.Total, o.Customer.Name),
+ new QueryOptions().Include(nameof(OrderData.Customer)));
+
+// paged projection:
+PagedResult summaryPage = await orders.GetPagedAsync(
+ PageRequest.Of(1, 20),
+ o => new OrderSummary(o.Id, o.Total, o.Customer.Name),
+ new QueryOptions().Include(nameof(OrderData.Customer)));
+
+// aggregates (Count returns long; Sum has an overload per numeric type):
+long paidCount = await orders.CountAsync(new QueryOptions().Where(o => o.Status, FilterOperator.Equal, OrderStatus.Paid));
+decimal paidTotal = await orders.SumAsync(o => o.Total, new QueryOptions().Where(o => o.Status, FilterOperator.Equal, OrderStatus.Paid));
+
+public record OrderSummary(Guid Id, decimal Total, string Customer);
```
-# DDD Pattern
+Every method has a synchronous twin on the queryable repository (`GetPaged`, `Get`, `Count`, `Sum`, …).
-## Domain Entity example:
+## 6. Specifications
+
+Encapsulate a reusable rule as an `ISpecification` (base class `Specification`).
```csharp
using System;
+using System.Linq.Expressions;
+using eQuantic.Linq.Specification;
-namespace eQuantic.Core.Web.Examples.Domain.Entities
+public sealed class PaidOrdersSpecification : Specification
{
- public class User
- {
- public ShortGuid Id { get; set; }
- public string UserName { get; set; }
- public string Password { get; set; }
- public string Email { get; set; }
- }
+ public override Expression> SatisfiedBy() => o => o.Status == OrderStatus.Paid;
}
+```
-namespace eQuantic.Core.Web.Examples.Domain.Entities
-{
- public class Person
- {
- public ShortGuid Id { get; set; }
- public string Name { get; set; }
- public DateTime BirthDate { get; set; }
- public string Phone { get; set; }
- public virtual User User { get; set; }
- }
-}
+Apply it either as the filter inside `QueryOptions`, or directly through `AllMatchingAsync`:
+
+```csharp
+var spec = new PaidOrdersSpecification();
+
+// inside QueryOptions (composes with sorting, includes, tracking, ...):
+var recentPaid = await orders.GetPagedAsync(
+ PageRequest.Of(1, 20),
+ new QueryOptions().Where(spec).OrderByDescending(o => o.CreatedAt));
+
+// or directly:
+IEnumerable allPaid = await orders.AllMatchingAsync(spec, new QueryOptions().NoTracking());
```
-## Specification Pattern
+## 7. Writing
+
+Writes are staged on the repository and persisted when the unit of work commits.
```csharp
-using System;
-using System.Linq.Expressions;
-using eQuantic.Core.Web.Examples.Infrastructure.Data;
-using eQuantic.Core.Linq.Specification;
+var order = new OrderData { Id = Guid.NewGuid(), Total = 150m, Status = OrderStatus.Pending, CreatedAt = DateTime.UtcNow };
-namespace eQuantic.Core.Web.Examples.Domain.Specification
-{
- public class PersonSpecification : Specification
- {
- private readonly string _term;
-
- public PersonSpecification(string term)
- {
- _term = term;
- }
- public override Expression> SatisfiedBy()
- {
- return p => p.Name.StartsWith(_term) || p.User.UserName.StartsWith(_term) || p.User.Email.StartsWith(_term);
- }
- }
-}
+await orders.AddAsync(order); // stage an insert
+await orders.AddRangeAsync(new[] { order1, order2 }); // stage several
+
+order.Status = OrderStatus.Paid;
+await orders.ModifyAsync(order); // mark modified
+
+await orders.RemoveAsync(order); // stage a delete
+
+int affected = await unitOfWork.CommitAsync(); // one round-trip, returns affected rows
```
-## Domain Services
+Set-based writes run directly in the database (EF `ExecuteUpdate`/`ExecuteDelete`) and do not need a
+commit:
+
+```csharp
+long cancelled = await orders.UpdateManyAsync(
+ o => o.Status == OrderStatus.Pending,
+ o => new OrderData { Status = OrderStatus.Cancelled });
+
+long removed = await orders.DeleteManyAsync(o => o.Total == 0m);
+```
-### Contract
+`DeleteManyAsync`/`UpdateManyAsync` also accept an `ISpecification` in place of the predicate.
+
+## 8. Custom repositories
+
+Need repository-specific methods, or the plain `IRepository`/`IAsyncRepository` shape resolved by
+`GetRepository`/`GetAsyncRepository`? Declare an interface and derive the generic base:
```csharp
-using System;
-using eQuantic.Core.Collections;
-using eQuantic.Core.Web.Examples.Domain.Entities;
+using eQuantic.Core.Data.EntityFramework.Repository;
+using eQuantic.Core.Data.Repository;
+
+public interface IOrderRepository : IRepository, IAsyncRepository
+{
+}
-namespace eQuantic.Core.Web.Examples.Domain.Services.Contracts
+public class OrderRepository : AsyncQueryableRepository, IOrderRepository
{
- public interface IPersonService
+ public OrderRepository(IQueryableUnitOfWork unitOfWork) : base(unitOfWork)
{
- Person Get(Guid id);
- bool Create(Person person);
- bool Update(Person person);
- bool Delete(Guid id);
- PagedList Find(string term, int pageIndex, int pageSize);
}
}
```
-### Implementation
+Register by scanning the assembly, then resolve through the unit of work (or inject `IOrderRepository`
+directly):
+
+```csharp
+using eQuantic.Core.Data.EntityFramework.Repository.Extensions;
+
+services.AddDbContext(o => o.UseSqlServer(connectionString));
+services.AddCustomRepositories(o => o
+ .AddLifetime(ServiceLifetime.Scoped)
+ .FromAssembly(typeof(OrderRepository).Assembly));
+
+// later:
+IAsyncRepository repo = unitOfWork.GetAsyncRepository();
+IRepository repoSync = unitOfWork.GetRepository();
+```
+
+## 9. A domain service
+
+Putting it together — a service consumes the unit of work and its repositories, keeping EF Core out of the
+domain layer.
```csharp
using System;
-using System.Collections.Generic;
-using AutoMapper;
-using eQuantic.Core.Collections;
-using eQuantic.Core.Linq;
-using eQuantic.Core.Web.Examples.Domain.Entities;
-using eQuantic.Core.Web.Examples.Domain.Specification;
-using eQuantic.Core.Web.Examples.Infrastructure;
-using eQuantic.Core.Web.Examples.Infrastructure.Data;
-using eQuantic.Core.Web.Examples.Infrastructure.Repositories.Contracts;
-
-namespace eQuantic.Core.Web.Examples.Domain.Services
+using System.Threading;
+using System.Threading.Tasks;
+using eQuantic.Core.Data.Repository;
+using eQuantic.Core.Data.Repository.Options;
+using eQuantic.Linq.Web;
+
+public class OrderService
{
- public class PersonService : IPersonService
- {
- public IMapper Mapper { get; }
- public ExampleUnitOfWork UnitOfWork { get; }
-
- public PersonService(IMapper mapper, ExampleUnitOfWork unitOfWork)
- {
- Mapper = mapper;
- UnitOfWork = unitOfWork;
- }
-
- public Person Get(Guid id)
- {
- Person person = null;
- var repo = UnitOfWork.GetRepository();
- var item = repo.Get(id, p => p.User);
- if (item != null) person = Mapper.Map(item);
-
- return person;
- }
-
- public bool Create(Person person)
- {
- var repo = UnitOfWork.GetRepository();
- var item = Mapper.Map(person);
- repo.Add(item);
- return UnitOfWork.Commit() > 0;
- }
-
- public bool Update(Person person)
- {
- var repo = UnitOfWork.GetRepository();
- var item = repo.Get(person.Id);
- Mapper.Map(person, item);
- repo.Modify(item);
- return UnitOfWork.Commit() > 0;
- }
-
- public bool Delete(Guid id)
- {
- var repo = UnitOfWork.GetRepository();
- var item = repo.Get(id);
- repo.Remove(item);
- return UnitOfWork.Commit() > 0;
- }
-
- public PagedList Find(string term, int pageIndex, int pageSize)
- {
- var repo = UnitOfWork.GetRepository();
- var specification = new PersonSpecification(term);
- var count = repo.Count(specification);
- var items = repo.GetPaged(specification, pageIndex, pageSize,
- new[] {new Sorting {Column = c => c.Name}}, p => p.User);
- var persons = Mapper.Map>(items);
- return new PagedList(persons, count){ PageIndex = pageIndex, PageSize = pageSize};
- }
- }
-}
-```
+ private readonly IAppUnitOfWork _unitOfWork;
-## Advanced Features (v4.4.0)
+ public OrderService(IAppUnitOfWork unitOfWork) => _unitOfWork = unitOfWork;
-### .NET 10 ExecuteUpdate Support
+ public Task GetAsync(Guid id, CancellationToken ct = default) =>
+ _unitOfWork.GetAsyncRepository()
+ .GetAsync(id, new QueryOptions().Include(nameof(OrderData.Customer)), ct);
-Starting with version 4.4.0, the repository fully supports .NET 10's `ExecuteUpdate` with the new `UpdateSettersBuilder` syntax. The `ExpressionConverter` automatically handles the transformation of anonymous object or member init expressions into the optimized EF Core format.
+ public async Task CreateAsync(OrderData order, CancellationToken ct = default)
+ {
+ var repo = _unitOfWork.GetAsyncRepository();
+ await repo.AddAsync(order, ct);
+ await _unitOfWork.CommitAsync(ct);
+ return order.Id;
+ }
-### Resource Management
+ public async Task DeleteAsync(Guid id, CancellationToken ct = default)
+ {
+ var repo = _unitOfWork.GetAsyncRepository();
+ var order = await repo.GetAsync(id, cancellationToken: ct);
+ if (order is null) return false;
-The `UnitOfWork` implementation now includes internal resource tracking to ensure that database connections and related resources are properly disposed of, even when multiple repositories are shared within the same scope.
+ await repo.RemoveAsync(order);
+ return await _unitOfWork.CommitAsync(ct) > 0;
+ }
+
+ public Task> FindAsync(decimal minTotal, int pageIndex, int pageSize, CancellationToken ct = default) =>
+ _unitOfWork.GetAsyncRepository()
+ .GetPagedAsync(
+ PageRequest.Of(pageIndex, pageSize),
+ new QueryOptions()
+ .Where(o => o.Total, FilterOperator.GreaterThanOrEqual, minTotal)
+ .OrderByDescending(o => o.CreatedAt)
+ .Include(nameof(OrderData.Customer))
+ .NoTracking(),
+ ct);
+
+ public Task RevenueAsync(CancellationToken ct = default) =>
+ _unitOfWork.GetAsyncRepository()
+ .SumAsync(o => o.Total, new QueryOptions().Where(o => o.Status, FilterOperator.Equal, OrderStatus.Paid), ct);
+}
+```
diff --git a/build/Directory.Build.targets b/build/Directory.Build.targets
deleted file mode 100644
index 486eff8..0000000
--- a/build/Directory.Build.targets
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/build/MSBump.props b/build/MSBump.props
deleted file mode 100644
index 21486c9..0000000
--- a/build/MSBump.props
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/build/MSBump.targets b/build/MSBump.targets
deleted file mode 100644
index 66d54cc..0000000
--- a/build/MSBump.targets
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- $(MSBumpNewVersion)
- $(MSBumpNewVersion)
-
-
-
-
-
-
-
-
- True
-
-
-
\ No newline at end of file
diff --git a/docs/IMPROVEMENT_PLAN.md b/docs/IMPROVEMENT_PLAN.md
new file mode 100644
index 0000000..9350575
--- /dev/null
+++ b/docs/IMPROVEMENT_PLAN.md
@@ -0,0 +1,331 @@
+# Improvement Plan — eQuantic.Core.Data.EntityFramework
+
+> 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.
+
+## Executive summary
+
+The package works and has been published on nuget.org for years (106 versions of the main package, 57 of
+the contract), but it accumulated debt across five layers. In order of severity:
+
+1. **Security (critical):** `SqlExecutor` builds SQL by interpolating values without escaping — a real SQL
+ injection in `ExecuteFunction`/`ExecuteProcedure` across the SqlServer, PostgreSql and MySql providers.
+ In addition, CI publishes to nuget.org on every push to any branch.
+2. **Correctness (critical/high):** double-dispose of the `UnitOfWork`, the MongoDb provider operating
+ against the wrong database (silently writing/deleting), `EXEC` (T-SQL) copied to PostgreSQL/MySQL,
+ parameters lost in `FromSqlRaw`, `configuration` discarded in `All`/`Any`, a DI registration that throws
+ at runtime for MongoDb, and bulk updates that silently write the wrong values.
+3. **Packaging/versioning (critical for consumers):** the same `PackageId` is published as parallel version
+ lines (4.x multi-target + 6.x/7.x/8.x/9.x/10.x single-target). NuGet treats it all as a single timeline:
+ "latest" = 10.0.2 (net10-only), breaking restore for anyone on net6–net9 and making the 4.x line (the
+ most complete) look abandoned. There are 21 hand-maintained csproj files, and the scheme already produced
+ real dependency-graph bugs.
+4. **Structural duplication:** ~2,400 lines are identical copies between providers — PostgreSql and MySql
+ are 100% renamed copies of SqlServer. That is the root cause of the `EXEC`→`CALL` bug (copied without
+ adapting the dialect).
+5. **Contracts (`eQuantic.Core.Data`):** combinatorial interface explosion
+ (`IAsyncReadRepository` has **100 members**; `SumAsync` alone is 30 overloads),
+ NRT disabled, and pagination returning `IEnumerable` with no total count.
+
+The plan below is organized into **5 phases**. Phases 0–2 are **non contract-breaking** and can ship on the
+current line. Phases 3–4 define the **v5.0.0 of the contracts** (deliberately breaking) and the versioning
+strategy on nuget.org.
+
+## Implementation status (Phase 1 — done)
+
+Fixes already applied on this branch, **with tests** (28 tests passing; all packages build —
+base/Relational/SqlServer/PostgreSql/MySql on net10, MongoDb on net8):
+
+| Finding | Fix | Commit |
+|--------|----------|--------|
+| **S1** SQL injection + **P4** + **P3** | Parameterized `SqlExecutor` (placeholders instead of interpolating values) across the 3 SQL providers; `EXEC`→`CALL` on PG/MySql; `.ToArray()` in `ExecuteQuery`. Tests prove the malicious value never reaches the SQL. | `fix(sql)` |
+| **C3** DI crash | `ISqlUnitOfWork` registered only when the implementation provides it; removed the duplicate `IQueryableUnitOfWork` registration. | `fix(core)` |
+| **C1** double-dispose | Unified `_disposed` flag (`protected`); the UoW is disposed exactly once. | `fix(core)` |
+| **A1** configuration discarded | Sync `All`/`Any` forward `configuration`. | `fix(read)` |
+| **A2** default key | `Get`/`GetAsync` validate `id is null` instead of `default(TKey)`. | `fix(read)` |
+| **P1/P6** MongoDb | Uses the database configured on the `DbContext` (not the collection name); throws instead of returning a silent `0`. | `fix(mongodb)` |
+| **A4/M7** key | Key value parameterized (closure) instead of a literal; PK metadata cached; `EF.Property` for shadow keys; throws on a partial composite key. | `fix(core)` |
+| **M4** DI | `AddRepository` honours the configured lifetime, uses `TryAdd`, and tolerates `ReflectionTypeLoadException`. | `fix(di)` |
+| **M2** async | `ConfigureAwait(false)` across 91 library awaits (base + 4 providers). | `perf(async)` |
+| **P2** MongoDb | `UpdateDefinitionBuilder` rejects (throws on) update expressions that reference the entity instead of silently writing a constant. New MongoDb test project. | `fix(mongodb)` |
+| **C2** ownership ⚠️ | The repository no longer disposes the injected `UnitOfWork` (the creator — DI container or caller — owns the lifetime). **Behavioural change.** | `fix(core)` |
+| **A5** pagination | `GetPaged`/`GetPagedAsync` order by the primary key when no explicit ordering is given (deterministic pagination); the caller's ordering is preserved. | `fix(read)` |
+
+The placebo test (`Assert.Pass()`) was replaced with real coverage: DI, disposal, SQL parameterization,
+queries, the key expression, pagination and the MongoDb `UpdateDefinitionBuilder`, via EF InMemory.
+
+⚠️ **C2 is the only behavioural change in this batch.** Anyone who relied on disposing the repository to
+close a manually-created context must now dispose the `UnitOfWork`/`DbContext` directly (the DI container
+already does this). Documented in the commit.
+
+**Investigated and corrected during diagnosis:** plan item **M8** (remove `Where(_ => true)` in
+`GetAllAsync`) was **wrong** — that `Where` is load-bearing (`SetBase` does not implement
+`IAsyncEnumerable`; the `Where` turns it into a real EF `IQueryable`). Documented in code + a regression
+test; nothing removed.
+
+Note on **P2**: the current fix **rejects** update expressions that reference the entity (avoids silent
+corruption). Actually supporting them via `$inc`/pipeline updates is left for a future MongoDb iteration.
+
+## Implementation status (Phase 0 — done, reduced scope)
+
+| Finding | Fix |
+|--------|----------|
+| **PK7** dead MSBump | Removed `build/MSBump.props` (circular self-import), `build/MSBump.targets` (missing task) and `build/Directory.Build.targets` (outside any ancestor chain — never imported). Nothing referenced them; verified by grep before deleting. |
+| **PK2** graph bug | `MySql.Net10.csproj` referenced the **Net9** base instead of **Net10** — fixed. |
+| **Q1** accidental publishing | CI split into **`ci.yml`** (build + test on every push/PR, publishing nothing) and **`release.yml`** (only on a `vX.Y.Z` tag, publishing behind a `nuget-release` GitHub Environment). |
+| **Q2** no tests in CI | `ci.yml` runs `dotnet test` on the 3 test projects (previously none ran). |
+| **Q3** dated pipeline | Actions updated (`checkout@v4`, `setup-dotnet@v4`), `ubuntu-latest` instead of `windows-latest`, NuGet cache, `-p:ContinuousIntegrationBuild=true`. |
+| **Q4** unpinned SDK | `global.json` at the root pinning `10.0.100` with `rollForward: latestFeature`. |
+
+**Design decision (matrix build, not `dotnet build sln`):** I tried running
+`dotnet build eQuantic.Core.Data.EntityFramework.sln` locally to simplify the 23 build steps — and
+reproduced exactly the **PK4** finding: because several `.csproj` files of the same package
+(`eQuantic.Core.Data.EntityFramework.csproj`, `.Net6.csproj`, `.Net7.csproj`, …) share the same folder
+without their own `BaseIntermediateOutputPath`, the parallel solution build corrupted each other's
+`project.assets.json` and hit a file-in-use `IOException`. The two new workflows keep the builds
+**individual** (as the old workflow already did), but each one runs as a **matrix job** — i.e. an isolated
+runner/checkout — which eliminates the shared `obj`/`bin` without having to settle the versioning decision
+(Part IV) first.
+
+⚠️ **Two things only a maintainer with GitHub access can finish:**
+1. **Nothing publishes automatically until a tag exists.** Before, any push to any branch attempted to
+ publish (mitigated only by `--skip-duplicate`). Now you need `git tag vX.Y.Z && git push origin vX.Y.Z`
+ to trigger `release.yml`. This is intentional (finding Q1) but changes the workflow.
+2. **The `environment: nuget-release` referenced by `release.yml` has no protection until configured.**
+ GitHub auto-creates the Environment on first use with no required reviewers or branch/tag restriction —
+ this session's PR tool has no permission to configure it. In *Settings → Environments → nuget-release*,
+ add at least one required reviewer for the gate to actually work.
+
+**Deliberately out of scope for Phase 0** (not done, because it depends on the still-open versioning
+decision — Part IV): adopting MinVer (would change how each of the 23 csproj `` values is
+determined) and any change to the published version numbers. Doing that now, before choosing between
+consolidating to a single line (option A) and keeping per-.NET `PackageId`s (option B), would risk rework.
+
+## Implementation status (Phase 2 — done)
+
+Extracted the shared relational implementation into a **single package**,
+`eQuantic.Core.Data.EntityFramework.Relational`, referenced by the 3 SQL providers.
+
+| Finding | Fix |
+|--------|----------|
+| **Structural duplication** (§4) | `RelationalSqlExecutor`, `RelationalUnitOfWork`/`RelationalUnitOfWork`, `RelationalSet` and the internal `ExpressionConverter`/`SqlConfigurationExtensions` now live once in the shared package. Each provider keeps thin `Set` and `UnitOfWork` subclasses plus `DefaultUnitOfWork`, so the consumer-facing types stay in their namespaces. **~2,200 fewer lines of duplicated source.** |
+| **P3 root cause** | The only genuine dialect difference — stored procedures use `EXEC` on SQL Server and the ANSI `CALL` elsewhere — is a single `BuildProcedureSql` virtual, overridden only by SQL Server. The copy-paste that let `EXEC`/`CALL` diverge is gone. |
+| **PK3 (partial)** | MySql's per-framework variants are realigned to reference the multi-target base project (matching SqlServer/PostgreSql) so the shared multi-target project does not pull a second copy of the base assembly. |
+
+**Why a new package rather than the base package or shared source** (the plan originally proposed "base
+package with dialect hooks", which the deeper analysis showed to be wrong):
+- The base package must **not** depend on `Microsoft.EntityFrameworkCore.Relational` — the MongoDb provider
+ references the base and is not relational (it depends only on `Microsoft.EntityFrameworkCore` core +
+ `MongoDB.Driver`). Putting relational code in the base would add `Relational` to every MongoDb consumer.
+- **Linked source** (``) does not work for public types shared across providers: the type
+ would be compiled into every provider assembly and collide if a consumer references two providers at once
+ (a scenario the library supports).
+- Changing the types' **namespace** would break the public API — that belongs to the v5 work.
+
+A separate shared assembly is therefore the only clean option. It is multi-target only (net6–net10, one
+version line) — no per-framework variants needed, because SqlServer/PostgreSql already reference the base
+multi-target project everywhere, so the shared multi-target project composes with them with a single base
+assembly (no duplicate).
+
+Note: the implementation-only public types `SqlExecutor`, the non-generic `UnitOfWork` and
+`SqlConfigurationExtensions` move to the `Relational` namespace, and `GetEntityByIdSpecification` now takes
+`RelationalUnitOfWork`. These are implementation types (not the consumer-facing `DefaultUnitOfWork` /
+`UnitOfWork` / `Set`), but referencing them by name is a minor source break.
+
+**What a maintainer still owns:** the new `eQuantic.Core.Data.EntityFramework.Relational` package is a new
+published package the SQL providers now depend on. Its version (`1.0.0`) and how it fits the versioning
+scheme is part of the Part IV decision.
+
+**Next steps** (larger, architectural/breaking phases — awaiting a chosen approach):
+- **Phase 3/4** — v5.0.0 of the contracts (`eQuantic.Core.Data`) and consolidation of the version lines on
+ nuget.org (see Parts III and IV) — this includes the versioning decision that also blocks MinVer.
+
+---
+
+## Part I — Diagnosis
+
+### 1. Security
+
+| # | Sev. | Problem | Location |
+|---|------|----------|-------|
+| S1 | 🔴 Critical | **SQL injection**: `GetQueryParameters` interpolates values with `string.Format(" '{0}'", value)` without escaping single quotes; `string`/`Guid`/`DateTime` go raw into the SQL text, which reaches `FromSqlRaw`/`ExecuteSqlRaw` **with no `DbParameter`**. The function/procedure `name` is interpolated too. Identical file across the 3 SQL providers. | `SqlServer/Repository/SqlExecutor.cs:367,375-407` (same in PostgreSql and MySql) |
+| S2 | 🟡 Medium | NuGet key exposed to any push: the workflow publishes with `secrets.nuget_key` on a push to **any branch**, with no protected environment and no tag/release gate. | `.github/workflows/dotnetcore.yml:3,66-67` |
+
+**S1 fix:** emit placeholders (`@p0`/`$1`/`?`) and pass real `DbParameter`s — the infrastructure already
+exists in the same file (`SetCommand`, `SqlExecutor.cs:419-445`) and is simply ignored by those methods.
+
+### 2. Correctness bugs — Core (`eQuantic.Core.Data.EntityFramework`)
+
+| # | Sev. | Problem | Location |
+|---|------|----------|-------|
+| C1 | 🔴 | **UnitOfWork double-dispose**: `AsyncQueryableRepository.Dispose(bool)` calls `base.Dispose()` (which already disposes the UoW) and disposes the UoW again — caused by a shadowed `_disposed` field on the derived class. | `Repository/AsyncQueryableRepository.cs:756-773` + `Repository/QueryableRepository.cs:363-378` |
+| C2 | 🔴 | **Inverted UoW ownership**: repositories dispose the **injected** UnitOfWork; with `AddGenericRepositories` the container disposes it too → dead DbContext for the other repositories in the scope. | `Repository/QueryableRepository.cs:374`; `Read/QueryableReadRepository.cs:22`; `Write/WriteRepository.cs:15` |
+| C3 | 🔴 | **DI registration throws at runtime**: `ISqlUnitOfWork` is registered unconditionally even when the implementation does not implement it (MongoDb) → `InvalidCastException` on resolve. Line 75 also duplicates the `IQueryableUnitOfWork` registration (dead code). | `Repository/Extensions/ServiceCollectionExtensions.cs:74-75` |
+| A1 | 🟠 | **Sync `All`/`Any` discard `configuration`**: `return this.All(specification.SatisfiedBy());` ignores includes/no-tracking/sorting. The async variants do it right — proof it is a bug, not design. | `Read/QueryableReadRepository.cs:208,233` |
+| A2 | 🟠 | **`Get(id)` rejects valid default keys with the wrong exception**: `if (Equals(id, default(TKey))) throw new ArgumentNullException` — `Get(0)`/`Guid.Empty` throw on a non-null argument. | `Read/QueryableReadRepository.cs:254-257`; `Read/AsyncQueryableReadRepository.cs:357-360` |
+| A3 | 🟠 | **Sync deferred vs async materialized**: sync `GetAll`/`GetFiltered`/`GetPaged` return a live `IQueryable` disguised as `IEnumerable` (double enumeration = 2 queries; late `ObjectDisposedException`), while the async ones do `ToListAsync`. Same method, divergent semantics. | `Read/QueryableReadRepository.cs:47,271,299,396` vs `Read/AsyncQueryableReadRepository.cs:44,336,732` |
+| A4 | 🟠 | **Key as `Expression.Constant`**: `GetFindByKeyExpression` embeds the key value in the tree → EF does not parameterize; each id creates a new query-cache entry and SQL with a literal (plan-cache pollution). | `Repository/Extensions/DbContextExtensions.cs:25,43` |
+| A5 | 🟠 | **Pagination without `OrderBy`**: `Skip/Take` with no guaranteed ordering → non-deterministic pages + EF's `RowLimitingOperationWithoutOrderBy` warning. | `Read/QueryableReadRepository.cs:395`; `Read/AsyncQueryableReadRepository.cs:729` |
+| M-core | 🟡 | Several: NRT disabled across the package; **zero `ConfigureAwait(false)`** in the whole library; `SumAsync` (~24 overloads) with no `CancellationToken` and no null validation; `AddRepository` ignores the configured lifetime; uncached reflection on the hot `Get(id, config)` path; `GetAllAsync` injects a redundant `Where(_ => true)`; inconsistent tracking between `Get(id)` (uses `Find`) and `Get(id, config)` (uses a query). | see the per-file detailed report |
+
+### 3. Correctness bugs — Providers
+
+| # | Sev. | Problem | Location |
+|---|------|----------|-------|
+| P1 | 🔴 | **MongoDb operates against the wrong database**: `GetDatabase(_collectionName)` uses the **collection** name as the **database** name → `DeleteMany`/`UpdateMany` run against a non-existent database and return `0` **silently**. | `MongoDb/Repository/Set.cs:129-131` |
+| P2 | 🔴 | **MongoDb `UpdateDefinitionBuilder` writes a constant**: `x => new E { Count = x.Count + 1 }` is compiled and invoked against `Activator.CreateInstance(...)` (a default instance) → writes `0+1=1` on every document instead of incrementing. Silent corruption. | `MongoDb/UpdateDefinitionBuilder.cs:38-43` |
+| P3 | 🟠 | **`EXEC` (T-SQL) copied to PG/MySQL**: `GetQueryProcedure` returns `$"EXEC {name}..."`; PostgreSQL/MySQL require `CALL` → `ExecuteProcedure` fails at runtime. Direct evidence of the copy-paste. | `PostgreSql/Repository/SqlExecutor.cs:367`; `MySql/…:367` |
+| P4 | 🟠 | **`FromSqlRaw` receives an `IEnumerable` as 1 parameter**: `FromSqlRaw(sql, configuration.Parameters.Select(p => p.Value))` — the `Select` becomes a single element of the `params object[]`. Missing `.ToArray()`. | `SqlExecutor.cs:219` (3 SQL providers) |
+| P5 | 🟠 | **Fragile `ExpressionConverter`**: `RewriteBinding` does not rebind the parameter → updates referencing the entity throw at runtime; `GetMethods().…Single(...)` breaks if EF adds a `SetProperty` overload; no cache. | `SqlServer/ExpressionConverter.cs:50-68,137-155` (3 SQL providers) |
+| P6 | 🟡 | **MongoDb: silent returns**: without an `IMongoClient` in DI, `GetCollection()` returns null and the bulk ops return `0` without throwing. `IsSimpleType` does not cover `Guid`/`DateTimeOffset`/collections → discarded updates or `TargetParameterCountException`. | `MongoDb/Repository/Set.cs:29-65`; `UpdateDefinitionBuilder.cs:45-73` |
+| P7 | 🟡 | **`SqlExecutor.Dispose` destroys the injected `DbContext`** (double-dispose in the DI scope). `IsMigrating` is a `static` shared across every context in the process. Unbounded `do/while` retry loop in `CommitAndRefreshChanges`. | `SqlExecutor.cs:483-497`; `UnitOfWork.cs:17,43-91` |
+| P8 | 🟡 | **Orphaned `GetEntityByIdSpecification`**: exists only in SqlServer, nothing in it is SQL Server-specific (it delegates to the base `DbContextExtensions`), zero uses in the repo. Should be in the base or deprecated. | `SqlServer/Specifications/GetEntityByIdSpecification.cs` |
+
+### 4. Structural duplication
+
+| File | SqlServer | PostgreSql | MySql | MongoDb |
+|---|---|---|---|---|
+| `SqlExecutor.cs` | 498 lines | **identical** | **identical** | — |
+| `UnitOfWork.cs` | 266 | **identical** | **identical** | ~180 same |
+| `ExpressionConverter.cs` | 175 | **identical** | **identical** | — |
+| `Set.cs` | 287 | 268 (= SqlServer minus the legacy blocks) | **identical to PG** | ~50 same |
+| `SqlConfigurationExtensions.cs` | 14 | **identical** | **identical** | — |
+
+**~2,400–2,500 redundant lines.** The only genuine dialect differences in `SqlExecutor` are 2 lines
+(`GetQueryFunction`/`GetQueryProcedure`). It is not merely cosmetic debt: the P3 bug (`EXEC`→`CALL`) exists
+precisely because the file was copied without adapting the dialect.
+
+### 5. Packaging and versioning
+
+| # | Sev. | Problem | Location |
+|---|------|----------|-------|
+| PK1 | 🔴 | **Parallel version lines on the same `PackageId`**: 4.x multi-target + 6.x/7.x/8.x/9.x/10.x single-target. NuGet sees a single timeline → `dotnet add package` pulls 10.0.2 (net10-only) and breaks restore on net6–net9; Dependabot suggests an impossible upgrade; the major stops meaning "breaking" (it means TFM). | 21 csproj in `src/`; confirmed on nuget.org |
+| PK2 | 🔴 | **Dependency-graph bug**: `MySql.Net10.csproj` references the **Net9** core → the MySql 10.0.2 package declares a dependency on core `>= 9.1.2` (net9-only). SqlServer/PG/MongoDb NetX reference the multi-target core 4.4.2 — the families are already crossed. | `MySql/…MySql.Net10.csproj:66-67` |
+| PK3 | 🔴 | **Pomelo 9 over EF Core 10**: MySql net10.0 uses `Pomelo.EntityFrameworkCore.MySql 9.0.0` (built for EF 9) with `Microsoft.EntityFrameworkCore 10.0.3`. There is no stable Pomelo 10 — risk of binary incompatibility. | `MySql/…MySql.csproj` (net10 block) |
+| PK4 | 🟠 | **Shared `obj/`/`bin/` directories**: several csproj files in the same folder without `BaseIntermediateOutputPath` → `project.assets.json` overwritten on each restore; parallel builds (`dotnet build -m` of the solution) are a declared race. | `src/*/` with multiple csproj |
+| PK5 | 🟠 | **MongoDb main package misaligned**: `Version 8.1.2.0`, `net8.0` only — there is no MongoDb in the 4.x family or a multi-target one; a net9/net10 consumer gets the net8 build. | `MongoDb/…MongoDb.csproj:7,9` |
+| PK6 | 🟠 | **Rolling `AssemblyVersion`** (changes on each patch) → in a diamond between providers compiled against different core lines, risk of `MissingMethodException`/`FileLoadException`. | all csproj `:27` |
+| PK7 | 🟡 | **Dead and broken MSBump**: `build/MSBump.props` imports itself (circular), `MSBump.targets` calls `BumpVersion` with no `UsingTask`, `Directory.Build.targets` is in `build/` (not an ancestor of `src/`, never applied), and its own comment says it is obsolete since NuGet 4.6. None of it is imported today. When it worked, it produced non-deterministic per-build versions. | `build/*` |
+
+### 6. Contracts (`eQuantic.Core.Data`)
+
+| # | Sev. | Problem | Location |
+|---|------|----------|-------|
+| K1 | 🟠 | **Combinatorial explosion**: `IAsyncReadRepository` = **100 members**; `IReadRepository` = 56; `ISqlUnitOfWork` ≈ 47 (impossible to implement by hand — defeats the purpose of a contract). `GetPagedAsync` = 18 overloads; `SumAsync` = 30. The most recent commit *added* 60 Sum overloads — the trend is worsening. | `Read/IAsyncReadRepository.cs`; `Sql/ISqlUnitOfWork.cs` |
+| K2 | 🟠 | **`TUnitOfWork` as a type parameter** enables a single member (`UnitOfWork { get; }`) but contaminates the whole hierarchy (~24 interfaces for 1 concept) and creates a circular UoW↔repository coupling. Inconsistent variance sync vs async. | `Repository/IRepository.cs:26,55`; `IAsyncRepository.cs:39` |
+| K3 | 🟠 | **Pagination without metadata**: `GetPaged*` returns raw `IEnumerable`, with no total/page → the consumer does a separate `Count()` (2 non-atomic round-trips). A `PagedResult` is missing. | `Read/IReadRepository.cs:267-314` |
+| K4 | 🟡 | **EF leaking into the contract**: `ISqlUnitOfWork` declares `GetPendingMigrations`/`UpdateDatabase`/`Attach` — contradicting the "persistence ignorance" its own XML docs claim. `IdentityGenerator` and `MigrationAttribute` are implementation in a contracts package. | `Sql/ISqlUnitOfWork.cs:23-127`; `IdentityGenerator.cs`; `Migration/MigrationAttribute.cs` |
+| K5 | 🟡 | **`CancellationToken` missing** on 30 `SumAsync`, `AddAsync`/`MergeAsync`/`ModifyAsync`/`RemoveAsync`, `LoadCollectionAsync`. NRT disabled (`Get*` returns `Task` with no null annotation). No `IAsyncEnumerable`/streaming. | `Read/IAsyncReadRepository.cs`; `Write/IAsyncWriteRepository.cs` |
+| K6 | 🟡 | **`new()` constraint everywhere** (hostile to DDD) and `IEntity`/`IEntity` do not tie the repository's `TKey` to the entity's real key (`IRepository` compiles even if the key is `int`). Should be `where TEntity : IEntity`. | `Repository/IRepository.cs:40` |
+| K7 | 🟢 | **Latent bug**: `IdentityGenerator.GuidRegex` contains 2 invisible zero-width characters (U+200C and U+200B) inside the `[0-9…a-fA-F]{12}` class — a copy-paste artifact (confirmed by a byte dump). Dead `eQuantic.Core` dependency (no file imports it). Typo `mintute` in `MigrationAttribute`. | `IdentityGenerator.cs:7`; `core-data.csproj` |
+
+### 7. Process, CI and tests
+
+| # | Sev. | Problem | Location |
+|---|------|----------|-------|
+| Q1 | 🔴 | **CI publishes on any push, without tests**: `on: [push]` → `dotnet nuget push` on every push to any branch, with the NuGet key. **No `dotnet test`** runs before publishing. | `.github/workflows/dotnetcore.yml:3,66-67` |
+| Q2 | 🔴 | **Placebo tests**: `UnitTest1.cs` is an `Assert.Pass()`. There is no coverage of any bug above. | `tests/…Tests/UnitTest1.cs` |
+| Q3 | 🟡 | 21 sequential `dotnet build` steps instead of the solution; outdated actions (`checkout@v3`, `setup-dotnet@v3`); unnecessary `windows-latest`; no `dotnet test`, cache, `global.json`, deterministic pack (`ContinuousIntegrationBuild`), symbols (snupkg) or provenance. | `dotnetcore.yml` |
+| Q4 | 🟡 | No central `Directory.Build.props`/`Directory.Packages.props`: metadata and package versions repeated across 21 csproj (the real source of PK2/PK3). No consistent NRT/`GenerateDocumentationFile` (packages ship to NuGet with no IntelliSense). The README says "Version 4.4.0" and does not explain the version matrix; `Repository.md` uses `IContainer`/pre-DI service-locator. | repo root |
+
+---
+
+## Part II — Phased execution plan
+
+### Phase 0 — Harden the pipeline (days, no production code) — **done**
+
+Prerequisite for everything: stop publishing by accident and have a safety net.
+
+1. **Split CI from Release.** `ci.yml` on `push`/`pull_request`: `restore` → `build` → **`dotnet test`** →
+ `dotnet pack` as an artifact (no push). `release.yml` only on `push: tags: ['v*']`, with the
+ `nuget_key` in a **protected GitHub Environment**.
+2. Update the actions to v4; move to `ubuntu-latest`; add a NuGet cache and `global.json`.
+3. **Delete `build/`** (dead MSBump). MinVer adoption is deferred until the versioning decision (Part IV).
+
+### Phase 1 — Non contract-breaking fixes (current line, patch/minor) — **done**
+
+Ship now, without touching `eQuantic.Core.Data`. Each covered by a test. See the status table above.
+
+### Phase 2 — Structural de-duplication (minor, internal refactor) — **done**
+
+Extracted the shared relational implementation into a new
+`eQuantic.Core.Data.EntityFramework.Relational` package (see the status section above for why a new package
+rather than the base package). Each provider becomes thin subclasses + a dialect override. Removes ~2,200
+lines and kills the P3 bug class at the root. **Consumer-facing types keep their namespaces**; only a few
+implementation-only types move.
+
+### Phase 3 — Contracts redesign v5.0.0 (deliberately breaking — see Part III)
+
+Consolidate the surface via *options objects*, introduce `PagedResult`, a uniform `CancellationToken`,
+annotated NRT, `where TEntity : IEntity`, remove `TUnitOfWork` from the hierarchy and the EF-specific
+content from the contracts. Reimplement on the EF package (which becomes ~10× smaller).
+
+### Phase 4 — Migration on nuget.org (see Part IV)
+
+Consolidate the version lines, deprecate the old ones without breaking anyone who already depends on them,
+and publish the compatibility matrix.
+
+---
+
+## Part III — Changes requiring a contract break in `eQuantic.Core.Data`
+
+General rule for the contracts package: **adding** a member to an interface already breaks every external
+implementer (mocks, fakes, decorators, plus the EF impl itself), and **changing a signature/removing**
+breaks callers too. Almost every fundamental improvement is therefore a v5.0.0. What requires breaking:
+
+1. **`CancellationToken` on the members that lack it** (30 `SumAsync`, `AddAsync`/`MergeAsync`/`ModifyAsync`/`RemoveAsync`, `LoadCollectionAsync`). Partial non-breaking route: *default interface methods* (DIM) delegating to the existing overload — viable because all TFMs are ≥ net6.0, but it crystallizes the overload explosion.
+2. **Consolidate overloads into options objects** (`QueryOptions` absorbing filter/specification/config; `PageRequest`): remove the 18 `GetPagedAsync`, the 60 `Sum*` etc. Reduces `IAsyncReadRepository` from 100 to ~12 members. This is the heart of v5.
+3. **`PagedResult` instead of `IEnumerable`** for pagination: a return-type change — hard breaking (not even DIM saves it; would require a new method with a different name, e.g. `QueryPagedAsync`).
+4. **Remove `TUnitOfWork` from the hierarchy** (collapse `IRepository` into `IRepository` + `IUnitOfWork UnitOfWork { get; }`): removes ~10 public interfaces; the EF package references those arities in `GetRepository`.
+5. **`where TEntity : IEntity` constraint** and/or removing `new()`: changes generic constraints — source+binary breaking.
+6. **Split sync/async in `IUnitOfWork`** and **remove `ExecuteTransactionAsync` from `ISqlExecutor`** (an async method on the "sync" interface).
+7. **Move `GetPendingMigrations`/`UpdateDatabase`/`MigrationAttribute`/`IdentityGenerator` to the EF package**: removes public types/members from the contract — a real break (the contract→EF direction rules out `[TypeForwardedTo]`).
+8. **Annotated NRT** (`TEntity?` on `Get`/`GetFirst`/`GetSingle`): technically only produces new warnings — the cheapest break; requires both packages to be annotated together to stay consistent.
+
+**Recommendation:** treat the next contract version as a **deliberately breaking v5.0.0** and reimplement
+the EF package on top of it, rather than stacking DIMs over a 100-member surface. The EF package's
+maintenance cost — today implementing ~156 members per provider × 4 providers — drops by an order of
+magnitude.
+
+---
+
+## Part IV — Versioning strategy on nuget.org
+
+The PK1 problem has two coherent exits. The recommended one is (A).
+
+**(A) Consolidate into a single multi-target line per `PackageId` (recommended).**
+One multi-target `.csproj` per package (`net8.0;net9.0;net10.0` — net6/net7 are EOL), a single version line,
+resumed **above** the highest already published so the timeline becomes increasing and monotonic again
+(e.g. **11.0.0**, or 5.0.0 if you accept the numeric "latest" dropping — which would confuse anyone already
+on 10.x). Multi-target already delivers the right binary per TFM inside a single `.nupkg` — exactly what the
+parallel-lines scheme tries to emulate by hand. This fixes PK1, PK2, PK4, PK5 and Q4 at once.
+
+**(B) Keep per-.NET families, but with distinct `PackageId`s.**
+E.g. `eQuantic.Core.Data.EntityFramework.Net8`. It is the only way NuGet treats the families as independent
+lines (each id's "latest" is correct for its TFM). Cost: it fragments the consumer ecosystem and discovery
+on nuget.org, and multiplies the packages to maintain. Only worth it if there is a strong reason to freeze
+each TFM to its own API.
+
+**Migration without breaking anyone already on the old versions** (applies to A and B):
+- **Never** unpublish (`unlist` keeps restore working for pinned versions; `delete` breaks it). Use
+ **deprecation** on nuget.org (`Legacy`/`Other`) on the old versions, pointing to the new one.
+- Publish the **compatibility matrix** (TFM × package × version) in the README — today the README says
+ "Version 4.4.0" and explains none of it.
+- Align the core + 4 providers to release **always together, at the same version** (resolves the
+ `AssemblyVersion` diamonds).
+- Only then resume the consolidated numbering and point the release CI at tags.
+
+Note: the new `eQuantic.Core.Data.EntityFramework.Relational` package introduced in Phase 2 must join
+whichever scheme is chosen here.
+
+---
+
+## Appendix — Suggested order (what to do first)
+
+1. **Phase 0** (pipeline) — unblocks everything safely. ✅ done
+2. **S1, C1, C2, C3, P1, P2** — the 6 🔴 security/runtime findings, with tests. ✅ done
+3. **Rest of Phase 1** (🟠 findings) on the current line. ✅ done
+4. **Phase 2** (de-dup) — cheap, high return, no break. ✅ done
+5. **Phases 3–4** — plan the contract v5.0.0 and the version consolidation as a separate milestone,
+ communicated to consumers in advance.
diff --git a/eQuantic.Core.Data.EntityFramework.sln b/eQuantic.Core.Data.EntityFramework.sln
index 7630e09..0f2964d 100644
--- a/eQuantic.Core.Data.EntityFramework.sln
+++ b/eQuantic.Core.Data.EntityFramework.sln
@@ -1,4 +1,4 @@
-
+
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26430.6
@@ -14,52 +14,20 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.github\workflows\dotnetcore.yml = .github\workflows\dotnetcore.yml
EndProjectSection
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "eQuantic.Core.Data.EntityFramework", "src\eQuantic.Core.Data.EntityFramework\eQuantic.Core.Data.EntityFramework.csproj", "{CB4BD082-A5A5-4E9D-A226-1C7B8C505CC2}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.Net6", "src\eQuantic.Core.Data.EntityFramework\eQuantic.Core.Data.EntityFramework.Net6.csproj", "{20175F0B-5566-4213-A60E-435A9458B018}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.Net7", "src\eQuantic.Core.Data.EntityFramework\eQuantic.Core.Data.EntityFramework.Net7.csproj", "{BFDD8BE6-912B-4593-8F53-C6B6B3F8490F}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.Net8", "src\eQuantic.Core.Data.EntityFramework\eQuantic.Core.Data.EntityFramework.Net8.csproj", "{AB04C373-0DBD-4B6F-811D-D668851BBAD5}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.SqlServer", "src\eQuantic.Core.Data.EntityFramework.SqlServer\eQuantic.Core.Data.EntityFramework.SqlServer.csproj", "{91233051-EE7E-4CBB-8FFD-B900ABBACBBE}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SqlServer", "SqlServer", "{5B80C186-5E75-413B-B825-0B5B2FE9A3E4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MongoDb", "MongoDb", "{F613B6F2-C420-4E72-981A-47A20A538BC6}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.MongoDb", "src\eQuantic.Core.Data.EntityFramework.MongoDb\eQuantic.Core.Data.EntityFramework.MongoDb.csproj", "{B1BE22DD-76F6-45BE-BBD8-6502B8CCB47D}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.SqlServer.Net6", "src\eQuantic.Core.Data.EntityFramework.SqlServer\eQuantic.Core.Data.EntityFramework.SqlServer.Net6.csproj", "{CD7B2B69-FCC3-49FB-9EE9-0078EC30507B}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.SqlServer.Net7", "src\eQuantic.Core.Data.EntityFramework.SqlServer\eQuantic.Core.Data.EntityFramework.SqlServer.Net7.csproj", "{068DEA0E-ED2C-43FE-9552-7D33F88B8C8F}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.SqlServer.Net8", "src\eQuantic.Core.Data.EntityFramework.SqlServer\eQuantic.Core.Data.EntityFramework.SqlServer.Net8.csproj", "{A5610597-89E9-4BE1-8B87-31B7B7E11473}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.MongoDb.Net9", "src\eQuantic.Core.Data.EntityFramework.MongoDb\eQuantic.Core.Data.EntityFramework.MongoDb.Net9.csproj", "{730DEB2B-3B8C-49A4-BD2D-12A274C8790A}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.SqlServer.Net9", "src\eQuantic.Core.Data.EntityFramework.SqlServer\eQuantic.Core.Data.EntityFramework.SqlServer.Net9.csproj", "{8FB6D5FF-A9A0-4A38-AA24-9746E3ADD344}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.Net9", "src\eQuantic.Core.Data.EntityFramework\eQuantic.Core.Data.EntityFramework.Net9.csproj", "{99E041B1-FD74-4D9E-97E0-00CA10E2E8A5}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MySql", "MySql", "{9D499CE2-6681-4F5D-8F47-F634EFF79132}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.MySql", "src\eQuantic.Core.Data.EntityFramework.MySql\eQuantic.Core.Data.EntityFramework.MySql.csproj", "{7921DCBF-FAFF-4080-8AA0-A00C0548FDD8}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.MySql.Net8", "src\eQuantic.Core.Data.EntityFramework.MySql\eQuantic.Core.Data.EntityFramework.MySql.Net8.csproj", "{C7367715-9273-460F-BE2C-1BB06095E45C}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.MySql.Net9", "src\eQuantic.Core.Data.EntityFramework.MySql\eQuantic.Core.Data.EntityFramework.MySql.Net9.csproj", "{CD4AFD53-E76D-4181-85BA-B14E37DEFAFD}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PostgreSql", "PostgreSql", "{A6FAA830-E2CB-48CD-91C6-E0D75BFD8291}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.PostgreSql", "src\eQuantic.Core.Data.EntityFramework.PostgreSql\eQuantic.Core.Data.EntityFramework.PostgreSql.csproj", "{B67128D8-373F-4215-9DCE-85594BEAC0E2}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.PostgreSql.Net8", "src\eQuantic.Core.Data.EntityFramework.PostgreSql\eQuantic.Core.Data.EntityFramework.PostgreSql.Net8.csproj", "{46781B67-3B50-4DC9-BA64-6713528F605A}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.PostgreSql.Net9", "src\eQuantic.Core.Data.EntityFramework.PostgreSql\eQuantic.Core.Data.EntityFramework.PostgreSql.Net9.csproj", "{715C2497-F3FF-468D-ACF6-A3BB5C18D2D6}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.Net10", "src\eQuantic.Core.Data.EntityFramework\eQuantic.Core.Data.EntityFramework.Net10.csproj", "{7D85EC36-84C6-4429-BE02-291F046BACA4}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.SqlServer.Net10", "src\eQuantic.Core.Data.EntityFramework.SqlServer\eQuantic.Core.Data.EntityFramework.SqlServer.Net10.csproj", "{D40D237B-5C44-4271-9DCC-53E07DE4342F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.PostgreSql.Net10", "src\eQuantic.Core.Data.EntityFramework.PostgreSql\eQuantic.Core.Data.EntityFramework.PostgreSql.Net10.csproj", "{9BB6467D-1212-4762-96C8-0C1B6E10109F}"
@@ -74,146 +42,223 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.SqlServer.Tests", "tests\eQuantic.Core.Data.EntityFramework.SqlServer.Tests\eQuantic.Core.Data.EntityFramework.SqlServer.Tests.csproj", "{A730FE2E-2972-41F6-AAF7-3AEE017A7418}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.MongoDb.Tests", "tests\eQuantic.Core.Data.EntityFramework.MongoDb.Tests\eQuantic.Core.Data.EntityFramework.MongoDb.Tests.csproj", "{8BFE9861-4A05-4D69-BC21-6FDB5C4B6A69}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.Relational", "src\eQuantic.Core.Data.EntityFramework.Relational\eQuantic.Core.Data.EntityFramework.Relational.csproj", "{A6953967-90B5-485D-956E-A62EFE67423F}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "eQuantic.Core.Data.EntityFramework.MongoDb", "eQuantic.Core.Data.EntityFramework.MongoDb", "{6A49CAD3-8848-795E-F57A-8F1C385ECFED}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.MongoDb.Net8", "src\eQuantic.Core.Data.EntityFramework.MongoDb\eQuantic.Core.Data.EntityFramework.MongoDb.Net8.csproj", "{2AD27AB3-FA4A-45B8-83AC-A83156FC4D94}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "eQuantic.Core.Data.EntityFramework", "eQuantic.Core.Data.EntityFramework", "{BEB4D7A8-0559-D436-17A0-F8FA25A80731}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eQuantic.Core.Data.EntityFramework.Net8", "src\eQuantic.Core.Data.EntityFramework\eQuantic.Core.Data.EntityFramework.Net8.csproj", "{0CAE00C1-4547-4DDA-A526-A4BBB78898A2}"
+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
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {CB4BD082-A5A5-4E9D-A226-1C7B8C505CC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {CB4BD082-A5A5-4E9D-A226-1C7B8C505CC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {CB4BD082-A5A5-4E9D-A226-1C7B8C505CC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {CB4BD082-A5A5-4E9D-A226-1C7B8C505CC2}.Release|Any CPU.Build.0 = Release|Any CPU
- {20175F0B-5566-4213-A60E-435A9458B018}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {20175F0B-5566-4213-A60E-435A9458B018}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {20175F0B-5566-4213-A60E-435A9458B018}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {20175F0B-5566-4213-A60E-435A9458B018}.Release|Any CPU.Build.0 = Release|Any CPU
- {BFDD8BE6-912B-4593-8F53-C6B6B3F8490F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {BFDD8BE6-912B-4593-8F53-C6B6B3F8490F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {BFDD8BE6-912B-4593-8F53-C6B6B3F8490F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {BFDD8BE6-912B-4593-8F53-C6B6B3F8490F}.Release|Any CPU.Build.0 = Release|Any CPU
- {AB04C373-0DBD-4B6F-811D-D668851BBAD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {AB04C373-0DBD-4B6F-811D-D668851BBAD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {AB04C373-0DBD-4B6F-811D-D668851BBAD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {AB04C373-0DBD-4B6F-811D-D668851BBAD5}.Release|Any CPU.Build.0 = Release|Any CPU
- {91233051-EE7E-4CBB-8FFD-B900ABBACBBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {91233051-EE7E-4CBB-8FFD-B900ABBACBBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {91233051-EE7E-4CBB-8FFD-B900ABBACBBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {91233051-EE7E-4CBB-8FFD-B900ABBACBBE}.Release|Any CPU.Build.0 = Release|Any CPU
- {B1BE22DD-76F6-45BE-BBD8-6502B8CCB47D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B1BE22DD-76F6-45BE-BBD8-6502B8CCB47D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B1BE22DD-76F6-45BE-BBD8-6502B8CCB47D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B1BE22DD-76F6-45BE-BBD8-6502B8CCB47D}.Release|Any CPU.Build.0 = Release|Any CPU
- {CD7B2B69-FCC3-49FB-9EE9-0078EC30507B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {CD7B2B69-FCC3-49FB-9EE9-0078EC30507B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {CD7B2B69-FCC3-49FB-9EE9-0078EC30507B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {CD7B2B69-FCC3-49FB-9EE9-0078EC30507B}.Release|Any CPU.Build.0 = Release|Any CPU
- {068DEA0E-ED2C-43FE-9552-7D33F88B8C8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {068DEA0E-ED2C-43FE-9552-7D33F88B8C8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {068DEA0E-ED2C-43FE-9552-7D33F88B8C8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {068DEA0E-ED2C-43FE-9552-7D33F88B8C8F}.Release|Any CPU.Build.0 = Release|Any CPU
{A5610597-89E9-4BE1-8B87-31B7B7E11473}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A5610597-89E9-4BE1-8B87-31B7B7E11473}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A5610597-89E9-4BE1-8B87-31B7B7E11473}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A5610597-89E9-4BE1-8B87-31B7B7E11473}.Debug|x64.Build.0 = Debug|Any CPU
+ {A5610597-89E9-4BE1-8B87-31B7B7E11473}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A5610597-89E9-4BE1-8B87-31B7B7E11473}.Debug|x86.Build.0 = Debug|Any CPU
{A5610597-89E9-4BE1-8B87-31B7B7E11473}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5610597-89E9-4BE1-8B87-31B7B7E11473}.Release|Any CPU.Build.0 = Release|Any CPU
- {730DEB2B-3B8C-49A4-BD2D-12A274C8790A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {730DEB2B-3B8C-49A4-BD2D-12A274C8790A}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {730DEB2B-3B8C-49A4-BD2D-12A274C8790A}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {730DEB2B-3B8C-49A4-BD2D-12A274C8790A}.Release|Any CPU.Build.0 = Release|Any CPU
- {8FB6D5FF-A9A0-4A38-AA24-9746E3ADD344}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {8FB6D5FF-A9A0-4A38-AA24-9746E3ADD344}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {8FB6D5FF-A9A0-4A38-AA24-9746E3ADD344}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {8FB6D5FF-A9A0-4A38-AA24-9746E3ADD344}.Release|Any CPU.Build.0 = Release|Any CPU
- {99E041B1-FD74-4D9E-97E0-00CA10E2E8A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {99E041B1-FD74-4D9E-97E0-00CA10E2E8A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {99E041B1-FD74-4D9E-97E0-00CA10E2E8A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {99E041B1-FD74-4D9E-97E0-00CA10E2E8A5}.Release|Any CPU.Build.0 = Release|Any CPU
- {7921DCBF-FAFF-4080-8AA0-A00C0548FDD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7921DCBF-FAFF-4080-8AA0-A00C0548FDD8}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7921DCBF-FAFF-4080-8AA0-A00C0548FDD8}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7921DCBF-FAFF-4080-8AA0-A00C0548FDD8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A5610597-89E9-4BE1-8B87-31B7B7E11473}.Release|x64.ActiveCfg = Release|Any CPU
+ {A5610597-89E9-4BE1-8B87-31B7B7E11473}.Release|x64.Build.0 = Release|Any CPU
+ {A5610597-89E9-4BE1-8B87-31B7B7E11473}.Release|x86.ActiveCfg = Release|Any CPU
+ {A5610597-89E9-4BE1-8B87-31B7B7E11473}.Release|x86.Build.0 = Release|Any CPU
{C7367715-9273-460F-BE2C-1BB06095E45C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C7367715-9273-460F-BE2C-1BB06095E45C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C7367715-9273-460F-BE2C-1BB06095E45C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C7367715-9273-460F-BE2C-1BB06095E45C}.Debug|x64.Build.0 = Debug|Any CPU
+ {C7367715-9273-460F-BE2C-1BB06095E45C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C7367715-9273-460F-BE2C-1BB06095E45C}.Debug|x86.Build.0 = Debug|Any CPU
{C7367715-9273-460F-BE2C-1BB06095E45C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C7367715-9273-460F-BE2C-1BB06095E45C}.Release|Any CPU.Build.0 = Release|Any CPU
- {CD4AFD53-E76D-4181-85BA-B14E37DEFAFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {CD4AFD53-E76D-4181-85BA-B14E37DEFAFD}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {CD4AFD53-E76D-4181-85BA-B14E37DEFAFD}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {CD4AFD53-E76D-4181-85BA-B14E37DEFAFD}.Release|Any CPU.Build.0 = Release|Any CPU
- {B67128D8-373F-4215-9DCE-85594BEAC0E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B67128D8-373F-4215-9DCE-85594BEAC0E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B67128D8-373F-4215-9DCE-85594BEAC0E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B67128D8-373F-4215-9DCE-85594BEAC0E2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C7367715-9273-460F-BE2C-1BB06095E45C}.Release|x64.ActiveCfg = Release|Any CPU
+ {C7367715-9273-460F-BE2C-1BB06095E45C}.Release|x64.Build.0 = Release|Any CPU
+ {C7367715-9273-460F-BE2C-1BB06095E45C}.Release|x86.ActiveCfg = Release|Any CPU
+ {C7367715-9273-460F-BE2C-1BB06095E45C}.Release|x86.Build.0 = Release|Any CPU
{46781B67-3B50-4DC9-BA64-6713528F605A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{46781B67-3B50-4DC9-BA64-6713528F605A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {46781B67-3B50-4DC9-BA64-6713528F605A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {46781B67-3B50-4DC9-BA64-6713528F605A}.Debug|x64.Build.0 = Debug|Any CPU
+ {46781B67-3B50-4DC9-BA64-6713528F605A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {46781B67-3B50-4DC9-BA64-6713528F605A}.Debug|x86.Build.0 = Debug|Any CPU
{46781B67-3B50-4DC9-BA64-6713528F605A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{46781B67-3B50-4DC9-BA64-6713528F605A}.Release|Any CPU.Build.0 = Release|Any CPU
- {715C2497-F3FF-468D-ACF6-A3BB5C18D2D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {715C2497-F3FF-468D-ACF6-A3BB5C18D2D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {715C2497-F3FF-468D-ACF6-A3BB5C18D2D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {715C2497-F3FF-468D-ACF6-A3BB5C18D2D6}.Release|Any CPU.Build.0 = Release|Any CPU
- {7D85EC36-84C6-4429-BE02-291F046BACA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7D85EC36-84C6-4429-BE02-291F046BACA4}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7D85EC36-84C6-4429-BE02-291F046BACA4}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7D85EC36-84C6-4429-BE02-291F046BACA4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {46781B67-3B50-4DC9-BA64-6713528F605A}.Release|x64.ActiveCfg = Release|Any CPU
+ {46781B67-3B50-4DC9-BA64-6713528F605A}.Release|x64.Build.0 = Release|Any CPU
+ {46781B67-3B50-4DC9-BA64-6713528F605A}.Release|x86.ActiveCfg = Release|Any CPU
+ {46781B67-3B50-4DC9-BA64-6713528F605A}.Release|x86.Build.0 = Release|Any CPU
{D40D237B-5C44-4271-9DCC-53E07DE4342F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D40D237B-5C44-4271-9DCC-53E07DE4342F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D40D237B-5C44-4271-9DCC-53E07DE4342F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D40D237B-5C44-4271-9DCC-53E07DE4342F}.Debug|x64.Build.0 = Debug|Any CPU
+ {D40D237B-5C44-4271-9DCC-53E07DE4342F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D40D237B-5C44-4271-9DCC-53E07DE4342F}.Debug|x86.Build.0 = Debug|Any CPU
{D40D237B-5C44-4271-9DCC-53E07DE4342F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D40D237B-5C44-4271-9DCC-53E07DE4342F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D40D237B-5C44-4271-9DCC-53E07DE4342F}.Release|x64.ActiveCfg = Release|Any CPU
+ {D40D237B-5C44-4271-9DCC-53E07DE4342F}.Release|x64.Build.0 = Release|Any CPU
+ {D40D237B-5C44-4271-9DCC-53E07DE4342F}.Release|x86.ActiveCfg = Release|Any CPU
+ {D40D237B-5C44-4271-9DCC-53E07DE4342F}.Release|x86.Build.0 = Release|Any CPU
{9BB6467D-1212-4762-96C8-0C1B6E10109F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9BB6467D-1212-4762-96C8-0C1B6E10109F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9BB6467D-1212-4762-96C8-0C1B6E10109F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9BB6467D-1212-4762-96C8-0C1B6E10109F}.Debug|x64.Build.0 = Debug|Any CPU
+ {9BB6467D-1212-4762-96C8-0C1B6E10109F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9BB6467D-1212-4762-96C8-0C1B6E10109F}.Debug|x86.Build.0 = Debug|Any CPU
{9BB6467D-1212-4762-96C8-0C1B6E10109F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9BB6467D-1212-4762-96C8-0C1B6E10109F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9BB6467D-1212-4762-96C8-0C1B6E10109F}.Release|x64.ActiveCfg = Release|Any CPU
+ {9BB6467D-1212-4762-96C8-0C1B6E10109F}.Release|x64.Build.0 = Release|Any CPU
+ {9BB6467D-1212-4762-96C8-0C1B6E10109F}.Release|x86.ActiveCfg = Release|Any CPU
+ {9BB6467D-1212-4762-96C8-0C1B6E10109F}.Release|x86.Build.0 = Release|Any CPU
{A3EED73E-6A4B-4C7C-9705-BE6FDEA9B748}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A3EED73E-6A4B-4C7C-9705-BE6FDEA9B748}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A3EED73E-6A4B-4C7C-9705-BE6FDEA9B748}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A3EED73E-6A4B-4C7C-9705-BE6FDEA9B748}.Debug|x64.Build.0 = Debug|Any CPU
+ {A3EED73E-6A4B-4C7C-9705-BE6FDEA9B748}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A3EED73E-6A4B-4C7C-9705-BE6FDEA9B748}.Debug|x86.Build.0 = Debug|Any CPU
{A3EED73E-6A4B-4C7C-9705-BE6FDEA9B748}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A3EED73E-6A4B-4C7C-9705-BE6FDEA9B748}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A3EED73E-6A4B-4C7C-9705-BE6FDEA9B748}.Release|x64.ActiveCfg = Release|Any CPU
+ {A3EED73E-6A4B-4C7C-9705-BE6FDEA9B748}.Release|x64.Build.0 = Release|Any CPU
+ {A3EED73E-6A4B-4C7C-9705-BE6FDEA9B748}.Release|x86.ActiveCfg = Release|Any CPU
+ {A3EED73E-6A4B-4C7C-9705-BE6FDEA9B748}.Release|x86.Build.0 = Release|Any CPU
{B5E862DD-A02A-4E26-AA83-687C608C4203}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B5E862DD-A02A-4E26-AA83-687C608C4203}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B5E862DD-A02A-4E26-AA83-687C608C4203}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B5E862DD-A02A-4E26-AA83-687C608C4203}.Debug|x64.Build.0 = Debug|Any CPU
+ {B5E862DD-A02A-4E26-AA83-687C608C4203}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B5E862DD-A02A-4E26-AA83-687C608C4203}.Debug|x86.Build.0 = Debug|Any CPU
{B5E862DD-A02A-4E26-AA83-687C608C4203}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B5E862DD-A02A-4E26-AA83-687C608C4203}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B5E862DD-A02A-4E26-AA83-687C608C4203}.Release|x64.ActiveCfg = Release|Any CPU
+ {B5E862DD-A02A-4E26-AA83-687C608C4203}.Release|x64.Build.0 = Release|Any CPU
+ {B5E862DD-A02A-4E26-AA83-687C608C4203}.Release|x86.ActiveCfg = Release|Any CPU
+ {B5E862DD-A02A-4E26-AA83-687C608C4203}.Release|x86.Build.0 = Release|Any CPU
{D0A7C1F3-94B3-4353-9C14-827ABBAD649D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D0A7C1F3-94B3-4353-9C14-827ABBAD649D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D0A7C1F3-94B3-4353-9C14-827ABBAD649D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D0A7C1F3-94B3-4353-9C14-827ABBAD649D}.Debug|x64.Build.0 = Debug|Any CPU
+ {D0A7C1F3-94B3-4353-9C14-827ABBAD649D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D0A7C1F3-94B3-4353-9C14-827ABBAD649D}.Debug|x86.Build.0 = Debug|Any CPU
{D0A7C1F3-94B3-4353-9C14-827ABBAD649D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D0A7C1F3-94B3-4353-9C14-827ABBAD649D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D0A7C1F3-94B3-4353-9C14-827ABBAD649D}.Release|x64.ActiveCfg = Release|Any CPU
+ {D0A7C1F3-94B3-4353-9C14-827ABBAD649D}.Release|x64.Build.0 = Release|Any CPU
+ {D0A7C1F3-94B3-4353-9C14-827ABBAD649D}.Release|x86.ActiveCfg = Release|Any CPU
+ {D0A7C1F3-94B3-4353-9C14-827ABBAD649D}.Release|x86.Build.0 = Release|Any CPU
{A730FE2E-2972-41F6-AAF7-3AEE017A7418}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A730FE2E-2972-41F6-AAF7-3AEE017A7418}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A730FE2E-2972-41F6-AAF7-3AEE017A7418}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A730FE2E-2972-41F6-AAF7-3AEE017A7418}.Debug|x64.Build.0 = Debug|Any CPU
+ {A730FE2E-2972-41F6-AAF7-3AEE017A7418}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A730FE2E-2972-41F6-AAF7-3AEE017A7418}.Debug|x86.Build.0 = Debug|Any CPU
{A730FE2E-2972-41F6-AAF7-3AEE017A7418}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A730FE2E-2972-41F6-AAF7-3AEE017A7418}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A730FE2E-2972-41F6-AAF7-3AEE017A7418}.Release|x64.ActiveCfg = Release|Any CPU
+ {A730FE2E-2972-41F6-AAF7-3AEE017A7418}.Release|x64.Build.0 = Release|Any CPU
+ {A730FE2E-2972-41F6-AAF7-3AEE017A7418}.Release|x86.ActiveCfg = Release|Any CPU
+ {A730FE2E-2972-41F6-AAF7-3AEE017A7418}.Release|x86.Build.0 = Release|Any CPU
+ {8BFE9861-4A05-4D69-BC21-6FDB5C4B6A69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8BFE9861-4A05-4D69-BC21-6FDB5C4B6A69}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8BFE9861-4A05-4D69-BC21-6FDB5C4B6A69}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8BFE9861-4A05-4D69-BC21-6FDB5C4B6A69}.Debug|x64.Build.0 = Debug|Any CPU
+ {8BFE9861-4A05-4D69-BC21-6FDB5C4B6A69}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8BFE9861-4A05-4D69-BC21-6FDB5C4B6A69}.Debug|x86.Build.0 = Debug|Any CPU
+ {8BFE9861-4A05-4D69-BC21-6FDB5C4B6A69}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8BFE9861-4A05-4D69-BC21-6FDB5C4B6A69}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8BFE9861-4A05-4D69-BC21-6FDB5C4B6A69}.Release|x64.ActiveCfg = Release|Any CPU
+ {8BFE9861-4A05-4D69-BC21-6FDB5C4B6A69}.Release|x64.Build.0 = Release|Any CPU
+ {8BFE9861-4A05-4D69-BC21-6FDB5C4B6A69}.Release|x86.ActiveCfg = Release|Any CPU
+ {8BFE9861-4A05-4D69-BC21-6FDB5C4B6A69}.Release|x86.Build.0 = Release|Any CPU
+ {A6953967-90B5-485D-956E-A62EFE67423F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A6953967-90B5-485D-956E-A62EFE67423F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A6953967-90B5-485D-956E-A62EFE67423F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A6953967-90B5-485D-956E-A62EFE67423F}.Debug|x64.Build.0 = Debug|Any CPU
+ {A6953967-90B5-485D-956E-A62EFE67423F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A6953967-90B5-485D-956E-A62EFE67423F}.Debug|x86.Build.0 = Debug|Any CPU
+ {A6953967-90B5-485D-956E-A62EFE67423F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A6953967-90B5-485D-956E-A62EFE67423F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A6953967-90B5-485D-956E-A62EFE67423F}.Release|x64.ActiveCfg = Release|Any CPU
+ {A6953967-90B5-485D-956E-A62EFE67423F}.Release|x64.Build.0 = Release|Any CPU
+ {A6953967-90B5-485D-956E-A62EFE67423F}.Release|x86.ActiveCfg = Release|Any CPU
+ {A6953967-90B5-485D-956E-A62EFE67423F}.Release|x86.Build.0 = Release|Any CPU
+ {2AD27AB3-FA4A-45B8-83AC-A83156FC4D94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2AD27AB3-FA4A-45B8-83AC-A83156FC4D94}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2AD27AB3-FA4A-45B8-83AC-A83156FC4D94}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {2AD27AB3-FA4A-45B8-83AC-A83156FC4D94}.Debug|x64.Build.0 = Debug|Any CPU
+ {2AD27AB3-FA4A-45B8-83AC-A83156FC4D94}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {2AD27AB3-FA4A-45B8-83AC-A83156FC4D94}.Debug|x86.Build.0 = Debug|Any CPU
+ {2AD27AB3-FA4A-45B8-83AC-A83156FC4D94}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2AD27AB3-FA4A-45B8-83AC-A83156FC4D94}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2AD27AB3-FA4A-45B8-83AC-A83156FC4D94}.Release|x64.ActiveCfg = Release|Any CPU
+ {2AD27AB3-FA4A-45B8-83AC-A83156FC4D94}.Release|x64.Build.0 = Release|Any CPU
+ {2AD27AB3-FA4A-45B8-83AC-A83156FC4D94}.Release|x86.ActiveCfg = Release|Any CPU
+ {2AD27AB3-FA4A-45B8-83AC-A83156FC4D94}.Release|x86.Build.0 = Release|Any CPU
+ {0CAE00C1-4547-4DDA-A526-A4BBB78898A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0CAE00C1-4547-4DDA-A526-A4BBB78898A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0CAE00C1-4547-4DDA-A526-A4BBB78898A2}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0CAE00C1-4547-4DDA-A526-A4BBB78898A2}.Debug|x64.Build.0 = Debug|Any CPU
+ {0CAE00C1-4547-4DDA-A526-A4BBB78898A2}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0CAE00C1-4547-4DDA-A526-A4BBB78898A2}.Debug|x86.Build.0 = Debug|Any CPU
+ {0CAE00C1-4547-4DDA-A526-A4BBB78898A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0CAE00C1-4547-4DDA-A526-A4BBB78898A2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0CAE00C1-4547-4DDA-A526-A4BBB78898A2}.Release|x64.ActiveCfg = Release|Any CPU
+ {0CAE00C1-4547-4DDA-A526-A4BBB78898A2}.Release|x64.Build.0 = Release|Any CPU
+ {0CAE00C1-4547-4DDA-A526-A4BBB78898A2}.Release|x86.ActiveCfg = Release|Any CPU
+ {0CAE00C1-4547-4DDA-A526-A4BBB78898A2}.Release|x86.Build.0 = Release|Any CPU
+ {B8AA3B17-808A-4D8B-8C1F-EB42E9C5C354}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B8AA3B17-808A-4D8B-8C1F-EB42E9C5C354}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B8AA3B17-808A-4D8B-8C1F-EB42E9C5C354}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B8AA3B17-808A-4D8B-8C1F-EB42E9C5C354}.Debug|x64.Build.0 = Debug|Any CPU
+ {B8AA3B17-808A-4D8B-8C1F-EB42E9C5C354}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B8AA3B17-808A-4D8B-8C1F-EB42E9C5C354}.Debug|x86.Build.0 = Debug|Any CPU
+ {B8AA3B17-808A-4D8B-8C1F-EB42E9C5C354}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B8AA3B17-808A-4D8B-8C1F-EB42E9C5C354}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B8AA3B17-808A-4D8B-8C1F-EB42E9C5C354}.Release|x64.ActiveCfg = Release|Any CPU
+ {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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
- {CB4BD082-A5A5-4E9D-A226-1C7B8C505CC2} = {0CA2F610-6D57-4D1F-92E3-EDCCEDAD8297}
- {20175F0B-5566-4213-A60E-435A9458B018} = {0CA2F610-6D57-4D1F-92E3-EDCCEDAD8297}
- {BFDD8BE6-912B-4593-8F53-C6B6B3F8490F} = {0CA2F610-6D57-4D1F-92E3-EDCCEDAD8297}
- {AB04C373-0DBD-4B6F-811D-D668851BBAD5} = {0CA2F610-6D57-4D1F-92E3-EDCCEDAD8297}
{5B80C186-5E75-413B-B825-0B5B2FE9A3E4} = {0CA2F610-6D57-4D1F-92E3-EDCCEDAD8297}
- {91233051-EE7E-4CBB-8FFD-B900ABBACBBE} = {5B80C186-5E75-413B-B825-0B5B2FE9A3E4}
{F613B6F2-C420-4E72-981A-47A20A538BC6} = {0CA2F610-6D57-4D1F-92E3-EDCCEDAD8297}
- {B1BE22DD-76F6-45BE-BBD8-6502B8CCB47D} = {F613B6F2-C420-4E72-981A-47A20A538BC6}
- {CD7B2B69-FCC3-49FB-9EE9-0078EC30507B} = {5B80C186-5E75-413B-B825-0B5B2FE9A3E4}
- {068DEA0E-ED2C-43FE-9552-7D33F88B8C8F} = {5B80C186-5E75-413B-B825-0B5B2FE9A3E4}
{A5610597-89E9-4BE1-8B87-31B7B7E11473} = {5B80C186-5E75-413B-B825-0B5B2FE9A3E4}
- {730DEB2B-3B8C-49A4-BD2D-12A274C8790A} = {F613B6F2-C420-4E72-981A-47A20A538BC6}
- {8FB6D5FF-A9A0-4A38-AA24-9746E3ADD344} = {5B80C186-5E75-413B-B825-0B5B2FE9A3E4}
- {99E041B1-FD74-4D9E-97E0-00CA10E2E8A5} = {0CA2F610-6D57-4D1F-92E3-EDCCEDAD8297}
{9D499CE2-6681-4F5D-8F47-F634EFF79132} = {0CA2F610-6D57-4D1F-92E3-EDCCEDAD8297}
- {7921DCBF-FAFF-4080-8AA0-A00C0548FDD8} = {9D499CE2-6681-4F5D-8F47-F634EFF79132}
{C7367715-9273-460F-BE2C-1BB06095E45C} = {9D499CE2-6681-4F5D-8F47-F634EFF79132}
- {CD4AFD53-E76D-4181-85BA-B14E37DEFAFD} = {9D499CE2-6681-4F5D-8F47-F634EFF79132}
{A6FAA830-E2CB-48CD-91C6-E0D75BFD8291} = {0CA2F610-6D57-4D1F-92E3-EDCCEDAD8297}
- {B67128D8-373F-4215-9DCE-85594BEAC0E2} = {A6FAA830-E2CB-48CD-91C6-E0D75BFD8291}
{46781B67-3B50-4DC9-BA64-6713528F605A} = {A6FAA830-E2CB-48CD-91C6-E0D75BFD8291}
- {715C2497-F3FF-468D-ACF6-A3BB5C18D2D6} = {A6FAA830-E2CB-48CD-91C6-E0D75BFD8291}
- {7D85EC36-84C6-4429-BE02-291F046BACA4} = {0CA2F610-6D57-4D1F-92E3-EDCCEDAD8297}
{D40D237B-5C44-4271-9DCC-53E07DE4342F} = {5B80C186-5E75-413B-B825-0B5B2FE9A3E4}
{9BB6467D-1212-4762-96C8-0C1B6E10109F} = {A6FAA830-E2CB-48CD-91C6-E0D75BFD8291}
{A3EED73E-6A4B-4C7C-9705-BE6FDEA9B748} = {9D499CE2-6681-4F5D-8F47-F634EFF79132}
{B5E862DD-A02A-4E26-AA83-687C608C4203} = {F613B6F2-C420-4E72-981A-47A20A538BC6}
{D0A7C1F3-94B3-4353-9C14-827ABBAD649D} = {EE71F02F-413D-4BC0-832F-726B6D8C0AD7}
{A730FE2E-2972-41F6-AAF7-3AEE017A7418} = {EE71F02F-413D-4BC0-832F-726B6D8C0AD7}
+ {8BFE9861-4A05-4D69-BC21-6FDB5C4B6A69} = {EE71F02F-413D-4BC0-832F-726B6D8C0AD7}
+ {A6953967-90B5-485D-956E-A62EFE67423F} = {0CA2F610-6D57-4D1F-92E3-EDCCEDAD8297}
+ {6A49CAD3-8848-795E-F57A-8F1C385ECFED} = {0CA2F610-6D57-4D1F-92E3-EDCCEDAD8297}
+ {2AD27AB3-FA4A-45B8-83AC-A83156FC4D94} = {6A49CAD3-8848-795E-F57A-8F1C385ECFED}
+ {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}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {83FECDD1-8A97-40B1-8529-3D5216E674C3}
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..512142d
--- /dev/null
+++ b/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "10.0.100",
+ "rollForward": "latestFeature"
+ }
+}
diff --git a/src/eQuantic.Core.Data.EntityFramework.MongoDb/Repository/Set.cs b/src/eQuantic.Core.Data.EntityFramework.MongoDb/Repository/Set.cs
index d51ccc0..dbe5b02 100644
--- a/src/eQuantic.Core.Data.EntityFramework.MongoDb/Repository/Set.cs
+++ b/src/eQuantic.Core.Data.EntityFramework.MongoDb/Repository/Set.cs
@@ -1,134 +1,100 @@
using System.Linq.Expressions;
using eQuantic.Core.Data.EntityFramework.Repository;
-using eQuantic.Core.Data.EntityFramework.Repository.Extensions;
using eQuantic.Core.Data.Repository;
-using eQuantic.Core.Data.Repository.Config;
-using eQuantic.Linq.Extensions;
using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver;
using MongoDB.EntityFrameworkCore.Extensions;
+using MongoDB.EntityFrameworkCore.Infrastructure;
namespace eQuantic.Core.Data.EntityFramework.MongoDb.Repository;
-public class Set : SetBase where TEntity : class, IEntity, new()
+public class Set : SetBase where TEntity : class, IEntity
{
private readonly IServiceProvider _serviceProvider;
private readonly string? _collectionName;
+ private readonly string? _databaseName;
private IMongoDatabase? _mongoDatabase;
-
+
public Set(IServiceProvider serviceProvider, DbContext context) : base(context)
{
_serviceProvider = serviceProvider;
var entityType = context.Model.FindEntityType(typeof(TEntity));
_collectionName = entityType?.GetCollectionName();
+
+ // Resolve the database name from the DbContext configuration up front (the context is only
+ // available here), NOT from the collection name.
+ _databaseName = context.GetService()
+ .FindExtension()?.DatabaseName;
}
-
+
public override long DeleteMany(Expression> filter)
{
- var collection = GetCollection();
- if (collection == null) return 0;
-
- var result = collection.DeleteMany(filter);
+ var result = GetCollection().DeleteMany(filter);
return result.DeletedCount;
}
public override async Task DeleteManyAsync(Expression> filter, CancellationToken cancellationToken = default)
{
- var collection = GetCollection();
- if (collection == null) return 0;
-
var mongoFilter = Builders.Filter.Where(filter);
- var result = await collection.DeleteManyAsync(mongoFilter, cancellationToken);
-
+ var result = await GetCollection().DeleteManyAsync(mongoFilter, cancellationToken).ConfigureAwait(false);
+
return result.DeletedCount;
}
public override long UpdateMany(Expression> filter, Expression> updateExpression)
{
- var collection = GetCollection();
- if (collection == null) return 0;
-
var mongoFilter = Builders.Filter.Where(filter);
var updateDefinition = UpdateDefinitionBuilder.BuildUpdateDefinition(updateExpression);
if (updateDefinition == null)
return 0;
-
- var result = collection.UpdateMany(mongoFilter, updateDefinition);
+
+ var result = GetCollection().UpdateMany(mongoFilter, updateDefinition);
return result.ModifiedCount;
}
public override async Task UpdateManyAsync(Expression> filter, Expression> updateExpression, CancellationToken cancellationToken = default)
{
- var collection = GetCollection();
- if (collection == null) return 0;
-
var mongoFilter = Builders.Filter.Where(filter);
var updateDefinition = UpdateDefinitionBuilder.BuildUpdateDefinition(updateExpression);
if (updateDefinition == null)
return 0;
-
- var result = await collection.UpdateManyAsync(mongoFilter, updateDefinition, null, cancellationToken);
+
+ var result = await GetCollection().UpdateManyAsync(mongoFilter, updateDefinition, null, cancellationToken).ConfigureAwait(false);
return result.ModifiedCount;
}
- public override IQueryable GetQueryable(Action configuration, Func, IQueryable> internalQueryAction)
+ private IMongoCollection GetCollection()
{
- if (configuration == null)
- {
- return internalQueryAction.Invoke(this);
- }
-
- var config = GetConfig(configuration);
- var queryableConfig = config as QueryableConfiguration;
-
- IQueryable query = this;
-
- if (config.HasNoTracking)
- {
- query = query.AsNoTracking();
- }
-
- if (config.Properties?.Any() == true)
- {
- query = query.IncludeMany(config.Properties.ToArray());
- }
-
- if (queryableConfig?.IgnoreQueryFilters == true)
- {
- query = query.IgnoreQueryFilters();
- }
-
- if (!string.IsNullOrEmpty(config.Tag))
- {
- query = query.TagWith(config.Tag);
- }
+ return GetDatabase().GetCollection(_collectionName);
+ }
- if (queryableConfig != null)
+ private IMongoDatabase GetDatabase()
+ {
+ if (_mongoDatabase != null)
{
- query = queryableConfig.BeforeCustomization.Invoke(query);
+ return _mongoDatabase;
}
- query = internalQueryAction.Invoke(query);
-
- if (config.SortingColumns.Any())
+ // The database name comes from the DbContext configuration (UseMongoDB(..., databaseName)),
+ // NOT from the collection name. Using the collection name here meant every bulk operation ran
+ // against the wrong database and silently affected zero documents.
+ if (string.IsNullOrEmpty(_databaseName))
{
- query = query.OrderBy(config.SortingColumns.ToArray());
+ throw new InvalidOperationException(
+ $"Could not resolve the MongoDB database name for '{typeof(TEntity).Name}'. " +
+ "Ensure the DbContext is configured with UseMongoDB(..., databaseName).");
}
- if (queryableConfig != null)
- {
- query = queryableConfig.AfterCustomization.Invoke(query);
- }
+ var client = _serviceProvider.GetService()
+ ?? throw new InvalidOperationException(
+ "No IMongoClient is registered in the service provider; bulk MongoDB operations " +
+ "(DeleteMany/UpdateMany) cannot be executed. Register an IMongoClient in DI.");
- return query;
- }
-
- private IMongoCollection? GetCollection()
- {
- _mongoDatabase ??= _serviceProvider.GetService()?.GetDatabase(_collectionName);
- return _mongoDatabase?.GetCollection(_collectionName);
+ _mongoDatabase = client.GetDatabase(_databaseName);
+ return _mongoDatabase;
}
-}
\ No newline at end of file
+}
diff --git a/src/eQuantic.Core.Data.EntityFramework.MongoDb/Repository/UnitOfWork.cs b/src/eQuantic.Core.Data.EntityFramework.MongoDb/Repository/UnitOfWork.cs
index 15f87d2..8e38967 100644
--- a/src/eQuantic.Core.Data.EntityFramework.MongoDb/Repository/UnitOfWork.cs
+++ b/src/eQuantic.Core.Data.EntityFramework.MongoDb/Repository/UnitOfWork.cs
@@ -18,7 +18,7 @@ public abstract class UnitOfWork : IQueryableUnitOfWork
/// The context
///
protected readonly DbContext Context;
-
+
///
/// The disposed
///
@@ -34,7 +34,7 @@ protected UnitOfWork(IServiceProvider serviceProvider, DbContext context)
ServiceProvider = serviceProvider;
Context = context;
}
-
+
public int Commit()
{
return Context.SaveChanges();
@@ -74,7 +74,7 @@ public async Task CommitAndRefreshChangesAsync(CancellationToken cancellati
{
try
{
- changes = await Context.SaveChangesAsync(cancellationToken);
+ changes = await Context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
saveFailed = false;
}
@@ -92,7 +92,7 @@ public async Task CommitAndRefreshChangesAsync(CancellationToken cancellati
public async Task CommitAsync(CancellationToken cancellationToken = default)
{
- return await Context.SaveChangesAsync(cancellationToken);
+ return await Context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
public int Commit(Action options)
@@ -115,16 +115,16 @@ public Task CommitAsync(Action options, CancellationToken canc
return CommitAsync(cancellationToken);
}
- public void ApplyCurrentValues(TEntity original, TEntity current) where TEntity : class, IEntity, new()
+ public void ApplyCurrentValues(TEntity original, TEntity current) where TEntity : class, IEntity
{
((Set)InternalCreateSet()).ApplyCurrentValues(original, current);
}
- public void Attach(TEntity item) where TEntity : class, IEntity, new()
+ public void Attach(TEntity item) where TEntity : class, IEntity
{
((Set)InternalCreateSet()).Attach(item);
}
-
+
public void LoadCollection(TEntity item,
Expression>> navigationProperty,
Expression>? filter = null) where TEntity : class where TElement : class
@@ -145,11 +145,11 @@ public async Task LoadCollectionAsync(TEntity item,
{
if (filter != null)
{
- await Context.Entry(item).Collection(navigationProperty).Query().Where(filter).LoadAsync();
+ await Context.Entry(item).Collection(navigationProperty).Query().Where(filter).LoadAsync().ConfigureAwait(false);
}
else
{
- await Context.Entry(item).Collection(navigationProperty).LoadAsync();
+ await Context.Entry(item).Collection(navigationProperty).LoadAsync().ConfigureAwait(false);
}
}
@@ -179,42 +179,38 @@ public virtual SaveOptions GetSaveOptions()
{
return new SaveOptions();
}
-
- public virtual IRepository GetRepository()
- where TEntity : class, IEntity, new() where TUnitOfWork : IUnitOfWork
+
+ public virtual IRepository GetRepository()
+ where TEntity : class, IEntity
{
- var repo = ServiceProvider.GetRequiredService>();
- return repo;
+ return ServiceProvider.GetRequiredService>();
}
- public IAsyncRepository GetAsyncRepository()
- where TEntity : class, IEntity, new()
- where TUnitOfWork : IUnitOfWork
+ public IAsyncRepository GetAsyncRepository()
+ where TEntity : class, IEntity
{
- return ServiceProvider.GetRequiredService>();
+ return ServiceProvider.GetRequiredService>();
}
- public Data.Repository.ISet CreateSet() where TEntity : class, IEntity, new() => InternalCreateSet();
+ public Data.Repository.ISet CreateSet() where TEntity : class, IEntity => InternalCreateSet();
- public IQueryableRepository GetQueryableRepository()
- where TEntity : class, IEntity, new()
- where TUnitOfWork : IQueryableUnitOfWork
+ public IQueryableRepository GetQueryableRepository()
+ where TEntity : class, IEntity
{
- return ServiceProvider.GetRequiredService>();
+ return ServiceProvider.GetRequiredService>();
}
- public IAsyncQueryableRepository GetAsyncQueryableRepository()
- where TEntity : class, IEntity, new()
- where TUnitOfWork : IQueryableUnitOfWork
+ public IAsyncQueryableRepository GetAsyncQueryableRepository()
+ where TEntity : class, IEntity
{
- return ServiceProvider.GetRequiredService>();
+ return ServiceProvider.GetRequiredService>();
}
-
+
internal DbContext GetDbContext() => Context;
-
- internal Data.Repository.ISet InternalCreateSet() where TEntity : class, IEntity, new() =>
+
+ internal Data.Repository.ISet InternalCreateSet() where TEntity : class, IEntity =>
new Set(ServiceProvider, Context);
-
+
///
/// Disposes this instance
///
@@ -223,7 +219,7 @@ public void Dispose()
Dispose(true);
GC.SuppressFinalize(this);
}
-
+
///
/// Disposes the disposing
///
@@ -246,4 +242,4 @@ protected virtual void Dispose(bool disposing)
public abstract class UnitOfWork(IServiceProvider serviceProvider, TDbContext context)
: UnitOfWork(serviceProvider, context)
- where TDbContext : DbContext;
\ No newline at end of file
+ where TDbContext : DbContext;
diff --git a/src/eQuantic.Core.Data.EntityFramework.MongoDb/UpdateDefinitionBuilder.cs b/src/eQuantic.Core.Data.EntityFramework.MongoDb/UpdateDefinitionBuilder.cs
index 1a6a34e..364dc0f 100644
--- a/src/eQuantic.Core.Data.EntityFramework.MongoDb/UpdateDefinitionBuilder.cs
+++ b/src/eQuantic.Core.Data.EntityFramework.MongoDb/UpdateDefinitionBuilder.cs
@@ -37,11 +37,53 @@ internal static class UpdateDefinitionBuilder
private static object? GetValueFromExpression(Expression expression, IReadOnlyList parameters)
{
+ // The value is evaluated against a fresh default instance of the entity. Any assignment that
+ // reads the entity (e.g. x => x.Count + 1) would therefore be evaluated against Count == 0 and
+ // silently write the constant 1 to every matched document instead of incrementing it. Reject
+ // such expressions loudly rather than corrupting data; only constant/closure values are safe.
+ if (ReferencesParameter(expression, parameters))
+ {
+ throw new NotSupportedException(
+ "The MongoDB update builder does not support update expressions that reference the " +
+ "entity being updated (e.g. x => x.Count + 1). Such an expression would be evaluated " +
+ "against a default instance and would overwrite documents with a constant value. Use a " +
+ "constant or captured value instead.");
+ }
+
var lambda = Expression.Lambda(expression, parameters);
var compiledLambda = lambda.Compile();
return compiledLambda.DynamicInvoke(Activator.CreateInstance(parameters[0].Type));
}
+ private static bool ReferencesParameter(Expression expression, IReadOnlyList parameters)
+ {
+ var detector = new ParameterReferenceDetector(parameters);
+ detector.Visit(expression);
+ return detector.Found;
+ }
+
+ private sealed class ParameterReferenceDetector : ExpressionVisitor
+ {
+ private readonly IReadOnlyList _parameters;
+
+ public ParameterReferenceDetector(IReadOnlyList parameters)
+ {
+ _parameters = parameters;
+ }
+
+ public bool Found { get; private set; }
+
+ protected override Expression VisitParameter(ParameterExpression node)
+ {
+ if (_parameters.Contains(node))
+ {
+ Found = true;
+ }
+
+ return base.VisitParameter(node);
+ }
+ }
+
private static bool IsSimpleType(Type type)
{
return type.IsPrimitive || type.IsEnum || type == typeof(string) || type == typeof(decimal) || type == typeof(DateTime);
diff --git a/src/eQuantic.Core.Data.EntityFramework.MongoDb/eQuantic.Core.Data.EntityFramework.MongoDb.Net10.csproj b/src/eQuantic.Core.Data.EntityFramework.MongoDb/eQuantic.Core.Data.EntityFramework.MongoDb.Net10.csproj
index 63a7812..2a64b88 100644
--- a/src/eQuantic.Core.Data.EntityFramework.MongoDb/eQuantic.Core.Data.EntityFramework.MongoDb.Net10.csproj
+++ b/src/eQuantic.Core.Data.EntityFramework.MongoDb/eQuantic.Core.Data.EntityFramework.MongoDb.Net10.csproj
@@ -4,8 +4,7 @@
Core Data library for Entity Framework and Mongo DB
eQuantic.Core.Data.EntityFramework.MongoDb
- 10.0.2.0
- eQuantic Systems
+ 10.1.0.0
net10.0
eQuantic.Core.Data.EntityFramework.MongoDb
eQuantic.Core.Data.EntityFramework.MongoDb
@@ -13,21 +12,8 @@
Entity ignorant persistance with Repository Pattern for Entity
Framework
- https://github.com/eQuantic/core-data-entityframework
- ../../artifacts/
- false
- false
- false
- True
- https://github.com/eQuantic/core-data-entityframework
- Git
- LICENSE
- README.md
- Copyright © 2016
- 10.0.2.0
- 10.0.2.0
- Icon.png
- latest
+ 10.1.0.0
+ 10.1.0.0
enable
enable
@@ -35,17 +21,17 @@
-
-
-
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
@@ -53,8 +39,6 @@
-
-
diff --git a/src/eQuantic.Core.Data.EntityFramework.MongoDb/eQuantic.Core.Data.EntityFramework.MongoDb.csproj b/src/eQuantic.Core.Data.EntityFramework.MongoDb/eQuantic.Core.Data.EntityFramework.MongoDb.Net8.csproj
similarity index 64%
rename from src/eQuantic.Core.Data.EntityFramework.MongoDb/eQuantic.Core.Data.EntityFramework.MongoDb.csproj
rename to src/eQuantic.Core.Data.EntityFramework.MongoDb/eQuantic.Core.Data.EntityFramework.MongoDb.Net8.csproj
index fe809e0..76c7c37 100644
--- a/src/eQuantic.Core.Data.EntityFramework.MongoDb/eQuantic.Core.Data.EntityFramework.MongoDb.csproj
+++ b/src/eQuantic.Core.Data.EntityFramework.MongoDb/eQuantic.Core.Data.EntityFramework.MongoDb.Net8.csproj
@@ -4,8 +4,7 @@
Core Data library for Entity Framework and Mongo DB
eQuantic.Core.Data.EntityFramework.MongoDb
- 8.1.2.0
- eQuantic Systems
+ 8.2.0.0
net8.0
eQuantic.Core.Data.EntityFramework.MongoDb
eQuantic.Core.Data.EntityFramework.MongoDb
@@ -13,21 +12,8 @@
Entity ignorant persistance with Repository Pattern for Entity
Framework
- https://github.com/eQuantic/core-data-entityframework
- ../../artifacts/
- false
- false
- false
- True
- https://github.com/eQuantic/core-data-entityframework
- Git
- LICENSE
- README.md
- Copyright © 2016
- 8.1.2.0
- 8.1.2.0
- Icon.png
- latest
+ 8.2.0.0
+ 8.2.0.0
enable
enable
@@ -35,17 +21,17 @@
-
-
-
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
@@ -53,8 +39,6 @@
-
-
diff --git a/src/eQuantic.Core.Data.EntityFramework.MongoDb/eQuantic.Core.Data.EntityFramework.MongoDb.Net9.csproj b/src/eQuantic.Core.Data.EntityFramework.MongoDb/eQuantic.Core.Data.EntityFramework.MongoDb.Net9.csproj
deleted file mode 100644
index 3ca09eb..0000000
--- a/src/eQuantic.Core.Data.EntityFramework.MongoDb/eQuantic.Core.Data.EntityFramework.MongoDb.Net9.csproj
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-
-
- Core Data library for Entity Framework and Mongo DB
- eQuantic.Core.Data.EntityFramework.MongoDb
- 9.1.2.0
- eQuantic Systems
- net9.0
- eQuantic.Core.Data.EntityFramework.MongoDb
- eQuantic.Core.Data.EntityFramework.MongoDb
- eQuantic;Core;Data;Library;Repository;Pattern;MongoDB
- Entity ignorant persistance with Repository Pattern for Entity
- Framework
-
- https://github.com/eQuantic/core-data-entityframework
- ../../artifacts/
- false
- false
- false
- True
- https://github.com/eQuantic/core-data-entityframework
- Git
- LICENSE
- README.md
- Copyright © 2016
- 9.1.2.0
- 9.1.2.0
- Icon.png
- latest
-
- enable
- enable
-
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <_Parameter1>$(AssemblyName).Tests
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/eQuantic.Core.Data.EntityFramework.MySql/Repository/Extensions/SqlConfigurationExtensions.cs b/src/eQuantic.Core.Data.EntityFramework.MySql/Repository/Extensions/SqlConfigurationExtensions.cs
deleted file mode 100644
index 08a7235..0000000
--- a/src/eQuantic.Core.Data.EntityFramework.MySql/Repository/Extensions/SqlConfigurationExtensions.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using eQuantic.Core.Data.Repository.Config;
-using Microsoft.EntityFrameworkCore;
-
-namespace eQuantic.Core.Data.EntityFramework.MySql.Repository.Extensions;
-
-public static class SqlConfigurationExtensions
-{
- private const int DefaultCommandTimeout = 60;
-
- public static int GetCommandTimeout(this SqlConfiguration config, DbContext context)
- {
- return config?.CommandTimeout ?? context?.Database.GetCommandTimeout() ?? DefaultCommandTimeout;
- }
-}
diff --git a/src/eQuantic.Core.Data.EntityFramework.MySql/Repository/Set.cs b/src/eQuantic.Core.Data.EntityFramework.MySql/Repository/Set.cs
index 2e31064..957e526 100644
--- a/src/eQuantic.Core.Data.EntityFramework.MySql/Repository/Set.cs
+++ b/src/eQuantic.Core.Data.EntityFramework.MySql/Repository/Set.cs
@@ -1,268 +1,16 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Linq.Expressions;
-using System.Threading;
-using System.Threading.Tasks;
-using eQuantic.Core.Data.EntityFramework.Repository;
-using eQuantic.Core.Data.EntityFramework.Repository.Extensions;
+using eQuantic.Core.Data.EntityFramework.Relational.Repository;
using eQuantic.Core.Data.Repository;
-using eQuantic.Core.Data.Repository.Config;
-using eQuantic.Linq.Extensions;
using Microsoft.EntityFrameworkCore;
namespace eQuantic.Core.Data.EntityFramework.MySql.Repository;
-public class Set : SetBase where TEntity : class, IEntity, new()
+///
+/// MySQL entity set. The implementation lives in ; this type
+/// is preserved for source compatibility.
+///
+public class Set : RelationalSet where TEntity : class, IEntity
{
public Set(DbContext context) : base(context)
{
}
-
- public override long DeleteMany(Expression> filter)
- {
- return InternalDbSet.Where(filter).ExecuteDelete();
- }
-
- public override async Task DeleteManyAsync(Expression> filter,
- CancellationToken cancellationToken = default)
- {
- return await InternalDbSet.Where(filter).ExecuteDeleteAsync(cancellationToken);
- }
-
- public void LoadCollection(TChildEntity item,
- Expression>> selector)
- where TChildEntity : class
- where TComplexProperty : class
- {
- DbContext.Entry(item).Collection(selector).Load();
- }
-
- public void LoadCollection(TChildEntity item, string propertyName)
- where TChildEntity : class
- {
- DbContext.Entry(item).Collection(propertyName).Load();
- }
-
- public async Task LoadCollectionAsync(TChildEntity item,
- Expression>> selector)
- where TChildEntity : class where TComplexProperty : class
- {
- await DbContext.Entry(item).Collection(selector).LoadAsync();
- }
-
- public async Task LoadCollectionAsync(TChildEntity item, string propertyName)
- where TChildEntity : class
- {
- await DbContext.Entry(item).Collection(propertyName).LoadAsync();
- }
-
- public void LoadProperties(TEntity entity, params string[] properties)
- {
- if (properties is not { Length: > 0 })
- {
- return;
- }
-
- foreach (var property in properties)
- {
- if (string.IsNullOrEmpty(property))
- {
- continue;
- }
-
- var props = property.Split('.');
-
- if (props.Length == 1)
- {
- LoadProperty(entity, property);
- }
- else
- {
- LoadCascade(props, entity);
- }
- }
- }
-
- public async Task LoadPropertiesAsync(TEntity entity, params string[] properties)
- {
- if (properties is { Length: > 0 })
- {
- foreach (var property in properties)
- {
- if (string.IsNullOrEmpty(property))
- {
- continue;
- }
-
- var props = property.Split('.');
-
- if (props.Length == 1)
- {
- await LoadPropertyAsync(entity, property);
- }
- else
- {
- await LoadCascadeAsync(props, entity);
- }
- }
- }
- }
-
- public void LoadProperty(TChildEntity item,
- Expression> selector)
- where TChildEntity : class
- where TComplexProperty : class
- {
- DbContext.Entry(item).Reference(selector).Load();
- }
-
- public void LoadProperty(TChildEntity item, string propertyName)
- where TChildEntity : class
- {
- if (typeof(IEnumerable).IsAssignableFrom(typeof(TChildEntity).GetProperty(propertyName)!.PropertyType))
- {
- DbContext.Entry(item).Collection(propertyName).Load();
- }
- else
- {
- DbContext.Entry(item).Reference(propertyName).Load();
- }
- }
-
- public async Task LoadPropertyAsync(TChildEntity item,
- Expression> selector, CancellationToken cancellationToken = default)
- where TChildEntity : class where TComplexProperty : class
- {
- await DbContext.Entry(item).Reference(selector).LoadAsync(cancellationToken);
- }
-
- public async Task LoadPropertyAsync(TChildEntity item, string propertyName,
- CancellationToken cancellationToken = default)
- where TChildEntity : class
- {
- if (typeof(IEnumerable).IsAssignableFrom(typeof(TChildEntity).GetProperty(propertyName)!.PropertyType))
- {
- await DbContext.Entry(item).Collection(propertyName).LoadAsync(cancellationToken);
- }
- else
- {
- await DbContext.Entry(item).Reference(propertyName).LoadAsync(cancellationToken);
- }
- }
-
- public override long UpdateMany(Expression> filter,
- Expression> updateExpression)
- {
- var convertedExpression = ExpressionConverter.ConvertExpression(updateExpression);
- return InternalDbSet.Where(filter).ExecuteUpdate(convertedExpression);
- }
-
- public override async Task UpdateManyAsync(Expression> filter,
- Expression> updateExpression, CancellationToken cancellationToken = default)
- {
- var convertedExpression = ExpressionConverter.ConvertExpression(updateExpression);
- return await InternalDbSet.Where(filter).ExecuteUpdateAsync(convertedExpression, cancellationToken);
- }
-
- private void LoadCascade(string[] props, object obj, int index = 0)
- {
- if (obj == null)
- {
- return;
- }
-
- var prop = obj.GetType().GetProperty(props[index]);
- var nextObj = prop?.GetValue(obj);
- if (nextObj == null)
- {
- LoadProperty(obj, props[index]);
- nextObj = prop?.GetValue(obj);
- }
-
- if (props.Length > index + 1)
- {
- LoadCascade(props, nextObj, index + 1);
- }
- }
-
- private async Task LoadCascadeAsync(string[] props, object obj, int index = 0)
- {
- if (obj == null)
- {
- return;
- }
-
- var prop = obj.GetType().GetProperty(props[index]);
- var nextObj = prop?.GetValue(obj);
- if (nextObj == null)
- {
- await LoadPropertyAsync(obj, props[index]);
- nextObj = prop?.GetValue(obj);
- }
-
- if (props.Length > index + 1)
- {
- await LoadCascadeAsync(props, nextObj, index + 1);
- }
- }
-
- internal Expression> GetExpression(TKey id)
- {
- return DbContext.GetFindByKeyExpression