Skip to content

Repository files navigation

eQuantic.Core.Data.EntityFramework

The Entity Framework Core implementation of eQuantic.Core.Data. You code against the provider-agnostic IRepository<TEntity, TKey> / IUnitOfWork contracts; this package supplies the EF Core engine for SQL Server, PostgreSQL, MySQL, MongoDB and Azure Cosmos DB.

// A repository over any IEntity<TKey>, obtained from your DbContext-backed unit of work:
var repo = unitOfWork.GetAsyncRepository<OrderData, Guid>();

// Query typed and fluent — one QueryOptions, no magic strings:
var page = await repo.GetPagedAsync(
    PageRequest.Of(pageIndex: 1, pageSize: 20),
    new QueryOptions<OrderData>()
        .Where(o => o.Total, FilterOperator.GreaterThan, 100m)
        .And(o => o.Customer.Name, FilterOperator.Contains, term)
        .OrderByDescending(o => o.CreatedAt)
        .Include(nameof(OrderData.Customer))
        .NoTracking());

// page is a PagedResult<OrderData>: Items + TotalCount + PageIndex/PageSize/PageCount + Has*Page

Why

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

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

Getting started

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

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

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

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

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

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

public interface IAppUnitOfWork : IQueryableUnitOfWork { }

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

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

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

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

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

What this package gives you

eQuantic.Core.Data.Abstractions defines the contractsIRepository, IUnitOfWork, QueryOptions, PageRequest, PagedResult, specifications — with the persistence engine kept out of the type signatures (IRepository<TEntity, TKey>, not IRepository<TUnitOfWork, TEntity, TKey>).

That is the only eQuantic package this one depends on. It carries no engine: installing an EF Core provider from here brings the vocabulary and Entity Framework, and nothing of the native eQuantic.Core.Data engine — no SQL renderer, no expression interpreters, no analyzers. If you want the native engine as well, reference it yourself; the two implement the same contracts side by side.

This package is the Entity Framework Core implementation of those contracts. It translates a single QueryOptions<TEntity> into an EF IQueryable — applying, in order, custom before hooks, the specification and predicate filter, eager Includes, sortings (server-side / EF-translatable), AsNoTracking, IgnoreQueryFilters, query tags and custom after hooks — and returns PagedResult<T> from paged reads. The relational providers also carry a parameterized raw-SQL executor (functions and stored procedures, always via DbParameter).

How you query

QueryOptions<TEntity> mirrors the eQuantic.Linq query builders, so filters read like code and fail at compile time — not at runtime:

new QueryOptions<OrderData>()
    .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();

You reach for whichever filter form fits — member selector, string path, ISpecification<T>, Expression<Func<T,bool>>, a serialized ExpressionModel<T>, 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.

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)
eQuantic.Core.Data.EntityFramework.CosmosDb Azure Cosmos DB (EF Core provider)

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

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

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

Cosmos has no server-side set-based delete/update (ExecuteDelete/ExecuteUpdate are relational-only), so DeleteMany/UpdateMany load the matching documents and modify them through the context.

Versioning

Pick the package major that matches your runtime — 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.

Checking the database against your model

Entity Framework records which migrations ran, and has-pending-model-changes compares the model to the migrations. Neither looks at the schema — so neither can tell you that a column was altered by hand on staging, that a migration stopped halfway, or that an environment was restored from a backup older than the last release. Only looking answers those.

services.AddDbContext<ShopContext>(options => options.UseNpgsql(connectionString));
services.AddDriftCheck<ShopContext>();

That registers IDatabaseSnapshotSource, which describes both sides of the comparison — what the model says the tables should be, and what the database actually has — in the provider's own store-type vocabulary, so a healthy schema compares silently:

var source = provider.GetRequiredService<IDatabaseSnapshotSource>();
var report = DriftComparer.Compare(source.Expect(), await source.ObserveAsync());

if (report.Breaks)
{
    // something the application reads on every query is not there, or not as it expects
}

report.Breaks separates the differences that stop the application from the ones that do not: a column the model does not map belongs to somebody else — databases get shared — and tables you do not map are not read at all. The command-line tool eqdata drift runs the same check and exits non-zero on the first kind, which makes it a deployment gate.

Reading the catalogue is provider-specific: PostgreSQL, MySQL and SQL Server are covered. Any other provider says it cannot answer rather than answering wrongly.

Generating migrations is deliberately absent. Entity Framework already does that, from the same model, and better than a second generator could. What this adds is the part it leaves out.

Learn more

MIT © eQuantic Tech

About

No description, website, or topics provided.

Resources

Security policy

Stars

1 star

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages