Skip to content

Feature: Entity Framework Core provider (BLite.EntityFrameworkCore) #9

@mrdevrobot

Description

@mrdevrobot

Summary

Build BLite.EntityFrameworkCore — an EF Core non-relational provider that sits on top of BLite.Client (gRPC) or BLite.Core (embedded), mapping DbContext/DbSet<T>/LINQ/SaveChanges to BLite's typed collection CRUD and QueryDescriptor.

Feasible with significant effort: the query pipeline is ~80% ready, but schema management requires custom implementation.


Feasibility Analysis

What BLite already provides ✅

  • Full LINQ → QueryDescriptor pipeline (Where, OrderBy, Select, Skip, Take, Count, Sum, Average)
  • Typed collections with IDocumentMapper<TId, T> (BSON ↔ C# bidirectional)
  • Async CRUD: InsertAsync, UpdateAsync, DeleteAsync, FindByIdAsync, InsertBulkAsync
  • Transactions: BeginTransactionAsync → Commit/Rollback
  • Secondary indexes with LINQ expression key selectors
  • Source Generators for mapper code generation
  • IBLiteQueryable<T> with server-side push-down
  • DocumentDbContext with Unit-of-Work pattern (embedded mode)

Gaps the provider must bridge

Gap Severity Mitigation
No relationships / foreign keys By design BLite is a document database — no navigations, no Include(). Owned entities only (embedded BSON sub-documents).
No GROUP BY server push-down MEDIUM Client-side evaluation (like MongoDB/Cosmos EF providers)
No schema migrations MEDIUM Only EnsureCreated (create collections + indexes); no Migrations runner (BLite is schema-less)
No auto-increment IDs LOW Default to ObjectId/Guid; optional int via engine sequence
No composite keys LOW Reject at model validation with clear error
No optimistic concurrency LOW Tracked in dedicated issue on BLite engine repo — requires engine-level auto-increment field or _version convention

Change tracking: EF Core's built-in snapshot change tracker works out of the box — no custom implementation needed.


Proposed Architecture

User Code: DbContext + DbSet<T> + LINQ + SaveChanges
    │
    ▼
BLite.EntityFrameworkCore (new project)
  ├─ UseBLite(connectionString) — gRPC mode
  ├─ UseBLiteEmbedded(path) — embedded mode (local dev / testing)
  ├─ BLiteOptionsExtension (DI wiring)
  ├─ BLiteDatabase : IDatabase (SaveChanges → Insert/Update/Delete)
  ├─ BLiteTransactionManager (wraps transaction)
  ├─ BLiteQueryCompilationContext (LINQ → QueryDescriptor)
  ├─ BLiteConventionSetBuilder (entity→collection, owned→embedded)
  ├─ BLiteTypeMappingSource (CLR → BsonType)
  ├─ BLiteValueGeneratorSelector (ObjectId/Guid auto-gen)
  └─ BLiteDatabaseCreator (EnsureCreated/Deleted)
    │
    ├─► BLite.Client (gRPC) → BLite.Server     [production]
    └─► BLite.Core (embedded BLiteEngine)       [dev / test]

Connection String Format

UseBLite("Host=localhost;Port=2626;ApiKey=my-key;UseTls=true")
UseBLiteEmbedded("Data Source=./data/mydb.db;PageSize=16384")

Implementation Plan

Phase 1: Project scaffold & DI plumbing

  1. Create src/BLite.EntityFrameworkCore/BLite.EntityFrameworkCore.csproj (net10.0, ref EF Core 10.x)
  2. Implement UseBLite(connectionString) → parses host/port/apiKey/tls, creates BLiteClient
  3. Implement UseBLiteEmbedded(connectionString) → parses data source path, creates BLiteEngine directly
  4. BLiteOptionsExtension : IDbContextOptionsExtension — registers all provider services
  5. Register core replacements: IDatabase, IDbContextTransactionManager, IQueryCompilationContextFactory, ITypeMappingSource, IModelValidator, IDatabaseCreator, IValueGeneratorSelector

Phase 2: Model & conventions

  1. BLiteConventionSetBuilder: entity → collection, owned → embedded BSON sub-document, reject many-to-many and navigation properties
  2. BLiteTypeMappingSource: string/int/long/double/decimal/bool/DateTime/Guid/byte[] → BsonType
  3. BLiteModelValidator: error on composite keys, TPH/TPT inheritance, table splitting, navigation properties to non-owned entities

Phase 3: Database CRUD (IDatabase)

  1. BLiteDatabase.SaveChangesAsync(IList<IUpdateEntry>): group entries by entity type, Added→Insert, Modified→Update, Deleted→Delete, bulk batch where possible
  2. Internal abstraction: IBLiteDataStore with two implementations — RemoteDataStore (BLite.Client/gRPC) and EmbeddedDataStore (BLite.Core)
  3. BLiteTransactionManager: wrap transaction in IDbContextTransaction (gRPC: RemoteTransaction; embedded: BLiteSession)
  4. BLiteDatabaseCreator: EnsureCreatedAsync creates collections + indexes, EnsureDeletedAsync drops

Phase 4: Query pipeline

  1. BLiteQueryableMethodTranslatingVisitor: translate ShapedQueryExpressionQueryDescriptor (reuse existing FilterOp mappings)
  2. BLiteShapedQueryCompilingVisitor: compile into executable delegate, IAsyncEnumerable<T> via query
  3. Special methods: Any() → Take(1), Contains()FilterOp.In, Sum/Average → server push-down

Phase 5: Value generation & materialization

  1. BLiteValueGeneratorSelector: ObjectId/Guid auto-generation for Added entities
  2. EfCoreDocumentMapper<TId, T>: bridge between IEntityType metadata and BsonDocument

Phase 6: Testing & samples

  1. Unit tests: type mapping, convention validation, query translation, SaveChanges grouping
  2. Integration tests using embedded mode (no external server needed — runs in-process)
  3. Integration tests using gRPC mode (requires running BLite.Server)
  4. Sample BLite.EfCore.Sample: Blog/Post model with owned Address type

Key Design Decisions

Decision Choice Rationale
Provider type Non-relational (like MongoDB EF / Cosmos EF) BLite is a document database; no SQL generation, views, stored procedures
Relationships None — owned entities only (embedded BSON) BLite has no foreign keys, no JOINs. Navigations to non-owned entities are rejected at model validation.
Migrations None — EnsureCreated/EnsureDeleted only BLite is schema-less
Change tracking EF Core built-in snapshot tracker Works out of the box; no proxy or lazy-loading needed
Dual target gRPC (BLite.Client) and embedded (BLite.Core) from day one Embedded mode is essential for local dev and testing without running BLite.Server
Connection ConnectionString-based API Standard pattern in EF Core ecosystem; familiar to developers
Reference impl Microsoft.EntityFrameworkCore.Cosmos Closest architectural match — same document-database constraints

Open Questions

  1. Optimistic concurrency: BLite has no built-in _etag or auto-increment fields. See dedicated issue on the BLite engine repo for engine-level support. Until then, concurrency tokens are not supported by the provider.
  2. Enum mapping: Default to string storage or integer? (Recommend string for readability, configurable)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions